diff --git a/src/fb-cpp/Statement.h b/src/fb-cpp/Statement.h index 09d2f93..8c880fc 100644 --- a/src/fb-cpp/Statement.h +++ b/src/fb-cpp/Statement.h @@ -37,6 +37,7 @@ #include "SmartPtrs.h" #include "Exception.h" #include "StructBinding.h" +#include "VariantTypeTraits.h" #include #include #include @@ -1565,7 +1566,7 @@ namespace fbcpp } /// - /// @brief Reads a Firebird 128-bit integer column. + /// @brief Reads a Firebird scaled 128-bit integer column. /// std::optional getScaledOpaqueInt128(unsigned index) { @@ -1585,7 +1586,7 @@ namespace fbcpp descriptor.scale}; default: - throwInvalidType("OpaqueInt128", descriptor.adjustedType); + throwInvalidType("ScaledOpaqueInt128", descriptor.adjustedType); } } @@ -2130,6 +2131,72 @@ namespace fbcpp setTuple(value, std::make_index_sequence{}); } + /// + /// @brief Retrieves a column value as a user-defined variant type. + /// @tparam V A std::variant type with possible C++ types. Use std::monostate for NULL. + /// @param index Zero-based column index. + /// @return The variant with column value, or std::monostate if NULL. + /// @throws FbCppException if NULL but variant lacks std::monostate. + /// @throws FbCppException if SQL type cannot convert to any alternative. + /// + template + V get(unsigned index) + { + using namespace impl::reflection; + + static_assert(variantAlternativesSupportedV, + "Variant contains unsupported types. All variant alternatives must be types supported by fb-cpp " + "(e.g., std::int32_t, std::string, Date, ScaledOpaqueInt128, etc.). Check VariantTypeTraits.h for the " + "complete list of supported types."); + + assert(isValid()); + + const auto& descriptor = getOutDescriptor(index); + + if (isNull(index)) + { + if constexpr (variantContainsV) + return V{std::monostate{}}; + else + { + throw FbCppException( + "NULL value encountered but variant does not contain std::monostate at index " + + std::to_string(index)); + } + } + + return getVariantValue(index, descriptor); + } + + /// + /// @brief Sets a parameter from a variant value. + /// @tparam V A std::variant type. + /// @param index Zero-based parameter index. + /// @param value The variant containing the value. + /// + template + void set(unsigned index, const V& value) + { + using namespace impl::reflection; + + static_assert(variantAlternativesSupportedV, + "Variant contains unsupported types. All variant alternatives must be types supported by fb-cpp " + "(e.g., std::int32_t, std::string, Date, ScaledOpaqueInt128, etc.). Check VariantTypeTraits.h for the " + "complete list of supported types."); + + std::visit( + [this, index](const auto& v) + { + using T = std::decay_t; + + if constexpr (std::is_same_v) + setNull(index); + else + set(index, v); + }, + value); + } + private: /// /// @brief Validates and returns the descriptor for the given input parameter index. @@ -2174,6 +2241,8 @@ namespace fbcpp if constexpr (isOptionalV) return get(index); + else if constexpr (isVariantV) + return get(index); else { auto opt = get>(index); @@ -2220,6 +2289,211 @@ namespace fbcpp (set(static_cast(Is), std::get(value)), ...); } + /// + /// @brief Helper to retrieve a column value as a variant. + /// Uses priority: exact type match first, then declaration order for conversions. + /// + template + V getVariantValue(unsigned index, const Descriptor& descriptor) + { + using namespace impl::reflection; + + // Try exact type matches first based on SQL type + switch (descriptor.adjustedType) + { + case DescriptorAdjustedType::BOOLEAN: + if constexpr (variantContainsV) + return V{get>(index).value()}; + break; + + case DescriptorAdjustedType::INT16: + if (descriptor.scale != 0) + { + // For scaled numbers, prefer exact scaled type, then larger scaled types + if constexpr (variantContainsV) + return V{get>(index).value()}; + if constexpr (variantContainsV) + return V{get>(index).value()}; + if constexpr (variantContainsV) + return V{get>(index).value()}; +#if FB_CPP_USE_BOOST_MULTIPRECISION != 0 + if constexpr (variantContainsV) + return V{get>(index).value()}; +#endif + } + if constexpr (variantContainsV) + return V{get>(index).value()}; + break; + + case DescriptorAdjustedType::INT32: + if (descriptor.scale != 0) + { + // For scaled numbers, prefer exact scaled type, then larger scaled types + if constexpr (variantContainsV) + return V{get>(index).value()}; + if constexpr (variantContainsV) + return V{get>(index).value()}; +#if FB_CPP_USE_BOOST_MULTIPRECISION != 0 + if constexpr (variantContainsV) + return V{get>(index).value()}; +#endif + } + if constexpr (variantContainsV) + return V{get>(index).value()}; + break; + + case DescriptorAdjustedType::INT64: + if (descriptor.scale != 0) + { + // For scaled numbers, prefer exact scaled type, then larger scaled types + if constexpr (variantContainsV) + return V{get>(index).value()}; +#if FB_CPP_USE_BOOST_MULTIPRECISION != 0 + if constexpr (variantContainsV) + return V{get>(index).value()}; +#endif + } + if constexpr (variantContainsV) + return V{get>(index).value()}; + break; + +#if FB_CPP_USE_BOOST_MULTIPRECISION != 0 + case DescriptorAdjustedType::INT128: + // Prefer opaque (native Firebird) types first + if constexpr (variantContainsV) + return V{get>(index).value()}; + if (descriptor.scale != 0) + { + if constexpr (variantContainsV) + return V{get>(index).value()}; + } + if constexpr (variantContainsV) + return V{get>(index).value()}; + break; +#endif + + case DescriptorAdjustedType::FLOAT: + if constexpr (variantContainsV) + return V{get>(index).value()}; + break; + + case DescriptorAdjustedType::DOUBLE: + if constexpr (variantContainsV) + return V{get>(index).value()}; + break; + +#if FB_CPP_USE_BOOST_MULTIPRECISION != 0 + case DescriptorAdjustedType::DECFLOAT16: + // Prefer opaque (native Firebird) types first + if constexpr (variantContainsV) + return V{get>(index).value()}; + if constexpr (variantContainsV) + return V{get>(index).value()}; + break; + + case DescriptorAdjustedType::DECFLOAT34: + // Prefer opaque (native Firebird) types first + if constexpr (variantContainsV) + return V{get>(index).value()}; + if constexpr (variantContainsV) + return V{get>(index).value()}; + break; +#endif + + case DescriptorAdjustedType::STRING: + if constexpr (variantContainsV) + return V{get>(index).value()}; + break; + + case DescriptorAdjustedType::DATE: + // Prefer opaque (native Firebird) types first + if constexpr (variantContainsV) + return V{get>(index).value()}; + if constexpr (variantContainsV) + return V{get>(index).value()}; + break; + + case DescriptorAdjustedType::TIME: + // Prefer opaque (native Firebird) types first + if constexpr (variantContainsV) + return V{get>(index).value()}; + if constexpr (variantContainsV) + return V{get>(index).value()}; + break; + + case DescriptorAdjustedType::TIMESTAMP: + // Prefer opaque (native Firebird) types first + if constexpr (variantContainsV) + return V{get>(index).value()}; + if constexpr (variantContainsV) + return V{get>(index).value()}; + break; + + case DescriptorAdjustedType::TIME_TZ: + // Prefer opaque (native Firebird) types first + if constexpr (variantContainsV) + return V{get>(index).value()}; + if constexpr (variantContainsV) + return V{get>(index).value()}; + break; + + case DescriptorAdjustedType::TIMESTAMP_TZ: + // Prefer opaque (native Firebird) types first + if constexpr (variantContainsV) + return V{get>(index).value()}; + if constexpr (variantContainsV) + return V{get>(index).value()}; + break; + + case DescriptorAdjustedType::BLOB: + if constexpr (variantContainsV) + return V{get>(index).value()}; + break; + + default: + break; + } + + // No exact match found, try variant alternatives in declaration order + return tryVariantAlternatives(index, descriptor); + } + + /// + /// @brief Recursively tries variant alternatives for type conversion. + /// + template + V tryVariantAlternatives(unsigned index, [[maybe_unused]] const Descriptor& descriptor) + { + using namespace impl::reflection; + + if constexpr (I >= std::variant_size_v) + { + throw FbCppException( + "Cannot convert SQL type to any variant alternative at index " + std::to_string(index)); + } + else + { + using Alt = std::variant_alternative_t; + + if constexpr (std::is_same_v) + { + // Skip monostate in non-null case + return tryVariantAlternatives(index, descriptor); + } + else if constexpr (isOpaqueTypeV) + { + // Skip opaque types - they only match exact SQL types, no conversions + return tryVariantAlternatives(index, descriptor); + } + else + { + // Try this alternative - get will throw if conversion fails + auto opt = get>(index); + return V{std::move(opt.value())}; + } + } + } + /// /// @brief Converts and writes numeric parameter values following descriptor rules. /// diff --git a/src/fb-cpp/StructBinding.h b/src/fb-cpp/StructBinding.h index a8c5af8..9b12478 100644 --- a/src/fb-cpp/StructBinding.h +++ b/src/fb-cpp/StructBinding.h @@ -30,6 +30,7 @@ #include #include #include +#include namespace fbcpp @@ -66,6 +67,50 @@ namespace fbcpp::impl::reflection template inline constexpr bool isOptionalV = IsOptional::value; + /// + /// Helper to detect std::variant. + /// + template + struct IsVariant : std::false_type + { + }; + + template + struct IsVariant> : std::true_type + { + }; + + template + inline constexpr bool isVariantV = IsVariant::value; + + /// + /// Check if variant contains a specific type. + /// + template + struct VariantContains : std::false_type + { + }; + + template + struct VariantContains> : std::disjunction...> + { + }; + + template + inline constexpr bool variantContainsV = VariantContains::value; + + /// + /// Helper to detect opaque (non-convertible) types that only match exact SQL types. + /// Forward declarations for opaque types from types.h. + /// + template + struct IsOpaqueType : std::false_type + { + }; + + template + inline constexpr bool isOpaqueTypeV = IsOpaqueType::value; + /// Universal converter for aggregate initialization detection. struct UniversalType { @@ -334,5 +379,14 @@ namespace fbcpp::impl::reflection using FieldType = std::remove_reference_t>>; } // namespace fbcpp::impl::reflection +namespace fbcpp +{ + /// + /// Concept constraining types to std::variant types. + /// + template + concept VariantLike = impl::reflection::isVariantV>; +} // namespace fbcpp + #endif // FBCPP_STRUCT_BINDING_H diff --git a/src/fb-cpp/VariantTypeTraits.h b/src/fb-cpp/VariantTypeTraits.h new file mode 100644 index 0000000..57bdbf1 --- /dev/null +++ b/src/fb-cpp/VariantTypeTraits.h @@ -0,0 +1,253 @@ +/* + * MIT License + * + * Copyright (c) 2025 Adriano dos Santos Fernandes + * + * 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_VARIANT_TYPE_TRAITS_H +#define FBCPP_VARIANT_TYPE_TRAITS_H + +#include "config.h" +#include "types.h" +#include "StructBinding.h" + +#if FB_CPP_USE_BOOST_MULTIPRECISION != 0 +#include +#include +#endif + + +namespace fbcpp::impl::reflection +{ + /// + /// Helper to check if a type is supported by the library for variant usage. + /// + template + struct IsSupportedVariantType : std::false_type + { + }; + + // Arithmetic types + template <> + struct IsSupportedVariantType : std::true_type + { + }; + template <> + struct IsSupportedVariantType : std::true_type + { + }; + template <> + struct IsSupportedVariantType : std::true_type + { + }; + template <> + struct IsSupportedVariantType : std::true_type + { + }; + template <> + struct IsSupportedVariantType : std::true_type + { + }; + template <> + struct IsSupportedVariantType : std::true_type + { + }; + + // Scaled numbers + template <> + struct IsSupportedVariantType : std::true_type + { + }; + template <> + struct IsSupportedVariantType : std::true_type + { + }; + template <> + struct IsSupportedVariantType : std::true_type + { + }; + + // String + template <> + struct IsSupportedVariantType : std::true_type + { + }; + + // Date/Time types + template <> + struct IsSupportedVariantType : std::true_type + { + }; + template <> + struct IsSupportedVariantType