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]})...,