3737#include " SmartPtrs.h"
3838#include " Exception.h"
3939#include " StructBinding.h"
40+ #include " VariantTypeTraits.h"
4041#include < charconv>
4142#include < cerrno>
4243#include < cstdlib>
@@ -1565,7 +1566,7 @@ namespace fbcpp
15651566 }
15661567
15671568 // /
1568- // / @brief Reads a Firebird 128-bit integer column.
1569+ // / @brief Reads a Firebird scaled 128-bit integer column.
15691570 // /
15701571 std::optional<ScaledOpaqueInt128> getScaledOpaqueInt128 (unsigned index)
15711572 {
@@ -1585,7 +1586,7 @@ namespace fbcpp
15851586 descriptor.scale };
15861587
15871588 default :
1588- throwInvalidType (" OpaqueInt128 " , descriptor.adjustedType );
1589+ throwInvalidType (" ScaledOpaqueInt128 " , descriptor.adjustedType );
15891590 }
15901591 }
15911592
@@ -2130,6 +2131,72 @@ namespace fbcpp
21302131 setTuple (value, std::make_index_sequence<N>{});
21312132 }
21322133
2134+ // /
2135+ // / @brief Retrieves a column value as a user-defined variant type.
2136+ // / @tparam V A std::variant type with possible C++ types. Use std::monostate for NULL.
2137+ // / @param index Zero-based column index.
2138+ // / @return The variant with column value, or std::monostate if NULL.
2139+ // / @throws FbCppException if NULL but variant lacks std::monostate.
2140+ // / @throws FbCppException if SQL type cannot convert to any alternative.
2141+ // /
2142+ template <VariantLike V>
2143+ V get (unsigned index)
2144+ {
2145+ using namespace impl ::reflection;
2146+
2147+ static_assert (variantAlternativesSupportedV<V>,
2148+ " Variant contains unsupported types. All variant alternatives must be types supported by fb-cpp "
2149+ " (e.g., std::int32_t, std::string, Date, ScaledOpaqueInt128, etc.). Check VariantTypeTraits.h for the "
2150+ " complete list of supported types." );
2151+
2152+ assert (isValid ());
2153+
2154+ const auto & descriptor = getOutDescriptor (index);
2155+
2156+ if (isNull (index))
2157+ {
2158+ if constexpr (variantContainsV<std::monostate, V>)
2159+ return V{std::monostate{}};
2160+ else
2161+ {
2162+ throw FbCppException (
2163+ " NULL value encountered but variant does not contain std::monostate at index " +
2164+ std::to_string (index));
2165+ }
2166+ }
2167+
2168+ return getVariantValue<V>(index, descriptor);
2169+ }
2170+
2171+ // /
2172+ // / @brief Sets a parameter from a variant value.
2173+ // / @tparam V A std::variant type.
2174+ // / @param index Zero-based parameter index.
2175+ // / @param value The variant containing the value.
2176+ // /
2177+ template <VariantLike V>
2178+ void set (unsigned index, const V& value)
2179+ {
2180+ using namespace impl ::reflection;
2181+
2182+ static_assert (variantAlternativesSupportedV<V>,
2183+ " Variant contains unsupported types. All variant alternatives must be types supported by fb-cpp "
2184+ " (e.g., std::int32_t, std::string, Date, ScaledOpaqueInt128, etc.). Check VariantTypeTraits.h for the "
2185+ " complete list of supported types." );
2186+
2187+ std::visit (
2188+ [this , index](const auto & v)
2189+ {
2190+ using T = std::decay_t <decltype (v)>;
2191+
2192+ if constexpr (std::is_same_v<T, std::monostate>)
2193+ setNull (index);
2194+ else
2195+ set (index, v);
2196+ },
2197+ value);
2198+ }
2199+
21332200 private:
21342201 // /
21352202 // / @brief Validates and returns the descriptor for the given input parameter index.
@@ -2174,6 +2241,8 @@ namespace fbcpp
21742241
21752242 if constexpr (isOptionalV<F>)
21762243 return get<F>(index);
2244+ else if constexpr (isVariantV<F>)
2245+ return get<F>(index);
21772246 else
21782247 {
21792248 auto opt = get<std::optional<F>>(index);
@@ -2220,6 +2289,211 @@ namespace fbcpp
22202289 (set (static_cast <unsigned >(Is), std::get<Is>(value)), ...);
22212290 }
22222291
2292+ // /
2293+ // / @brief Helper to retrieve a column value as a variant.
2294+ // / Uses priority: exact type match first, then declaration order for conversions.
2295+ // /
2296+ template <typename V>
2297+ V getVariantValue (unsigned index, const Descriptor& descriptor)
2298+ {
2299+ using namespace impl ::reflection;
2300+
2301+ // Try exact type matches first based on SQL type
2302+ switch (descriptor.adjustedType )
2303+ {
2304+ case DescriptorAdjustedType::BOOLEAN :
2305+ if constexpr (variantContainsV<bool , V>)
2306+ return V{get<std::optional<bool >>(index).value ()};
2307+ break ;
2308+
2309+ case DescriptorAdjustedType::INT16 :
2310+ if (descriptor.scale != 0 )
2311+ {
2312+ // For scaled numbers, prefer exact scaled type, then larger scaled types
2313+ if constexpr (variantContainsV<ScaledInt16, V>)
2314+ return V{get<std::optional<ScaledInt16>>(index).value ()};
2315+ if constexpr (variantContainsV<ScaledInt32, V>)
2316+ return V{get<std::optional<ScaledInt32>>(index).value ()};
2317+ if constexpr (variantContainsV<ScaledInt64, V>)
2318+ return V{get<std::optional<ScaledInt64>>(index).value ()};
2319+ #if FB_CPP_USE_BOOST_MULTIPRECISION != 0
2320+ if constexpr (variantContainsV<ScaledBoostInt128, V>)
2321+ return V{get<std::optional<ScaledBoostInt128>>(index).value ()};
2322+ #endif
2323+ }
2324+ if constexpr (variantContainsV<std::int16_t , V>)
2325+ return V{get<std::optional<std::int16_t >>(index).value ()};
2326+ break ;
2327+
2328+ case DescriptorAdjustedType::INT32 :
2329+ if (descriptor.scale != 0 )
2330+ {
2331+ // For scaled numbers, prefer exact scaled type, then larger scaled types
2332+ if constexpr (variantContainsV<ScaledInt32, V>)
2333+ return V{get<std::optional<ScaledInt32>>(index).value ()};
2334+ if constexpr (variantContainsV<ScaledInt64, V>)
2335+ return V{get<std::optional<ScaledInt64>>(index).value ()};
2336+ #if FB_CPP_USE_BOOST_MULTIPRECISION != 0
2337+ if constexpr (variantContainsV<ScaledBoostInt128, V>)
2338+ return V{get<std::optional<ScaledBoostInt128>>(index).value ()};
2339+ #endif
2340+ }
2341+ if constexpr (variantContainsV<std::int32_t , V>)
2342+ return V{get<std::optional<std::int32_t >>(index).value ()};
2343+ break ;
2344+
2345+ case DescriptorAdjustedType::INT64 :
2346+ if (descriptor.scale != 0 )
2347+ {
2348+ // For scaled numbers, prefer exact scaled type, then larger scaled types
2349+ if constexpr (variantContainsV<ScaledInt64, V>)
2350+ return V{get<std::optional<ScaledInt64>>(index).value ()};
2351+ #if FB_CPP_USE_BOOST_MULTIPRECISION != 0
2352+ if constexpr (variantContainsV<ScaledBoostInt128, V>)
2353+ return V{get<std::optional<ScaledBoostInt128>>(index).value ()};
2354+ #endif
2355+ }
2356+ if constexpr (variantContainsV<std::int64_t , V>)
2357+ return V{get<std::optional<std::int64_t >>(index).value ()};
2358+ break ;
2359+
2360+ #if FB_CPP_USE_BOOST_MULTIPRECISION != 0
2361+ case DescriptorAdjustedType::INT128 :
2362+ // Prefer opaque (native Firebird) types first
2363+ if constexpr (variantContainsV<ScaledOpaqueInt128, V>)
2364+ return V{get<std::optional<ScaledOpaqueInt128>>(index).value ()};
2365+ if (descriptor.scale != 0 )
2366+ {
2367+ if constexpr (variantContainsV<ScaledBoostInt128, V>)
2368+ return V{get<std::optional<ScaledBoostInt128>>(index).value ()};
2369+ }
2370+ if constexpr (variantContainsV<BoostInt128, V>)
2371+ return V{get<std::optional<BoostInt128>>(index).value ()};
2372+ break ;
2373+ #endif
2374+
2375+ case DescriptorAdjustedType::FLOAT :
2376+ if constexpr (variantContainsV<float , V>)
2377+ return V{get<std::optional<float >>(index).value ()};
2378+ break ;
2379+
2380+ case DescriptorAdjustedType::DOUBLE :
2381+ if constexpr (variantContainsV<double , V>)
2382+ return V{get<std::optional<double >>(index).value ()};
2383+ break ;
2384+
2385+ #if FB_CPP_USE_BOOST_MULTIPRECISION != 0
2386+ case DescriptorAdjustedType::DECFLOAT16 :
2387+ // Prefer opaque (native Firebird) types first
2388+ if constexpr (variantContainsV<OpaqueDecFloat16, V>)
2389+ return V{get<std::optional<OpaqueDecFloat16>>(index).value ()};
2390+ if constexpr (variantContainsV<BoostDecFloat16, V>)
2391+ return V{get<std::optional<BoostDecFloat16>>(index).value ()};
2392+ break ;
2393+
2394+ case DescriptorAdjustedType::DECFLOAT34 :
2395+ // Prefer opaque (native Firebird) types first
2396+ if constexpr (variantContainsV<OpaqueDecFloat34, V>)
2397+ return V{get<std::optional<OpaqueDecFloat34>>(index).value ()};
2398+ if constexpr (variantContainsV<BoostDecFloat34, V>)
2399+ return V{get<std::optional<BoostDecFloat34>>(index).value ()};
2400+ break ;
2401+ #endif
2402+
2403+ case DescriptorAdjustedType::STRING :
2404+ if constexpr (variantContainsV<std::string, V>)
2405+ return V{get<std::optional<std::string>>(index).value ()};
2406+ break ;
2407+
2408+ case DescriptorAdjustedType::DATE :
2409+ // Prefer opaque (native Firebird) types first
2410+ if constexpr (variantContainsV<OpaqueDate, V>)
2411+ return V{get<std::optional<OpaqueDate>>(index).value ()};
2412+ if constexpr (variantContainsV<Date, V>)
2413+ return V{get<std::optional<Date>>(index).value ()};
2414+ break ;
2415+
2416+ case DescriptorAdjustedType::TIME :
2417+ // Prefer opaque (native Firebird) types first
2418+ if constexpr (variantContainsV<OpaqueTime, V>)
2419+ return V{get<std::optional<OpaqueTime>>(index).value ()};
2420+ if constexpr (variantContainsV<Time, V>)
2421+ return V{get<std::optional<Time>>(index).value ()};
2422+ break ;
2423+
2424+ case DescriptorAdjustedType::TIMESTAMP :
2425+ // Prefer opaque (native Firebird) types first
2426+ if constexpr (variantContainsV<OpaqueTimestamp, V>)
2427+ return V{get<std::optional<OpaqueTimestamp>>(index).value ()};
2428+ if constexpr (variantContainsV<Timestamp, V>)
2429+ return V{get<std::optional<Timestamp>>(index).value ()};
2430+ break ;
2431+
2432+ case DescriptorAdjustedType::TIME_TZ :
2433+ // Prefer opaque (native Firebird) types first
2434+ if constexpr (variantContainsV<OpaqueTimeTz, V>)
2435+ return V{get<std::optional<OpaqueTimeTz>>(index).value ()};
2436+ if constexpr (variantContainsV<TimeTz, V>)
2437+ return V{get<std::optional<TimeTz>>(index).value ()};
2438+ break ;
2439+
2440+ case DescriptorAdjustedType::TIMESTAMP_TZ :
2441+ // Prefer opaque (native Firebird) types first
2442+ if constexpr (variantContainsV<OpaqueTimestampTz, V>)
2443+ return V{get<std::optional<OpaqueTimestampTz>>(index).value ()};
2444+ if constexpr (variantContainsV<TimestampTz, V>)
2445+ return V{get<std::optional<TimestampTz>>(index).value ()};
2446+ break ;
2447+
2448+ case DescriptorAdjustedType::BLOB :
2449+ if constexpr (variantContainsV<BlobId, V>)
2450+ return V{get<std::optional<BlobId>>(index).value ()};
2451+ break ;
2452+
2453+ default :
2454+ break ;
2455+ }
2456+
2457+ // No exact match found, try variant alternatives in declaration order
2458+ return tryVariantAlternatives<V, 0 >(index, descriptor);
2459+ }
2460+
2461+ // /
2462+ // / @brief Recursively tries variant alternatives for type conversion.
2463+ // /
2464+ template <typename V, std::size_t I = 0 >
2465+ V tryVariantAlternatives (unsigned index, [[maybe_unused]] const Descriptor& descriptor)
2466+ {
2467+ using namespace impl ::reflection;
2468+
2469+ if constexpr (I >= std::variant_size_v<V>)
2470+ {
2471+ throw FbCppException (
2472+ " Cannot convert SQL type to any variant alternative at index " + std::to_string (index));
2473+ }
2474+ else
2475+ {
2476+ using Alt = std::variant_alternative_t <I, V>;
2477+
2478+ if constexpr (std::is_same_v<Alt, std::monostate>)
2479+ {
2480+ // Skip monostate in non-null case
2481+ return tryVariantAlternatives<V, I + 1 >(index, descriptor);
2482+ }
2483+ else if constexpr (isOpaqueTypeV<Alt>)
2484+ {
2485+ // Skip opaque types - they only match exact SQL types, no conversions
2486+ return tryVariantAlternatives<V, I + 1 >(index, descriptor);
2487+ }
2488+ else
2489+ {
2490+ // Try this alternative - get<T> will throw if conversion fails
2491+ auto opt = get<std::optional<Alt>>(index);
2492+ return V{std::move (opt.value ())};
2493+ }
2494+ }
2495+ }
2496+
22232497 // /
22242498 // / @brief Converts and writes numeric parameter values following descriptor rules.
22252499 // /
0 commit comments