From ba05917796b4cc88ef64c3fd47f30e2d1fe7814e Mon Sep 17 00:00:00 2001 From: "F.D.Castel" Date: Wed, 11 Mar 2026 23:06:34 -0300 Subject: [PATCH 01/10] Add disconnected RowSet class with typed Row access --- src/fb-cpp/Row.h | 1231 ++++++++++++++++++++++++++++++++++++++ src/fb-cpp/RowSet.cpp | 62 ++ src/fb-cpp/RowSet.h | 167 ++++++ src/fb-cpp/Statement.cpp | 2 + src/fb-cpp/Statement.h | 752 ++--------------------- src/fb-cpp/fb-cpp.h | 1 + src/test/RowSet.cpp | 218 +++++++ 7 files changed, 1736 insertions(+), 697 deletions(-) create mode 100644 src/fb-cpp/Row.h create mode 100644 src/fb-cpp/RowSet.cpp create mode 100644 src/fb-cpp/RowSet.h create mode 100644 src/test/RowSet.cpp diff --git a/src/fb-cpp/Row.h b/src/fb-cpp/Row.h new file mode 100644 index 0000000..e0c940e --- /dev/null +++ b/src/fb-cpp/Row.h @@ -0,0 +1,1231 @@ +/* + * 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 + +#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: + Row() = default; + + /// + /// @brief Constructs a Row view over the given message buffer. + /// + /// @param message Pointer to the raw row data. + /// @param descriptors Column descriptors. + /// @param numericConverter Numeric converter. + /// @param calendarConverter Calendar converter. + /// + Row(const std::byte* message, const std::vector& descriptors, + impl::NumericConverter& numericConverter, impl::CalendarConverter& calendarConverter) + : message{message}, + descriptors{&descriptors}, + numericConverter{&numericConverter}, + calendarConverter{&calendarConverter} + { + } + + 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 getCalendarConverter().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