6#include <ydb-cpp-sdk/client/result/result.h>
7#include <ydb-cpp-sdk/client/value/value.h>
16#include <fmt/ranges.h>
17#include <boost/pfr/core.hpp>
18#include <boost/pfr/core_name.hpp>
20#include <userver/utils/assert.hpp>
21#include <userver/utils/constexpr_indices.hpp>
22#include <userver/utils/enumerate.hpp>
23#include <userver/utils/forward_like.hpp>
24#include <userver/utils/trivial_map.hpp>
26#include <userver/ydb/exceptions.hpp>
27#include <userver/ydb/impl/cast.hpp>
28#include <userver/ydb/io/generic_optional.hpp>
29#include <userver/ydb/io/traits.hpp>
31USERVER_NAMESPACE_BEGIN
37struct NotStruct
final {};
40constexpr decltype(T::kYdbMemberNames) DetectStructMemberNames()
noexcept {
41 return T::kYdbMemberNames;
44template <
typename T,
typename... Args>
45constexpr NotStruct DetectStructMemberNames(Args&&...)
noexcept {
51class EmptyRange
final {
53 constexpr T* begin()
const noexcept {
return nullptr; }
54 constexpr T* end()
const noexcept {
return nullptr; }
60struct CustomMemberName
final {
61 std::string_view cpp_name;
62 std::string_view ydb_name;
68template <std::size_t N>
69struct StructMemberNames
final {
70 CustomMemberName custom_names[N];
75struct StructMemberNames<0>
final {
76 impl::EmptyRange<CustomMemberName> custom_names;
81StructMemberNames() -> StructMemberNames<0>;
84template <std::size_t N>
85StructMemberNames(CustomMemberName (&&)[N]) -> StructMemberNames<N>;
113template <
typename T, std::size_t N>
114constexpr auto WithDeducedNames(
const StructMemberNames<N>& given_names) {
115 auto names = boost::pfr::names_as_array<T>();
116 for (
const CustomMemberName& entry : given_names.custom_names) {
117 std::size_t count = 0;
118 for (
auto& name : names) {
119 if (name == entry.cpp_name) {
120 name = entry.ydb_name;
127 "In a StructMemberNames, each cpp_name must match the C++ name of "
128 "exactly 1 member of struct T"
135template <
typename T, std::size_t... Indices>
136constexpr auto MakeTupleOfOptionals(std::index_sequence<Indices...>) {
137 return std::tuple<std::optional<boost::pfr::tuple_element_t<Indices, T>>...>();
140template <
typename T,
typename Tuple, std::size_t... Indices>
141constexpr auto MakeFromTupleOfOptionals(Tuple&& tuple, std::index_sequence<Indices...>) {
142 return T{*
utils::ForwardLike<Tuple>(std::get<Indices>(tuple))...};
148struct ValueTraits<T, std::enable_if_t<!std::is_same_v<
decltype(kStructMemberNames<T>),
const impl::NotStruct>>> {
149 static_assert(std::is_aggregate_v<T>);
150 static constexpr auto kFieldNames = impl::WithDeducedNames<T>(kStructMemberNames<T>);
151 static constexpr auto kFieldNamesSet =
utils::MakeTrivialSet<kFieldNames>();
152 static constexpr auto kFieldsCount = kFieldNames.size();
154 static T Parse(NYdb::TValueParser& parser,
const ParseContext& context) {
155 auto parsed_fields = impl::MakeTupleOfOptionals<T>(std::make_index_sequence<kFieldsCount>{});
158 while (parser.TryNextMember()) {
159 const auto& field_name = parser.GetMemberName();
160 const auto index = kFieldNamesSet.GetIndex(field_name);
161 if (!index.has_value()) {
165 "Unexpected field name '{}' for '{}' struct type, "
166 "expected one of: {}",
169 fmt::join(kFieldNames,
", ")
173 utils::WithConstexprIndex<kFieldsCount>(*index, [&](
auto index_c) {
174 auto& field = std::get<
decltype(index_c)::value>(parsed_fields);
175 using FieldType =
typename std::decay_t<
decltype(field)>::value_type;
176 field.emplace(ydb::Parse<FieldType>(parser, context));
180 parser.CloseStruct();
182 std::string_view missing_field;
183 utils::ForEachIndex<kFieldsCount>([&](
auto index_c) {
184 if (!std::get<
decltype(index_c)::value>(parsed_fields).has_value()) {
185 missing_field = kFieldNames[index_c];
188 if (!missing_field.empty()) {
191 fmt::format(
"Missing field '{}' for '{}' struct type", missing_field,
compiler::GetTypeName<T>())
195 return impl::MakeFromTupleOfOptionals<T>(std::move(parsed_fields), std::make_index_sequence<kFieldsCount>{});
198 template <
typename Builder>
199 static void Write(NYdb::TValueBuilderBase<Builder>& builder,
const T& value) {
200 builder.BeginStruct();
201 boost::pfr::for_each_field(value, [&](
const auto& field, std::size_t i) {
202 builder.AddMember(impl::ToString(kFieldNames[i]));
208 static NYdb::TType MakeType() {
209 NYdb::TTypeBuilder builder;
210 builder.BeginStruct();
211 utils::ForEachIndex<kFieldsCount>([&](
auto index_c) {
213 impl::ToString(kFieldNames[index_c]),
214 ValueTraits<boost::pfr::tuple_element_t<
decltype(index_c)::value, T>>::MakeType()
218 return builder.Build();
225 std::enable_if_t<!std::is_same_v<
decltype(kStructMemberNames<T>),
const impl::NotStruct>>>
226 : impl::GenericOptionalValueTraits<T> {};
231struct StructRowParser
final {
232 static_assert(std::is_aggregate_v<T>);
233 static constexpr auto kFieldNames = impl::WithDeducedNames<T>(kStructMemberNames<T>);
234 static constexpr auto kFieldsCount = kFieldNames.size();
236 static std::unique_ptr<std::size_t[]> MakeCppToYdbFieldMapping(NYdb::TResultSetParser& parser) {
237 auto result = std::make_unique<std::size_t[]>(kFieldsCount);
238 for (
const auto [pos, field_name] :
utils::enumerate(kFieldNames)) {
239 const auto column_index = parser.ColumnIndex(impl::ToString(field_name));
240 if (column_index == -1) {
242 fmt::format(
"Missing column '{}' for '{}' struct type", field_name,
compiler::GetTypeName<T>())
245 result[pos] =
static_cast<std::size_t>(column_index);
248 const auto columns_count = parser.ColumnsCount();
249 UASSERT(columns_count >= kFieldsCount);
250 if (columns_count != kFieldsCount) {
252 "Unexpected extra columns while parsing row to '{}' struct type",
260 static T ParseRow(NYdb::TResultSetParser& parser,
const std::unique_ptr<std::size_t[]>& cpp_to_ydb_mapping) {
261 return ParseRowImpl(parser, cpp_to_ydb_mapping, std::make_index_sequence<kFieldsCount>{});
264 template <std::size_t... Indices>
265 static T ParseRowImpl(NYdb::TResultSetParser& parser,
const std::unique_ptr<std::size_t[]>& cpp_to_ydb_mapping, std::index_sequence<Indices...>) {
267 Parse<boost::pfr::tuple_element_t<Indices, T>>(
268 parser.ColumnParser(cpp_to_ydb_mapping[Indices]),
269 ParseContext{.column_name = kFieldNames[Indices]}