3#include <ydb-cpp-sdk/client/result/result.h>
4#include <ydb-cpp-sdk/client/value/value.h>
13#include <boost/pfr/core.hpp>
14#include <boost/pfr/core_name.hpp>
16#include <userver/utils/assert.hpp>
17#include <userver/utils/constexpr_indices.hpp>
18#include <userver/utils/enumerate.hpp>
19#include <userver/utils/trivial_map.hpp>
21#include <userver/ydb/exceptions.hpp>
22#include <userver/ydb/impl/cast.hpp>
23#include <userver/ydb/io/traits.hpp>
25USERVER_NAMESPACE_BEGIN
31struct NotStruct
final {};
34constexpr decltype(T::kYdbMemberNames) DetectStructMemberNames()
noexcept {
35 return T::kYdbMemberNames;
38template <
typename T,
typename... Args>
39constexpr NotStruct DetectStructMemberNames(Args&&...)
noexcept {
46struct CustomMemberName
final {
47 std::string_view cpp_name;
48 std::string_view ydb_name;
54template <std::size_t N>
55struct StructMemberNames
final {
56 CustomMemberName custom_names[N];
60StructMemberNames() -> StructMemberNames<0>;
63template <std::size_t N>
64StructMemberNames(CustomMemberName (&&)[N]) -> StructMemberNames<N>;
88constexpr auto kStructMemberNames = impl::DetectStructMemberNames<T>();
92template <
typename T, std::size_t N>
93constexpr auto WithDeducedNames(
const StructMemberNames<N>& given_names) {
94 auto names = boost::pfr::names_as_array<T>();
95 for (
const CustomMemberName& entry : given_names.custom_names) {
96 std::size_t count = 0;
97 for (
auto& name : names) {
98 if (name == entry.cpp_name) {
99 name = entry.ydb_name;
106 "In a StructMemberNames, each cpp_name must match the C++ name of "
107 "exactly 1 member of struct T");
113template <
typename T, std::size_t... Indices>
114constexpr auto MakeTupleOfOptionals(std::index_sequence<Indices...>) {
116 std::optional<boost::pfr::tuple_element_t<Indices, T>>...>();
119template <
typename T,
typename Tuple, std::size_t... Indices>
120constexpr auto MakeFromTupleOfOptionals(Tuple&& tuple,
121 std::index_sequence<Indices...>) {
122 return T{*std::get<Indices>(std::forward<Tuple>(tuple))...};
129 T, std::enable_if_t<!std::is_same_v<
decltype(kStructMemberNames<T>),
130 const impl::NotStruct>>> {
131 static_assert(std::is_aggregate_v<T>);
132 static constexpr auto kFieldNames =
133 impl::WithDeducedNames<T>(kStructMemberNames<T>);
134 static constexpr auto kFieldNamesSet = utils::MakeTrivialSet<kFieldNames>();
135 static constexpr auto kFieldsCount = kFieldNames.size();
137 static T Parse(NYdb::TValueParser& parser,
const ParseContext& context) {
139 impl::MakeTupleOfOptionals<T>(std::make_index_sequence<kFieldsCount>{});
142 while (parser.TryNextMember()) {
143 const auto& field_name = parser.GetMemberName();
144 const auto index = kFieldNamesSet.GetIndex(field_name);
145 if (!index.has_value()) {
146 throw ColumnParseError(
148 fmt::format(
"Unexpected field name '{}' for '{}' struct type, "
149 "expected one of: {}",
150 field_name, compiler::GetTypeName<T>(),
151 fmt::join(kFieldNames,
", ")));
153 utils::WithConstexprIndex<kFieldsCount>(*index, [&](
auto index_c) {
154 auto& field = std::get<
decltype(index_c)::value>(parsed_fields);
155 using FieldType =
typename std::decay_t<
decltype(field)>::value_type;
156 field.emplace(ydb::Parse<FieldType>(parser, context));
160 parser.CloseStruct();
162 std::string_view missing_field;
163 utils::ForEachIndex<kFieldsCount>([&](
auto index_c) {
164 if (!std::get<
decltype(index_c)::value>(parsed_fields).has_value()) {
165 missing_field = kFieldNames[index_c];
168 if (!missing_field.empty()) {
169 throw ColumnParseError(
171 fmt::format(
"Missing field '{}' for '{}' struct type", missing_field,
172 compiler::GetTypeName<T>()));
175 return impl::MakeFromTupleOfOptionals<T>(
176 std::move(parsed_fields), std::make_index_sequence<kFieldsCount>{});
179 template <
typename Builder>
180 static void Write(NYdb::TValueBuilderBase<Builder>& builder,
const T& value) {
181 builder.BeginStruct();
182 boost::pfr::for_each_field(value, [&](
const auto& field, std::size_t i) {
183 builder.AddMember(impl::ToString(kFieldNames[i]));
184 ydb::Write(builder, field);
189 static NYdb::TType MakeType() {
190 NYdb::TTypeBuilder builder;
191 builder.BeginStruct();
192 utils::ForEachIndex<kFieldsCount>([&](
auto index_c) {
194 impl::ToString(kFieldNames[index_c]),
195 ValueTraits<boost::pfr::tuple_element_t<
decltype(index_c)::value,
199 return builder.Build();
206struct StructRowParser
final {
207 static_assert(std::is_aggregate_v<T>);
208 static constexpr auto kFieldNames =
209 impl::WithDeducedNames<T>(kStructMemberNames<T>);
210 static constexpr auto kFieldsCount = kFieldNames.size();
212 static std::unique_ptr<std::size_t[]> MakeCppToYdbFieldMapping(
213 NYdb::TResultSetParser& parser) {
214 auto result = std::make_unique<std::size_t[]>(kFieldsCount);
215 for (
const auto [pos, field_name] : utils::enumerate(kFieldNames)) {
216 const auto column_index = parser.ColumnIndex(impl::ToString(field_name));
217 if (column_index == -1) {
218 throw ParseError(fmt::format(
"Missing column '{}' for '{}' struct type",
219 field_name, compiler::GetTypeName<T>()));
221 result[pos] =
static_cast<std::size_t>(column_index);
224 const auto columns_count = parser.ColumnsCount();
225 UASSERT(columns_count >= kFieldsCount);
226 if (columns_count != kFieldsCount) {
227 throw ParseError(fmt::format(
228 "Unexpected extra columns while parsing row to '{}' struct type",
229 compiler::GetTypeName<T>()));
235 static T ParseRow(NYdb::TResultSetParser& parser,
236 const std::unique_ptr<std::size_t[]>& cpp_to_ydb_mapping) {
237 return ParseRowImpl(parser, cpp_to_ydb_mapping,
238 std::make_index_sequence<kFieldsCount>{});
241 template <std::size_t... Indices>
242 static T ParseRowImpl(
243 NYdb::TResultSetParser& parser,
244 const std::unique_ptr<std::size_t[]>& cpp_to_ydb_mapping,
245 std::index_sequence<Indices...>) {
247 Parse<boost::pfr::tuple_element_t<Indices, T>>(
248 parser.ColumnParser(cpp_to_ydb_mapping[Indices]),
249 ParseContext{kFieldNames[Indices]})...,