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#include "utils/cstring.hpp"
12
13#include <iterator>
14#include <optional>
15#include <stack>
16#include <vector>
17
18namespace cppgres {
19
20template <typename T>
23
24template <convertible_from_nullable_datum... Args> struct spi_plan {
25 friend struct spi_executor;
26
27 spi_plan(spi_plan &&p) : kept(p.kept), plan(p.plan), ctx(std::move(p.ctx)) { p.kept = false; }
28
29 operator ::SPIPlanPtr() {
30 if (ctx.resets() > 0) {
32 }
33 return plan;
34 }
35
36 void keep() {
37 ffi_guard{::SPI_keepplan}(*this);
38 kept = true;
39 }
40
48 ::SPIPlanPtr release() noexcept {
49 kept = false;
50 return plan;
51 }
52
53 ~spi_plan() {
54 if (kept) {
55 ffi_guard{::SPI_freeplan}(*this);
56 }
57 }
58
59private:
60 spi_plan(::SPIPlanPtr plan)
61 : kept(false), plan(plan), ctx(tracking_memory_context(memory_context::for_pointer(plan))) {}
62
63 bool kept;
64 ::SPIPlanPtr plan;
65 tracking_memory_context<memory_context> ctx;
66};
67
68struct executor {};
69
70template <typename T>
71concept a_vector = requires {
72 typename T::value_type;
73 typename T::allocator_type;
74} && std::same_as<T, std::vector<typename T::value_type, typename T::allocator_type>>;
75
79enum class spi_opt : int { none = 0, nonatomic = SPI_OPT_NONATOMIC };
80
81constexpr spi_opt operator|(spi_opt lhs, spi_opt rhs) {
82 return static_cast<spi_opt>(static_cast<int>(lhs) | static_cast<int>(rhs));
83}
84
88struct spi_executor : public executor {
93
102 explicit spi_executor(spi_opt opts) : spi_executor(static_cast<int>(opts)) {}
103 ~spi_executor() {
104 ffi_guard{::SPI_finish}();
105 executors.pop();
106 }
107
108 template <typename T> struct result_iterator {
109 using iterator_category = std::random_access_iterator_tag;
110 using value_type = T;
111 using difference_type = std::ptrdiff_t;
112
113 ::SPITupleTable *tuptable;
114 size_t index;
115 mutable std::vector<std::optional<T>> tuples;
116
117 constexpr result_iterator() noexcept {}
118
119 constexpr result_iterator(::SPITupleTable *tuptable) noexcept
120 : tuptable(tuptable), index(0),
121 tuples(std::vector<std::optional<T>>(tuptable->numvals, std::nullopt)) {
122 tuples.reserve(tuptable->numvals);
123 }
124 constexpr result_iterator(::SPITupleTable *tuptable, size_t n) noexcept
125 : tuptable(tuptable), index(n),
126 tuples(std::vector<std::optional<T>>(tuptable->numvals, std::nullopt)) {
127 tuples.reserve(tuptable->numvals);
128 }
129
130 bool operator==(size_t end_index) const { return index == end_index; }
131 bool operator!=(size_t end_index) const { return index != end_index; }
132
133 constexpr T &operator*() const { return this->operator[](static_cast<difference_type>(index)); }
134
135 constexpr result_iterator &operator++() noexcept {
136 index++;
137 return *this;
138 }
139 constexpr result_iterator operator++(int) noexcept {
140 auto ret = *this;
141 index++;
142 return ret;
143 }
144
145 constexpr result_iterator &operator--() noexcept {
146 index--;
147 return *this;
148 }
149 constexpr result_iterator operator--(int) noexcept {
150 auto ret = *this;
151 index--;
152 return ret;
153 }
154
155 constexpr result_iterator operator+(const difference_type n) const noexcept {
156 return result_iterator(tuptable, index + n);
157 }
158
159 result_iterator &operator+=(difference_type n) noexcept {
160 index += n;
161 return *this;
162 }
163
164 constexpr result_iterator operator-(difference_type n) const noexcept {
165 return result_iterator(tuptable, index - n);
166 }
167
168 result_iterator &operator-=(difference_type n) noexcept {
169 index -= n;
170 return *this;
171 }
172
173 constexpr difference_type operator-(const result_iterator &other) const noexcept {
174 return index - other.index;
175 }
176
177 T &operator[](difference_type n) const {
178 if (tuples.at(n).has_value()) {
179 return tuples.at(n).value();
180 }
181 if constexpr (convertible_from_datum<T>) {
182 // if a special case of a directly convertible type
183 if constexpr (composite_type<T>) {
184 bool isnull;
185 ::Datum value =
186 ffi_guard{::SPI_getbinval}(tuptable->vals[n], tuptable->tupdesc, 1, &isnull);
187 ::NullableDatum datum = {.value = value, .isnull = isnull};
188 auto ret = from_nullable_datum<T>(nullable_datum(datum),
189 ffi_guard{::SPI_gettypeid}(tuptable->tupdesc, 1),
190 memory_context(tuptable->tuptabcxt));
191 tuples.at(n).emplace(ret);
192 return tuples.at(n).value();
193 } else if (tuptable->tupdesc->natts == 1) {
194 bool isnull;
195 ::Datum value =
196 ffi_guard{::SPI_getbinval}(tuptable->vals[n], tuptable->tupdesc, 1, &isnull);
197 ::NullableDatum datum = {.value = value, .isnull = isnull};
198 auto ret = from_nullable_datum<T>(nullable_datum(datum),
199 ffi_guard{::SPI_gettypeid}(tuptable->tupdesc, 1),
200 memory_context(tuptable->tuptabcxt));
201 tuples.at(n).emplace(ret);
202 return tuples.at(n).value();
203 }
204 }
205 if constexpr (a_vector<T>) {
206 T ret;
207 for (int i = 0; i < tuptable->tupdesc->natts; i++) {
208 bool isnull;
209 ::Datum value =
210 ffi_guard{::SPI_getbinval}(tuptable->vals[n], tuptable->tupdesc, i + 1, &isnull);
211 ::NullableDatum datum = {.value = value, .isnull = isnull};
212 auto nd = nullable_datum(datum);
213 ret.emplace_back(from_nullable_datum<typename T::value_type>(
214 nd, ffi_guard{::SPI_gettypeid}(tuptable->tupdesc, i + 1),
215 memory_context(tuptable->tuptabcxt)));
216 }
217 tuples.at(n).emplace(ret);
218 } else {
219 auto ret = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
220 return T{([&] {
221 bool isnull;
222 ::Datum value =
223 ffi_guard{::SPI_getbinval}(tuptable->vals[n], tuptable->tupdesc, Is + 1, &isnull);
224 ::NullableDatum datum = {.value = value, .isnull = isnull};
225 auto nd = nullable_datum(datum);
226 return from_nullable_datum<utils::tuple_element_t<Is, T>>(
227 nd, ffi_guard{::SPI_gettypeid}(tuptable->tupdesc, Is + 1),
228 memory_context(tuptable->tuptabcxt));
229 }())...};
230 }(std::make_index_sequence<utils::tuple_size_v<T>>{});
231 tuples.at(n).emplace(ret);
232 }
233 return tuples.at(n).value();
234 }
235
236 constexpr bool operator==(const result_iterator &other) const noexcept {
237 return tuptable == other.tuptable && index == other.index;
238 }
239 constexpr bool operator!=(const result_iterator &other) const noexcept {
240 return !(tuptable == other.tuptable && index == other.index);
241 }
242 constexpr bool operator<(const result_iterator &other) const noexcept {
243 return index < other.index;
244 }
245 constexpr bool operator>(const result_iterator &other) const noexcept {
246 return index > other.index;
247 }
248 constexpr bool operator<=(const result_iterator &other) const noexcept {
249 return index <= other.index;
250 }
251 constexpr bool operator>=(const result_iterator &other) const noexcept {
252 return index >= other.index;
253 }
254
255 operator const heap_tuple() const { return tuptable->vals[index]; }
256
257 private:
258 };
259
260 template <typename Ret> struct results {
261 ::SPITupleTable *table;
262
263 results(::SPITupleTable *table) : table(table) {
264 auto natts = table->tupdesc->natts;
265 if constexpr (a_vector<Ret>) {
266 for (int i = 0; i < natts; i++) {
267 auto oid = ffi_guard{::SPI_gettypeid}(table->tupdesc, i + 1);
268 auto t = type{.oid = oid};
270 throw std::invalid_argument(
271 cppgres::fmt::format("invalid return type in position {} ({}), got OID {}", i,
272 utils::type_name<typename Ret::value_type>(), oid));
273 }
274 }
275 } else {
276 if (natts != utils::tuple_size_v<Ret>) {
277 if (natts == 1 && convertible_from_datum<Ret>) {
278 // okay, this is just a type we can convert
279 } else {
280 throw std::runtime_error(cppgres::fmt::format("expected {} return values, got {}",
281 utils::tuple_size_v<Ret>, natts));
282 }
283 } else {
284 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
285 (([&] {
286 auto oid = ffi_guard{::SPI_gettypeid}(table->tupdesc, Is + 1);
287 auto t = type{.oid = oid};
288 if (!type_traits<utils::tuple_element_t<Is, Ret>>().is(t)) {
289 throw std::invalid_argument(cppgres::fmt::format(
290 "invalid return type in position {} ({}), got OID {}", Is,
291 utils::type_name<utils::tuple_element_t<Is, Ret>>(), oid));
292 }
293 }()),
294 ...);
295 }(std::make_index_sequence<utils::tuple_size_v<Ret>>{});
296 }
297 }
298 }
299
300 result_iterator<Ret> begin() const { return result_iterator<Ret>(table); }
301 size_t end() const { return count(); }
302
303 size_t count() const { return table->numvals; }
304
305 tuple_descriptor get_tuple_descriptor() const { return table->tupdesc; }
306 };
307
308 struct options {
309 explicit options() : read_only_(false), count_(0) {}
310 options(bool read_only) : read_only_(read_only), count_(0) {}
311 options(int count) : read_only_(false), count_(count) {}
312 options(bool read_only, int count) : read_only_(read_only), count_(count) {}
313
314 bool read_only() const { return read_only_; }
315 int count() const { return count_; }
316
317 private:
318 bool read_only_;
319 int count_;
320 };
321
336 template <typename Ret, convertible_into_nullable_datum_and_has_a_type... Args>
338 return this->query<Ret>(query, options(), std::forward<Args>(args)...);
339 }
340
353 template <typename Ret, convertible_into_nullable_datum_and_has_a_type... Args>
355 if (executors.top() != this) {
356 throw std::runtime_error("not a current SPI executor");
357 }
358 constexpr size_t nargs = sizeof...(Args);
359 std::array<::Oid, nargs> types = {type_traits<Args>(args).type_for().oid...};
360 auto nullable_datums = make_nullable_datums(std::forward<Args>(args)...);
361 auto datums = make_datums(nullable_datums);
362 auto nulls = make_nulls(nullable_datums);
363 auto rc = ffi_guard{::SPI_execute_with_args}(utils::to_cstring(query), nargs, types.data(),
364 datums.data(), nulls.data(), opts.read_only(),
365 opts.count());
366 if (rc == SPI_OK_SELECT || rc == SPI_OK_INSERT_RETURNING || rc == SPI_OK_UPDATE_RETURNING ||
367 rc == SPI_OK_DELETE_RETURNING || (rc == SPI_OK_UTILITY && SPI_tuptable != nullptr)
368#if PG_MAJORVERSION_NUM >= 17
369 || rc == SPI_OK_MERGE_RETURNING
370#endif
371 ) {
372 // static_assert(std::random_access_iterator<result_iterator<Ret>>);
373 return results<Ret>(SPI_tuptable);
374 } else {
375 throw std::runtime_error(
376 fmt::format("spi error in `{}`", std::string_view(utils::to_cstring(query))));
377 }
378 }
379
381 spi_plan<Args...> plan(utils::convertible_to_cstring auto query) {
382 if (executors.top() != this) {
383 throw std::runtime_error("not a current SPI executor");
384 }
385 constexpr size_t nargs = sizeof...(Args);
386 std::array<::Oid, nargs> types = {type_traits<Args>().type_for().oid...};
387 return spi_plan<Args...>(
388 ffi_guard{::SPI_prepare}(utils::to_cstring(query), nargs, types.data()));
389 }
390
391#if PG_MAJORVERSION_NUM >= 14
402 spi_plan<> plan(utils::convertible_to_cstring auto query,
403 const ::SPIPrepareOptions &opts) {
404 if (executors.top() != this) {
405 throw std::runtime_error("not a current SPI executor");
406 }
407 ::SPIPrepareOptions options = opts;
408 return spi_plan<>(ffi_guard{::SPI_prepare_extended}(utils::to_cstring(query), &options));
409 }
410#endif
411
418 if (executors.empty()) {
419 throw std::runtime_error("no SPI executor in scope");
420 }
421 return *executors.top();
422 }
423
424 template <typename Ret, convertible_into_nullable_datum... Args>
425 results<Ret> query(spi_plan<Args...> &query, Args &&...args) {
426 return this->query<Ret, Args...>(query, options(), std::forward<Args>(args)...);
427 }
428
429 template <typename Ret, convertible_into_nullable_datum... Args>
430 results<Ret> query(spi_plan<Args...> &query, options &&opts, Args &&...args) {
431 if (executors.top() != this) {
432 throw std::runtime_error("not a current SPI executor");
433 }
434 auto nullable_datums = make_nullable_datums(std::forward<Args>(args)...);
435 auto datums = make_datums(nullable_datums);
436 auto nulls = make_nulls(nullable_datums);
437 auto rc = ffi_guard{::SPI_execute_plan}(query, datums.data(), nulls.data(), opts.read_only(),
438 opts.count());
439 if (rc > 0) {
440 // static_assert(std::random_access_iterator<result_iterator<Ret>>);
441 return results<Ret>(SPI_tuptable);
442 } else {
443 throw std::runtime_error("spi error in a query plan");
444 }
445 }
446
447 template <convertible_into_nullable_datum_and_has_a_type... Args>
448 uint64_t execute(std::string_view query, Args &&...args) {
449 return execute(query, options(), std::forward<Args>(args)...);
450 }
451
452 template <convertible_into_nullable_datum_and_has_a_type... Args>
453 uint64_t execute(std::string_view query, options &&opts, Args &&...args) {
454 if (executors.top() != this) {
455 throw std::runtime_error("not a current SPI executor");
456 }
457 constexpr size_t nargs = sizeof...(Args);
458 std::array<::Oid, nargs> types = {type_traits<Args>(args).type_for().oid...};
459 auto nullable_datums = make_nullable_datums(std::forward<Args>(args)...);
460 auto datums = make_datums(nullable_datums);
461 auto nulls = make_nulls(nullable_datums);
462 auto rc = ffi_guard{::SPI_execute_with_args}(utils::to_cstring(query), nargs, types.data(),
463 datums.data(), nulls.data(), opts.read_only(),
464 opts.count());
465 if (rc >= 0) {
466 return SPI_processed;
467 } else {
468 throw std::runtime_error(cppgres::fmt::format("spi error in `{}`", query));
469 }
470 }
471
472private:
473 template <convertible_into_nullable_datum... Args>
474 static auto make_nullable_datums(Args &&...args) {
475 return std::array<nullable_datum, sizeof...(Args)>{
476 into_nullable_datum(std::forward<Args>(args))...};
477 }
478
479 template <std::size_t nargs>
480 static std::array<::Datum, nargs> make_datums(const std::array<nullable_datum, nargs> &values) {
481 std::array<::Datum, nargs> datums{};
482 for (std::size_t i = 0; i < nargs; i++) {
483 datums[i] = values[i].is_null()
484 ? ::Datum(0)
485 : static_cast<const ::Datum &>(static_cast<const datum &>(values[i]));
486 }
487 return datums;
488 }
489
490 template <std::size_t nargs>
491 static std::array<char, nargs> make_nulls(
492 const std::array<nullable_datum, nargs> &values) {
493 std::array<char, nargs> nulls{};
494 for (std::size_t i = 0; i < nargs; i++) {
495 nulls[i] = values[i].is_null() ? 'n' : ' ';
496 }
497 return nulls;
498 }
499
500 ::MemoryContext before_spi;
501 ::MemoryContext spi;
502
503protected:
504 static inline std::stack<spi_executor *> executors;
505 spi_executor(int flags) : before_spi(::CurrentMemoryContext) {
506 ffi_guard{::SPI_connect_ext}(flags);
507 spi = ::CurrentMemoryContext;
508 ::CurrentMemoryContext = before_spi;
509 executors.push(this);
510 }
511};
512
515
516 spi_nonatomic_executor() : spi_executor(SPI_OPT_NONATOMIC) {
517 auto atomic = cppgres::current_postgres_function::atomic();
518 if (atomic.has_value() && atomic.value()) {
519 throw std::runtime_error("must be called in a non-atomic context");
520 }
521 }
522
523 void commit(bool chain = false) {
524 if (executors.top() != this) {
525 throw std::runtime_error("not a current SPI executor");
526 }
527 ffi_guard(chain ? ::SPI_commit_and_chain : ::SPI_commit)();
528 }
529
530 void rollback(bool chain = false) {
531 if (executors.top() != this) {
532 throw std::runtime_error("not a current SPI executor");
533 }
534 ffi_guard(chain ? ::SPI_rollback_and_chain : ::SPI_rollback)();
535 }
536};
537
538} // namespace cppgres
Definition: executor.hpp:71
Definition: record.hpp:451
Definition: datum.hpp:176
Definition: type.hpp:268
Definition: cstring.hpp:21
spi_opt
SPI connection options.
Definition: executor.hpp:79
Definition: datum.hpp:39
Definition: executor.hpp:68
Definition: guard.hpp:20
Heap tuple convenience wrapper.
Definition: heap_tuple.hpp:11
Definition: memory.hpp:139
Definition: datum.hpp:60
Definition: datum.hpp:17
Definition: memory.hpp:326
Definition: executor.hpp:308
Definition: executor.hpp:108
Definition: executor.hpp:260
SPI executor API
Definition: executor.hpp:88
results< Ret > query(utils::convertible_to_cstring auto query, options &&opts, Args &&...args)
Queries using a string view.
Definition: executor.hpp:354
spi_executor()
Creates an SPI executor.
Definition: executor.hpp:92
results< Ret > query(utils::convertible_to_cstring auto query, Args &&...args)
Queries using a string view.
Definition: executor.hpp:337
static spi_executor & current()
The innermost live SPI executor.
Definition: executor.hpp:417
spi_executor(spi_opt opts)
Creates an SPI executor with explicitly chosen options.
Definition: executor.hpp:102
Definition: executor.hpp:513
spi_executor()
Creates an SPI executor.
Definition: executor.hpp:92
Definition: executor.hpp:24
::SPIPlanPtr release() noexcept
Releases ownership of the plan to the caller.
Definition: executor.hpp:48
Tuple descriptor operator.
Definition: record.hpp:21
Definition: type.hpp:43
Postgres type.
Definition: type.hpp:22
Definition: value.hpp:8