Skip to content

Commit 6015a7e

Browse files
committed
Feature #9 - Reading fields or setting parameters with variant
1 parent 7fc7363 commit 6015a7e

4 files changed

Lines changed: 1063 additions & 2 deletions

File tree

src/fb-cpp/Statement.h

Lines changed: 276 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
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
///

src/fb-cpp/StructBinding.h

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <tuple>
3131
#include <type_traits>
3232
#include <utility>
33+
#include <variant>
3334

3435

3536
namespace fbcpp
@@ -66,6 +67,50 @@ namespace fbcpp::impl::reflection
6667
template <typename T>
6768
inline constexpr bool isOptionalV = IsOptional<T>::value;
6869

70+
///
71+
/// Helper to detect std::variant.
72+
///
73+
template <typename T>
74+
struct IsVariant : std::false_type
75+
{
76+
};
77+
78+
template <typename... Ts>
79+
struct IsVariant<std::variant<Ts...>> : std::true_type
80+
{
81+
};
82+
83+
template <typename T>
84+
inline constexpr bool isVariantV = IsVariant<T>::value;
85+
86+
///
87+
/// Check if variant contains a specific type.
88+
///
89+
template <typename T, typename Variant>
90+
struct VariantContains : std::false_type
91+
{
92+
};
93+
94+
template <typename T, typename... Ts>
95+
struct VariantContains<T, std::variant<Ts...>> : std::disjunction<std::is_same<T, Ts>...>
96+
{
97+
};
98+
99+
template <typename T, typename Variant>
100+
inline constexpr bool variantContainsV = VariantContains<T, Variant>::value;
101+
102+
///
103+
/// Helper to detect opaque (non-convertible) types that only match exact SQL types.
104+
/// Forward declarations for opaque types from types.h.
105+
///
106+
template <typename T>
107+
struct IsOpaqueType : std::false_type
108+
{
109+
};
110+
111+
template <typename T>
112+
inline constexpr bool isOpaqueTypeV = IsOpaqueType<T>::value;
113+
69114
/// Universal converter for aggregate initialization detection.
70115
struct UniversalType
71116
{
@@ -334,5 +379,14 @@ namespace fbcpp::impl::reflection
334379
using FieldType = std::remove_reference_t<std::tuple_element_t<I, TupleType<T>>>;
335380
} // namespace fbcpp::impl::reflection
336381

382+
namespace fbcpp
383+
{
384+
///
385+
/// Concept constraining types to std::variant types.
386+
///
387+
template <typename T>
388+
concept VariantLike = impl::reflection::isVariantV<std::remove_cvref_t<T>>;
389+
} // namespace fbcpp
390+
337391

338392
#endif // FBCPP_STRUCT_BINDING_H

0 commit comments

Comments
 (0)