Cppgres
Build Postgres extensions in C++
Loading...
Searching...
No Matches
executor.hpp
Go to the documentation of this file.
1
4#pragma once
5
6#include "datum.hpp"
7#include "function.hpp"
8#include "guard.hpp"
9#include "memory.hpp"
10#include "types.hpp"
11
12#include <iterator>
13#include <optional>
14#include <stack>
15#include <vector>
16
17namespace cppgres {
18
19template <typename Tuple, std::size_t... Is>
20constexpr bool all_convertible_from_nullable(std::index_sequence<Is...>) {
21 return ((convertible_from_nullable_datum<
22 utils::remove_optional_t<utils::tuple_element_t<Is, Tuple>>>) &&
23 ...);
24}
25
26template <typename T>
27concept datumable_tuple = requires {
28 typename utils::tuple_size<T>::type;
29} && all_convertible_from_nullable<T>(std::make_index_sequence<utils::tuple_size_v<T>>{});
30
31template <typename T>
34
35template <convertible_from_nullable_datum... Args> struct spi_plan {
36 friend struct spi_executor;
37
38 spi_plan(spi_plan &&p) : kept(p.kept), plan(p.plan), ctx(std::move(p.ctx)) { p.kept = false; }
39
40 operator ::SPIPlanPtr() {
41 if (ctx.resets() > 0) {
43 }
44 return plan;
45 }
46
47 void keep() {
48 ffi_guarded(::SPI_keepplan)(*this);
49 kept = true;
50 }
51
52 ~spi_plan() {
53 if (kept) {
54 ffi_guarded(::SPI_freeplan)(*this);
55 }
56 }
57
58private:
59 spi_plan(::SPIPlanPtr plan)
60 : kept(false), plan(plan), ctx(tracking_memory_context(memory_context::for_pointer(plan))) {}
61
62 bool kept;
63 ::SPIPlanPtr plan;
65};
66
67struct executor {};
68
72struct spi_executor : public executor {
78 ffi_guarded(::SPI_finish)();
79 executors.pop();
80 }
81
82 template <datumable_tuple T> struct result_iterator {
83 using iterator_category = std::random_access_iterator_tag;
84 using value_type = T;
85 using difference_type = std::ptrdiff_t;
86
87 ::SPITupleTable *tuptable;
88 size_t index;
89 mutable std::vector<std::optional<T>> tuples;
90
91 constexpr result_iterator() noexcept {}
92
93 constexpr result_iterator(::SPITupleTable *tuptable) noexcept
94 : tuptable(tuptable), index(0),
95 tuples(std::vector<std::optional<T>>(tuptable->numvals, std::nullopt)) {
96 tuples.reserve(tuptable->numvals);
97 }
98 constexpr result_iterator(::SPITupleTable *tuptable, size_t n) noexcept
99 : tuptable(tuptable), index(n),
100 tuples(std::vector<std::optional<T>>(tuptable->numvals, std::nullopt)) {
101 tuples.reserve(tuptable->numvals);
102 }
103
104 bool operator==(size_t end_index) const { return index == end_index; }
105 bool operator!=(size_t end_index) const { return index != end_index; }
106
107 constexpr T &operator*() const { return this->operator[](static_cast<difference_type>(index)); }
108
109 constexpr result_iterator &operator++() noexcept {
110 index++;
111 return *this;
112 }
113 constexpr result_iterator operator++(int) noexcept {
114 index++;
115 return this;
116 }
117
118 constexpr result_iterator &operator--() noexcept {
119 index++;
120 return this;
121 }
122 constexpr result_iterator operator--(int) noexcept {
123 index--;
124 return this;
125 }
126
127 constexpr result_iterator operator+(const difference_type n) const noexcept {
128 return result_iterator(tuptable, index + n);
129 }
130
131 result_iterator &operator+=(difference_type n) noexcept {
132 index += n;
133 return this;
134 }
135
136 constexpr result_iterator operator-(difference_type n) const noexcept {
137 return result_iterator(tuptable, index - n);
138 }
139
140 result_iterator &operator-=(difference_type n) noexcept {
141 index -= n;
142 return this;
143 }
144
145 constexpr difference_type operator-(const result_iterator &other) const noexcept {
146 return index - other.index;
147 }
148
149 T &operator[](difference_type n) const {
150 if (tuples.at(n).has_value()) {
151 return tuples.at(n).value();
152 }
153 if constexpr (convertible_from_datum<T>) {
154 if (tuptable->tupdesc->natts == 1) {
155 // if a special case of a directly convertible type
156 bool isnull;
157 ::Datum value =
158 ffi_guarded(::SPI_getbinval)(tuptable->vals[n], tuptable->tupdesc, 1, &isnull);
159 ::NullableDatum datum = {.value = value, .isnull = isnull};
160 auto ret =
161 from_nullable_datum<T>(nullable_datum(datum), memory_context(tuptable->tuptabcxt));
162 tuples.emplace(std::next(tuples.begin(), n), std::in_place, ret);
163 return tuples.at(n).value();
164 }
165 }
166 auto ret = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
167 return T{([&] {
168 bool isnull;
169 ::Datum value =
170 ffi_guarded(::SPI_getbinval)(tuptable->vals[n], tuptable->tupdesc, Is + 1, &isnull);
171 ::NullableDatum datum = {.value = value, .isnull = isnull};
172 auto nd = nullable_datum(datum);
173 return from_nullable_datum<utils::tuple_element_t<Is, T>>(
174 nd, memory_context(tuptable->tuptabcxt));
175 }())...};
176 }(std::make_index_sequence<utils::tuple_size_v<T>>{});
177 tuples.emplace(std::next(tuples.begin(), n), std::in_place, ret);
178 return tuples.at(n).value();
179 }
180
181 constexpr bool operator==(const result_iterator &other) const noexcept {
182 return tuptable == other.tuptable && index == other.index;
183 }
184 constexpr bool operator!=(const result_iterator &other) const noexcept {
185 return !(tuptable == other.tuptable && index == other.index);
186 }
187 constexpr bool operator<(const result_iterator &other) const noexcept {
188 return index < other.index;
189 }
190 constexpr bool operator>(const result_iterator &other) const noexcept {
191 return index > other.index;
192 }
193 constexpr bool operator<=(const result_iterator &other) const noexcept {
194 return index <= other.index;
195 }
196 constexpr bool operator>=(const result_iterator &other) const noexcept {
197 return index >= other.index;
198 }
199
200 private:
201 };
202
204 ::SPITupleTable *table;
205
206 results(::SPITupleTable *table) : table(table) {
207 auto natts = table->tupdesc->natts;
208 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
209 (([&] {
210 auto oid = ffi_guarded(::SPI_gettypeid)(table->tupdesc, Is + 1);
211 auto t = type{.oid = oid};
212 if (!type_traits<utils::tuple_element_t<Is, Ret>>::is(t)) {
213 throw std::invalid_argument(
214 std::format("invalid return type in position {} ({}), got OID {}", Is,
215 utils::type_name<utils::tuple_element_t<Is, Ret>>(), oid));
216 }
217 }()),
218 ...);
219 }(std::make_index_sequence<sizeof...(Args)>{});
220 if (natts != utils::tuple_size_v<Ret>) {
221 if (natts == 1 && convertible_from_datum<Ret>) {
222 // okay, this is just a type we can convert
223 } else {
224 throw std::runtime_error(
225 std::format("expected {} return values, got {}", utils::tuple_size_v<Ret>, natts));
226 }
227 }
228 }
229
230 result_iterator<Ret> begin() const { return result_iterator<Ret>(table); }
231 size_t end() const { return table->numvals; }
232 };
233
243 results<Ret, Args...> query(std::string_view query, Args &&...args) {
244 if (executors.top() != this) {
245 throw std::runtime_error("not a current SPI executor");
246 }
247 constexpr size_t nargs = sizeof...(Args);
248 std::array<::Oid, nargs> types = {type_traits<Args>::type_for().oid...};
249 std::array<::Datum, nargs> datums = {into_nullable_datum(args)...};
250 std::array<const char, nargs> nulls = {into_nullable_datum(args).is_null() ? 'n' : ' ' ...};
251 auto rc = ffi_guarded(::SPI_execute_with_args)(query.data(), nargs, types.data(), datums.data(),
252 nulls.data(), false, 0);
253 if (rc > 0) {
254 // static_assert(std::random_access_iterator<result_iterator<Ret>>);
255 return results<Ret, Args...>(SPI_tuptable);
256 } else {
257 throw std::runtime_error("spi error");
258 }
259 }
260
262 spi_plan<Args...> plan(std::string_view query) {
263 if (executors.top() != this) {
264 throw std::runtime_error("not a current SPI executor");
265 }
266 constexpr size_t nargs = sizeof...(Args);
267 std::array<::Oid, nargs> types = {type_traits<Args>::type_for().oid...};
268 return spi_plan<Args...>(ffi_guarded(::SPI_prepare)(query.data(), nargs, types.data()));
269 }
270
271 template <datumable_tuple Ret, convertible_into_nullable_datum... Args>
272 results<Ret, Args...> query(spi_plan<Args...> &query, Args &&...args) {
273 if (executors.top() != this) {
274 throw std::runtime_error("not a current SPI executor");
275 }
276 constexpr size_t nargs = sizeof...(Args);
277 std::array<::Datum, nargs> datums = {into_nullable_datum(args)...};
278 std::array<const char, nargs> nulls = {into_nullable_datum(args).is_null() ? 'n' : ' ' ...};
279 auto rc = ffi_guarded(::SPI_execute_plan)(query, datums.data(), nulls.data(), false, 0);
280 if (rc > 0) {
281 // static_assert(std::random_access_iterator<result_iterator<Ret>>);
282 return results<Ret, Args...>(SPI_tuptable);
283 } else {
284 throw std::runtime_error("spi error");
285 }
286 }
287
288 template <convertible_into_nullable_datum_and_has_a_type... Args>
289 uint64_t execute(std::string_view query, Args &&...args) {
290 if (executors.top() != this) {
291 throw std::runtime_error("not a current SPI executor");
292 }
293 constexpr size_t nargs = sizeof...(Args);
294 std::array<::Oid, nargs> types = {type_traits<Args>::type_for().oid...};
295 std::array<::Datum, nargs> datums = {into_nullable_datum(args)...};
296 std::array<const char, nargs> nulls = {into_nullable_datum(args).is_null() ? 'n' : ' ' ...};
297 auto rc = ffi_guarded(::SPI_execute_with_args)(query.data(), nargs, types.data(), datums.data(),
298 nulls.data(), false, 0);
299 if (rc >= 0) {
300 return SPI_processed;
301 } else {
302 throw std::runtime_error(std::format("spi error"));
303 }
304 }
305
306private:
307 ::MemoryContext before_spi;
308 ::MemoryContext spi;
309
310protected:
311 static inline std::stack<spi_executor *> executors;
312 spi_executor(int flags) : before_spi(::CurrentMemoryContext) {
313 ffi_guarded(::SPI_connect_ext)(flags);
314 spi = ::CurrentMemoryContext;
315 ::CurrentMemoryContext = before_spi;
316 executors.push(this);
317 }
318};
319
322
323 spi_nonatomic_executor() : spi_executor(SPI_OPT_NONATOMIC) {
324 auto atomic = cppgres::current_postgres_function::atomic();
325 if (atomic.has_value() && atomic.value()) {
326 throw std::runtime_error("must be called in a non-atomic context");
327 }
328 }
329
330 void commit(bool chain = false) {
331 if (executors.top() != this) {
332 throw std::runtime_error("not a current SPI executor");
333 }
334 ffi_guarded(chain ? ::SPI_commit_and_chain : ::SPI_commit)();
335 }
336
337 void rollback(bool chain = false) {
338 if (executors.top() != this) {
339 throw std::runtime_error("not a current SPI executor");
340 }
341 ffi_guarded(chain ? ::SPI_rollback_and_chain : ::SPI_rollback)();
342 }
343};
344
345} // namespace cppgres
Definition: datum.hpp:109
Definition: executor.hpp:27
Definition: type.hpp:236
Definition: datum.hpp:19
Definition: executor.hpp:67
Definition: memory.hpp:61
Definition: datum.hpp:38
Definition: memory.hpp:239
Definition: executor.hpp:82
Definition: executor.hpp:203
SPI executor API
Definition: executor.hpp:72
spi_executor()
Creates an SPI executor.
Definition: executor.hpp:76
results< Ret, Args... > query(std::string_view query, Args &&...args)
Queries using a string view.
Definition: executor.hpp:243
Definition: executor.hpp:320
spi_executor()
Creates an SPI executor.
Definition: executor.hpp:76
Definition: executor.hpp:35
Definition: memory.hpp:107
Definition: type.hpp:41
Postgres type.
Definition: type.hpp:20