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
41 ~spi_plan() {
42 if (kept) {
43 ffi_guard{::SPI_freeplan}(*this);
44 }
45 }
46
47private:
48 spi_plan(::SPIPlanPtr plan)
49 : kept(false), plan(plan), ctx(tracking_memory_context(memory_context::for_pointer(plan))) {}
50
51 bool kept;
52 ::SPIPlanPtr plan;
54};
55
56struct executor {};
57
58template <typename T>
59concept a_vector = requires {
60 typename T::value_type;
61 typename T::allocator_type;
62} && std::same_as<T, std::vector<typename T::value_type, typename T::allocator_type>>;
63
67struct spi_executor : public executor {
73 ffi_guard{::SPI_finish}();
74 executors.pop();
75 }
76
77 template <typename T> struct result_iterator {
78 using iterator_category = std::random_access_iterator_tag;
79 using value_type = T;
80 using difference_type = std::ptrdiff_t;
81
82 ::SPITupleTable *tuptable;
83 size_t index;
84 mutable std::vector<std::optional<T>> tuples;
85
86 constexpr result_iterator() noexcept {}
87
88 constexpr result_iterator(::SPITupleTable *tuptable) noexcept
89 : tuptable(tuptable), index(0),
90 tuples(std::vector<std::optional<T>>(tuptable->numvals, std::nullopt)) {
91 tuples.reserve(tuptable->numvals);
92 }
93 constexpr result_iterator(::SPITupleTable *tuptable, size_t n) noexcept
94 : tuptable(tuptable), index(n),
95 tuples(std::vector<std::optional<T>>(tuptable->numvals, std::nullopt)) {
96 tuples.reserve(tuptable->numvals);
97 }
98
99 bool operator==(size_t end_index) const { return index == end_index; }
100 bool operator!=(size_t end_index) const { return index != end_index; }
101
102 constexpr T &operator*() const { return this->operator[](static_cast<difference_type>(index)); }
103
104 constexpr result_iterator &operator++() noexcept {
105 index++;
106 return *this;
107 }
108 constexpr result_iterator operator++(int) noexcept {
109 auto ret = *this;
110 index++;
111 return ret;
112 }
113
114 constexpr result_iterator &operator--() noexcept {
115 index--;
116 return *this;
117 }
118 constexpr result_iterator operator--(int) noexcept {
119 auto ret = *this;
120 index--;
121 return ret;
122 }
123
124 constexpr result_iterator operator+(const difference_type n) const noexcept {
125 return result_iterator(tuptable, index + n);
126 }
127
128 result_iterator &operator+=(difference_type n) noexcept {
129 index += n;
130 return *this;
131 }
132
133 constexpr result_iterator operator-(difference_type n) const noexcept {
134 return result_iterator(tuptable, index - n);
135 }
136
137 result_iterator &operator-=(difference_type n) noexcept {
138 index -= n;
139 return *this;
140 }
141
142 constexpr difference_type operator-(const result_iterator &other) const noexcept {
143 return index - other.index;
144 }
145
146 T &operator[](difference_type n) const {
147 if (tuples.at(n).has_value()) {
148 return tuples.at(n).value();
149 }
150 if constexpr (convertible_from_datum<T>) {
151 // if a special case of a directly convertible type
152 if constexpr (composite_type<T>) {
153 bool isnull;
154 ::Datum value =
155 ffi_guard{::SPI_getbinval}(tuptable->vals[n], tuptable->tupdesc, 1, &isnull);
156 ::NullableDatum datum = {.value = value, .isnull = isnull};
157 auto ret = from_nullable_datum<T>(nullable_datum(datum),
158 ffi_guard{::SPI_gettypeid}(tuptable->tupdesc, 1),
159 memory_context(tuptable->tuptabcxt));
160 tuples.at(n).emplace(ret);
161 return tuples.at(n).value();
162 } else if (tuptable->tupdesc->natts == 1) {
163 bool isnull;
164 ::Datum value =
165 ffi_guard{::SPI_getbinval}(tuptable->vals[n], tuptable->tupdesc, 1, &isnull);
166 ::NullableDatum datum = {.value = value, .isnull = isnull};
167 auto ret = from_nullable_datum<T>(nullable_datum(datum),
168 ffi_guard{::SPI_gettypeid}(tuptable->tupdesc, 1),
169 memory_context(tuptable->tuptabcxt));
170 tuples.at(n).emplace(ret);
171 return tuples.at(n).value();
172 }
173 }
174 if constexpr (a_vector<T>) {
175 T ret;
176 for (int i = 0; i < tuptable->tupdesc->natts; i++) {
177 bool isnull;
178 ::Datum value =
179 ffi_guard{::SPI_getbinval}(tuptable->vals[n], tuptable->tupdesc, i + 1, &isnull);
180 ::NullableDatum datum = {.value = value, .isnull = isnull};
181 auto nd = nullable_datum(datum);
182 ret.emplace_back(from_nullable_datum<typename T::value_type>(
183 nd, ffi_guard{::SPI_gettypeid}(tuptable->tupdesc, i + 1),
184 memory_context(tuptable->tuptabcxt)));
185 }
186 tuples.at(n).emplace(ret);
187 } else {
188 auto ret = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
189 return T{([&] {
190 bool isnull;
191 ::Datum value =
192 ffi_guard{::SPI_getbinval}(tuptable->vals[n], tuptable->tupdesc, Is + 1, &isnull);
193 ::NullableDatum datum = {.value = value, .isnull = isnull};
194 auto nd = nullable_datum(datum);
195 return from_nullable_datum<utils::tuple_element_t<Is, T>>(
196 nd, ffi_guard{::SPI_gettypeid}(tuptable->tupdesc, Is + 1),
197 memory_context(tuptable->tuptabcxt));
198 }())...};
199 }(std::make_index_sequence<utils::tuple_size_v<T>>{});
200 tuples.at(n).emplace(ret);
201 }
202 return tuples.at(n).value();
203 }
204
205 constexpr bool operator==(const result_iterator &other) const noexcept {
206 return tuptable == other.tuptable && index == other.index;
207 }
208 constexpr bool operator!=(const result_iterator &other) const noexcept {
209 return !(tuptable == other.tuptable && index == other.index);
210 }
211 constexpr bool operator<(const result_iterator &other) const noexcept {
212 return index < other.index;
213 }
214 constexpr bool operator>(const result_iterator &other) const noexcept {
215 return index > other.index;
216 }
217 constexpr bool operator<=(const result_iterator &other) const noexcept {
218 return index <= other.index;
219 }
220 constexpr bool operator>=(const result_iterator &other) const noexcept {
221 return index >= other.index;
222 }
223
224 operator const heap_tuple() const { return tuptable->vals[index]; }
225
226 private:
227 };
228
229 template <typename Ret> struct results {
230 ::SPITupleTable *table;
231
232 results(::SPITupleTable *table) : table(table) {
233 auto natts = table->tupdesc->natts;
234 if constexpr (a_vector<Ret>) {
235 for (int i = 0; i < natts; i++) {
236 auto oid = ffi_guard{::SPI_gettypeid}(table->tupdesc, i + 1);
237 auto t = type{.oid = oid};
239 throw std::invalid_argument(
240 cppgres::fmt::format("invalid return type in position {} ({}), got OID {}", i,
241 utils::type_name<typename Ret::value_type>(), oid));
242 }
243 }
244 } else {
245 if (natts != utils::tuple_size_v<Ret>) {
246 if (natts == 1 && convertible_from_datum<Ret>) {
247 // okay, this is just a type we can convert
248 } else {
249 throw std::runtime_error(cppgres::fmt::format("expected {} return values, got {}",
250 utils::tuple_size_v<Ret>, natts));
251 }
252 } else {
253 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
254 (([&] {
255 auto oid = ffi_guard{::SPI_gettypeid}(table->tupdesc, Is + 1);
256 auto t = type{.oid = oid};
257 if (!type_traits<utils::tuple_element_t<Is, Ret>>().is(t)) {
258 throw std::invalid_argument(cppgres::fmt::format(
259 "invalid return type in position {} ({}), got OID {}", Is,
260 utils::type_name<utils::tuple_element_t<Is, Ret>>(), oid));
261 }
262 }()),
263 ...);
264 }(std::make_index_sequence<utils::tuple_size_v<Ret>>{});
265 }
266 }
267 }
268
269 result_iterator<Ret> begin() const { return result_iterator<Ret>(table); }
270 size_t end() const { return count(); }
271
272 size_t count() const { return table->numvals; }
273
274 tuple_descriptor get_tuple_descriptor() const { return table->tupdesc; }
275 };
276
277 struct options {
278 explicit options() : read_only_(false), count_(0) {}
279 options(bool read_only) : read_only_(read_only), count_(0) {}
280 options(int count) : read_only_(false), count_(count) {}
281 options(bool read_only, int count) : read_only_(read_only), count_(count) {}
282
283 bool read_only() const { return read_only_; }
284 int count() const { return count_; }
285
286 private:
287 bool read_only_;
288 int count_;
289 };
290
305 template <typename Ret, convertible_into_nullable_datum_and_has_a_type... Args>
307 return this->query<Ret>(query, options(), std::forward<Args>(args)...);
308 }
309
322 template <typename Ret, convertible_into_nullable_datum_and_has_a_type... Args>
324 if (executors.top() != this) {
325 throw std::runtime_error("not a current SPI executor");
326 }
327 constexpr size_t nargs = sizeof...(Args);
328 std::array<::Oid, nargs> types = {type_traits<Args>(args).type_for().oid...};
329 auto nullable_datums = make_nullable_datums(std::forward<Args>(args)...);
330 auto datums = make_datums(nullable_datums);
331 auto nulls = make_nulls(nullable_datums);
332 auto rc = ffi_guard{::SPI_execute_with_args}(utils::to_cstring(query), nargs, types.data(),
333 datums.data(), nulls.data(), opts.read_only(),
334 opts.count());
335 if (rc == SPI_OK_SELECT || rc == SPI_OK_INSERT_RETURNING || rc == SPI_OK_UPDATE_RETURNING ||
336 rc == SPI_OK_DELETE_RETURNING || (rc == SPI_OK_UTILITY && SPI_tuptable != nullptr)
337#if PG_MAJORVERSION_NUM >= 17
338 || rc == SPI_OK_MERGE_RETURNING
339#endif
340 ) {
341 // static_assert(std::random_access_iterator<result_iterator<Ret>>);
342 return results<Ret>(SPI_tuptable);
343 } else {
344 throw std::runtime_error(
345 fmt::format("spi error in `{}`", std::string_view(utils::to_cstring(query))));
346 }
347 }
348
350 spi_plan<Args...> plan(utils::convertible_to_cstring auto query) {
351 if (executors.top() != this) {
352 throw std::runtime_error("not a current SPI executor");
353 }
354 constexpr size_t nargs = sizeof...(Args);
355 std::array<::Oid, nargs> types = {type_traits<Args>().type_for().oid...};
356 return spi_plan<Args...>(
357 ffi_guard{::SPI_prepare}(utils::to_cstring(query), nargs, types.data()));
358 }
359
360 template <typename Ret, convertible_into_nullable_datum... Args>
361 results<Ret> query(spi_plan<Args...> &query, Args &&...args) {
362 return this->query<Ret, Args...>(query, options(), std::forward<Args>(args)...);
363 }
364
365 template <typename Ret, convertible_into_nullable_datum... Args>
366 results<Ret> query(spi_plan<Args...> &query, options &&opts, Args &&...args) {
367 if (executors.top() != this) {
368 throw std::runtime_error("not a current SPI executor");
369 }
370 auto nullable_datums = make_nullable_datums(std::forward<Args>(args)...);
371 auto datums = make_datums(nullable_datums);
372 auto nulls = make_nulls(nullable_datums);
373 auto rc = ffi_guard{::SPI_execute_plan}(query, datums.data(), nulls.data(), opts.read_only(),
374 opts.count());
375 if (rc > 0) {
376 // static_assert(std::random_access_iterator<result_iterator<Ret>>);
377 return results<Ret>(SPI_tuptable);
378 } else {
379 throw std::runtime_error("spi error in a query plan");
380 }
381 }
382
383 template <convertible_into_nullable_datum_and_has_a_type... Args>
384 uint64_t execute(std::string_view query, Args &&...args) {
385 return execute(query, options(), std::forward<Args>(args)...);
386 }
387
388 template <convertible_into_nullable_datum_and_has_a_type... Args>
389 uint64_t execute(std::string_view query, options &&opts, Args &&...args) {
390 if (executors.top() != this) {
391 throw std::runtime_error("not a current SPI executor");
392 }
393 constexpr size_t nargs = sizeof...(Args);
394 std::array<::Oid, nargs> types = {type_traits<Args>(args).type_for().oid...};
395 auto nullable_datums = make_nullable_datums(std::forward<Args>(args)...);
396 auto datums = make_datums(nullable_datums);
397 auto nulls = make_nulls(nullable_datums);
398 auto rc = ffi_guard{::SPI_execute_with_args}(utils::to_cstring(query), nargs, types.data(),
399 datums.data(), nulls.data(), opts.read_only(),
400 opts.count());
401 if (rc >= 0) {
402 return SPI_processed;
403 } else {
404 throw std::runtime_error(cppgres::fmt::format("spi error in `{}`", query));
405 }
406 }
407
408private:
409 template <convertible_into_nullable_datum... Args>
410 static auto make_nullable_datums(Args &&...args) {
411 return std::array<nullable_datum, sizeof...(Args)>{
412 into_nullable_datum(std::forward<Args>(args))...};
413 }
414
415 template <std::size_t nargs>
416 static std::array<::Datum, nargs> make_datums(const std::array<nullable_datum, nargs> &values) {
417 std::array<::Datum, nargs> datums{};
418 for (std::size_t i = 0; i < nargs; i++) {
419 datums[i] = values[i].is_null()
420 ? ::Datum(0)
421 : static_cast<const ::Datum &>(static_cast<const datum &>(values[i]));
422 }
423 return datums;
424 }
425
426 template <std::size_t nargs>
427 static std::array<char, nargs> make_nulls(
428 const std::array<nullable_datum, nargs> &values) {
429 std::array<char, nargs> nulls{};
430 for (std::size_t i = 0; i < nargs; i++) {
431 nulls[i] = values[i].is_null() ? 'n' : ' ';
432 }
433 return nulls;
434 }
435
436 ::MemoryContext before_spi;
437 ::MemoryContext spi;
438
439protected:
440 static inline std::stack<spi_executor *> executors;
441 spi_executor(int flags) : before_spi(::CurrentMemoryContext) {
442 ffi_guard{::SPI_connect_ext}(flags);
443 spi = ::CurrentMemoryContext;
444 ::CurrentMemoryContext = before_spi;
445 executors.push(this);
446 }
447};
448
451
452 spi_nonatomic_executor() : spi_executor(SPI_OPT_NONATOMIC) {
453 auto atomic = cppgres::current_postgres_function::atomic();
454 if (atomic.has_value() && atomic.value()) {
455 throw std::runtime_error("must be called in a non-atomic context");
456 }
457 }
458
459 void commit(bool chain = false) {
460 if (executors.top() != this) {
461 throw std::runtime_error("not a current SPI executor");
462 }
463 ffi_guard(chain ? ::SPI_commit_and_chain : ::SPI_commit)();
464 }
465
466 void rollback(bool chain = false) {
467 if (executors.top() != this) {
468 throw std::runtime_error("not a current SPI executor");
469 }
470 ffi_guard(chain ? ::SPI_rollback_and_chain : ::SPI_rollback)();
471 }
472};
473
474} // namespace cppgres
Definition: executor.hpp:59
Definition: record.hpp:444
Definition: datum.hpp:176
Definition: type.hpp:274
Definition: cstring.hpp:21
Definition: datum.hpp:39
Definition: executor.hpp:56
Definition: guard.hpp:19
Heap tuple convenience wrapper.
Definition: heap_tuple.hpp:11
Definition: memory.hpp:103
Definition: datum.hpp:60
Definition: datum.hpp:17
Definition: memory.hpp:290
Definition: executor.hpp:277
Definition: executor.hpp:77
Definition: executor.hpp:229
SPI executor API
Definition: executor.hpp:67
results< Ret > query(utils::convertible_to_cstring auto query, options &&opts, Args &&...args)
Queries using a string view.
Definition: executor.hpp:323
spi_executor()
Creates an SPI executor.
Definition: executor.hpp:71
results< Ret > query(utils::convertible_to_cstring auto query, Args &&...args)
Queries using a string view.
Definition: executor.hpp:306
Definition: executor.hpp:449
spi_executor()
Creates an SPI executor.
Definition: executor.hpp:71
Definition: executor.hpp:24
Definition: memory.hpp:153
Tuple descriptor operator.
Definition: record.hpp:21
Definition: type.hpp:42
Postgres type.
Definition: type.hpp:21
Definition: value.hpp:8