userver: userver/storages/mysql/impl/io/insert_binder.hpp Source File
Loading...
Searching...
No Matches
insert_binder.hpp
1#pragma once
2
3#include <array>
4
5#include <boost/pfr/core.hpp>
6
7#include <userver/utils/assert.hpp>
8#include <userver/utils/meta.hpp>
9
10#include <userver/storages/mysql/convert.hpp>
11#include <userver/storages/mysql/impl/io/binder_declarations.hpp>
12#include <userver/storages/mysql/impl/io/params_binder_base.hpp>
13
14USERVER_NAMESPACE_BEGIN
15
16namespace storages::mysql::impl::io {
17
18// tidy complains about this being specified not in the outer namespace scope
19template <typename Row, bool IsMapped>
20struct OwnedMappedRow final {};
21template <typename Row>
22struct OwnedMappedRow<Row, true> final {
23 Row value;
24};
25
26class InsertBinderBase : public ParamsBinderBase {
27 public:
28 explicit InsertBinderBase(std::size_t size);
29 ~InsertBinderBase();
30
31 InsertBinderBase(const InsertBinderBase& other) = delete;
32
33 protected:
34 void SetBindCallback(void* user_data,
35 char (*param_cb)(void*, void*, std::size_t));
36
37 void UpdateBinds(void* binds_array);
38};
39
40template <typename Container, typename MapTo = typename Container::value_type>
41class InsertBinder final : public InsertBinderBase {
42 public:
43 explicit InsertBinder(const Container& container)
44 : InsertBinderBase{kColumnsCount},
45 container_{container},
46 current_row_it_{container_.begin()} {
47 static_assert(kColumnsCount != 0, "Rows to insert have zero columns");
48 static_assert(meta::kIsSizable<Container>,
49 "Container should be sizeable for batch insertion");
50
51 UASSERT(!container_.empty());
52
53 BindColumns();
54
55 SetBindCallback(this, &BindsRowCallback);
56 }
57
58 std::size_t GetRowsCount() const final { return container_.size(); }
59
60 private:
61 using Row = MapTo;
62 static constexpr std::size_t kColumnsCount = boost::pfr::tuple_size_v<Row>;
63 static constexpr bool kIsMapped =
64 !std::is_same_v<typename Container::value_type, Row>;
65
66 static char BindsRowCallback(void* user_data, void* binds_array,
67 std::size_t row_number);
68
69 void BindColumns() {
70 UpdateCurrentRowPtr();
71 boost::pfr::for_each_field(
72 *current_row_ptr_,
73 [&binds = GetBinds()](const auto& field, std::size_t i) {
74 storages::mysql::impl::io::BindInput(binds, i, field);
75 });
76 }
77
78 void UpdateCurrentRowPtr() {
79 if constexpr (kIsMapped) {
80 current_row_.value =
81 storages::mysql::convert::DoConvert<Row>(*current_row_it_);
82 current_row_ptr_ = &current_row_.value;
83 } else {
84 current_row_ptr_ = &*current_row_it_;
85 }
86 }
87
88 void UpdateCurrentRow(std::size_t row_number) {
89 UASSERT(CheckRowNumber(row_number));
90 UASSERT(current_row_it_ != container_.end());
91
92 if (row_number == 0) {
93 return; // everything already done at construction
94 }
95
96 BindColumns();
97 }
98
99 bool CheckRowNumber(std::size_t row_number) {
100 if (row_number < max_row_number_seen_ ||
101 row_number > max_row_number_seen_ + 1) {
102 return false;
103 }
104
105 max_row_number_seen_ = std::max(row_number, max_row_number_seen_);
106 return true;
107 }
108
109 const Container& container_;
110
111 typename Container::const_iterator current_row_it_;
112
113 OwnedMappedRow<Row, kIsMapped> current_row_;
114 const Row* current_row_ptr_;
115
116 std::size_t max_row_number_seen_{0};
117};
118
119template <typename Container, typename MapTo>
120char InsertBinder<Container, MapTo>::BindsRowCallback(void* user_data,
121 void* binds_array,
122 std::size_t row_number) {
123 auto* self = static_cast<InsertBinder*>(user_data);
124 UASSERT(self);
125
126 self->UpdateBinds(binds_array);
127 self->UpdateCurrentRow(row_number);
128
129 ++self->current_row_it_;
130
131 return 0;
132}
133
134} // namespace storages::mysql::impl::io
135
136USERVER_NAMESPACE_END