userver: userver/storages/mysql/impl/io/insert_binder.hpp Source File
⚠️ This is the documentation for an old userver version. Click here to switch to the latest version.
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
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