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 index++;
110 return this;
111 }
112
113 constexpr result_iterator &operator--() noexcept {
114 index--;
115 return this;
116 }
117 constexpr result_iterator operator--(int) noexcept {
118 index--;
119 return this;
120 }
121
122 constexpr result_iterator operator+(const difference_type n) const noexcept {
123 return result_iterator(tuptable, index + n);
124 }
125
126 result_iterator &operator+=(difference_type n) noexcept {
127 index += n;
128 return this;
129 }
130
131 constexpr result_iterator operator-(difference_type n) const noexcept {
132 return result_iterator(tuptable, index - n);
133 }
134
135 result_iterator &operator-=(difference_type n) noexcept {
136 index -= n;
137 return this;
138 }
139
140 constexpr difference_type operator-(const result_iterator &other) const noexcept {
141 return index - other.index;
142 }
143
144 T &operator[](difference_type n) const {
145 if (tuples.at(n).has_value()) {
146 return tuples.at(n).value();
147 }
148 if constexpr (convertible_from_datum<T>) {
149 // if a special case of a directly convertible type
150 if constexpr (composite_type<T>) {
151 bool isnull;
152 ::Datum value =
153 ffi_guard{::SPI_getbinval}(tuptable->vals[n], tuptable->tupdesc, 1, &isnull);
154 ::NullableDatum datum = {.value = value, .isnull = isnull};
155 auto ret = from_nullable_datum<T>(nullable_datum(datum),
156 ffi_guard{::SPI_gettypeid}(tuptable->tupdesc, 1),
157 memory_context(tuptable->tuptabcxt));
158 tuples.emplace(std::next(tuples.begin(), n), std::in_place, ret);
159 return tuples.at(n).value();
160 } else if (tuptable->tupdesc->natts == 1) {
161 bool isnull;
162 ::Datum value =
163 ffi_guard{::SPI_getbinval}(tuptable->vals[n], tuptable->tupdesc, 1, &isnull);
164 ::NullableDatum datum = {.value = value, .isnull = isnull};
165 auto ret = from_nullable_datum<T>(nullable_datum(datum),
166 ffi_guard{::SPI_gettypeid}(tuptable->tupdesc, 1),
167 memory_context(tuptable->tuptabcxt));
168 tuples.emplace(std::next(tuples.begin(), n), std::in_place, ret);
169 return tuples.at(n).value();
170 }
171 }
172 if constexpr (a_vector<T>) {
173 T ret;
174 for (int i = 0; i < tuptable->tupdesc->natts; i++) {
175 bool isnull;
176 ::Datum value =
177 ffi_guard{::SPI_getbinval}(tuptable->vals[n], tuptable->tupdesc, i + 1, &isnull);
178 ::NullableDatum datum = {.value = value, .isnull = isnull};
179 auto nd = nullable_datum(datum);
180 ret.emplace_back(from_nullable_datum<typename T::value_type>(
181 nd, ffi_guard{::SPI_gettypeid}(tuptable->tupdesc, i + 1),
182 memory_context(tuptable->tuptabcxt)));
183 }
184 tuples.emplace(std::next(tuples.begin(), n), std::in_place, ret);
185 } else {
186 auto ret = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
187 return T{([&] {
188 bool isnull;
189 ::Datum value =
190 ffi_guard{::SPI_getbinval}(tuptable->vals[n], tuptable->tupdesc, Is + 1, &isnull);
191 ::NullableDatum datum = {.value = value, .isnull = isnull};
192 auto nd = nullable_datum(datum);
193 return from_nullable_datum<utils::tuple_element_t<Is, T>>(
194 nd, ffi_guard{::SPI_gettypeid}(tuptable->tupdesc, Is + 1),
195 memory_context(tuptable->tuptabcxt));
196 }())...};
197 }(std::make_index_sequence<utils::tuple_size_v<T>>{});
198 tuples.emplace(std::next(tuples.begin(), n), std::in_place, ret);
199 }
200 return tuples.at(n).value();
201 }
202
203 constexpr bool operator==(const result_iterator &other) const noexcept {
204 return tuptable == other.tuptable && index == other.index;
205 }
206 constexpr bool operator!=(const result_iterator &other) const noexcept {
207 return !(tuptable == other.tuptable && index == other.index);
208 }
209 constexpr bool operator<(const result_iterator &other) const noexcept {
210 return index < other.index;
211 }
212 constexpr bool operator>(const result_iterator &other) const noexcept {
213 return index > other.index;
214 }
215 constexpr bool operator<=(const result_iterator &other) const noexcept {
216 return index <= other.index;
217 }
218 constexpr bool operator>=(const result_iterator &other) const noexcept {
219 return index >= other.index;
220 }
221
222 operator const heap_tuple() const { return tuptable->vals[index]; }
223
224 private:
225 };
226
227 template <typename Ret> struct results {
228 ::SPITupleTable *table;
229
230 results(::SPITupleTable *table) : table(table) {
231 auto natts = table->tupdesc->natts;
232 if constexpr (a_vector<Ret>) {
233 for (int i = 0; i < natts; i++) {
234 auto oid = ffi_guard{::SPI_gettypeid}(table->tupdesc, i + 1);
235 auto t = type{.oid = oid};
237 throw std::invalid_argument(
238 cppgres::fmt::format("invalid return type in position {} ({}), got OID {}", i,
239 utils::type_name<typename Ret::value_type>(), oid));
240 }
241 }
242 } else {
243 if (natts != utils::tuple_size_v<Ret>) {
244 if (natts == 1 && convertible_from_datum<Ret>) {
245 // okay, this is just a type we can convert
246 } else {
247 throw std::runtime_error(cppgres::fmt::format("expected {} return values, got {}",
248 utils::tuple_size_v<Ret>, natts));
249 }
250 } else {
251 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
252 (([&] {
253 auto oid = ffi_guard{::SPI_gettypeid}(table->tupdesc, Is + 1);
254 auto t = type{.oid = oid};
255 if (!type_traits<utils::tuple_element_t<Is, Ret>>().is(t)) {
256 throw std::invalid_argument(cppgres::fmt::format(
257 "invalid return type in position {} ({}), got OID {}", Is,
258 utils::type_name<utils::tuple_element_t<Is, Ret>>(), oid));
259 }
260 }()),
261 ...);
262 }(std::make_index_sequence<utils::tuple_size_v<Ret>>{});
263 }
264 }
265 }
266
267 result_iterator<Ret> begin() const { return result_iterator<Ret>(table); }
268 size_t end() const { return count(); }
269
270 size_t count() const { return table->numvals; }
271
272 tuple_descriptor get_tuple_descriptor() const { return table->tupdesc; }
273 };
274
275 struct options {
276 explicit options() : read_only_(false), count_(0) {}
277 options(bool read_only) : read_only_(read_only), count_(0) {}
278 options(int count) : read_only_(false), count_(count) {}
279 options(bool read_only, int count) : read_only_(read_only), count_(count) {}
280
281 bool read_only() const { return read_only_; }
282 int count() const { return count_; }
283
284 private:
285 bool read_only_;
286 int count_;
287 };
288
303 template <typename Ret, convertible_into_nullable_datum_and_has_a_type... Args>
305 return this->query<Ret>(query, options(), std::forward<Args>(args)...);
306 }
307
320 template <typename Ret, convertible_into_nullable_datum_and_has_a_type... Args>
322 if (executors.top() != this) {
323 throw std::runtime_error("not a current SPI executor");
324 }
325 constexpr size_t nargs = sizeof...(Args);
326 std::array<::Oid, nargs> types = {type_traits<Args>(args...).type_for().oid...};
327 std::array<::Datum, nargs> datums = {into_nullable_datum(args)...};
328 std::array<const char, nargs> nulls = {into_nullable_datum(args).is_null() ? 'n' : ' ' ...};
329 auto rc = ffi_guard{::SPI_execute_with_args}(utils::to_cstring(query), nargs, types.data(),
330 datums.data(), nulls.data(), opts.read_only(),
331 opts.count());
332 if (rc == SPI_OK_SELECT || rc == SPI_OK_INSERT_RETURNING || rc == SPI_OK_UPDATE_RETURNING ||
333 rc == SPI_OK_DELETE_RETURNING || (rc == SPI_OK_UTILITY && SPI_tuptable != nullptr)
334#if PG_MAJORVERSION_NUM >= 17
335 || rc == SPI_OK_MERGE_RETURNING
336#endif
337 ) {
338 // static_assert(std::random_access_iterator<result_iterator<Ret>>);
339 return results<Ret>(SPI_tuptable);
340 } else {
341 throw std::runtime_error(
342 fmt::format("spi error in `{}`", std::string_view(utils::to_cstring(query))));
343 }
344 }
345
347 spi_plan<Args...> plan(utils::convertible_to_cstring auto query) {
348 if (executors.top() != this) {
349 throw std::runtime_error("not a current SPI executor");
350 }
351 constexpr size_t nargs = sizeof...(Args);
352 std::array<::Oid, nargs> types = {type_traits<Args>().type_for().oid...};
353 return spi_plan<Args...>(
354 ffi_guard{::SPI_prepare}(utils::to_cstring(query), nargs, types.data()));
355 }
356
357 template <typename Ret, convertible_into_nullable_datum... Args>
358 results<Ret> query(spi_plan<Args...> &query, Args &&...args) {
359 return this->query<Ret, Args...>(query, options(), std::forward<Args>(args)...);
360 }
361
362 template <typename Ret, convertible_into_nullable_datum... Args>
363 results<Ret> query(spi_plan<Args...> &query, options &&opts, Args &&...args) {
364 if (executors.top() != this) {
365 throw std::runtime_error("not a current SPI executor");
366 }
367 constexpr size_t nargs = sizeof...(Args);
368 std::array<::Datum, nargs> datums = {into_nullable_datum(args)...};
369 std::array<const char, nargs> nulls = {into_nullable_datum(args).is_null() ? 'n' : ' ' ...};
370 auto rc = ffi_guard{::SPI_execute_plan}(query, datums.data(), nulls.data(), opts.read_only(),
371 opts.count());
372 if (rc > 0) {
373 // static_assert(std::random_access_iterator<result_iterator<Ret>>);
374 return results<Ret>(SPI_tuptable);
375 } else {
376 throw std::runtime_error("spi error in a query plan");
377 }
378 }
379
380 template <convertible_into_nullable_datum_and_has_a_type... Args>
381 uint64_t execute(std::string_view query, Args &&...args) {
382 return execute(query, options(), std::forward<Args>(args)...);
383 }
384
385 template <convertible_into_nullable_datum_and_has_a_type... Args>
386 uint64_t execute(std::string_view query, options &&opts, Args &&...args) {
387 if (executors.top() != this) {
388 throw std::runtime_error("not a current SPI executor");
389 }
390 constexpr size_t nargs = sizeof...(Args);
391 std::array<::Oid, nargs> types = {type_traits<Args>(args...).type_for().oid...};
392 std::array<::Datum, nargs> datums = {into_nullable_datum(args)...};
393 std::array<const char, nargs> nulls = {into_nullable_datum(args).is_null() ? 'n' : ' ' ...};
394 auto rc = ffi_guard{::SPI_execute_with_args}(query.data(), nargs, types.data(), datums.data(),
395 nulls.data(), opts.read_only(), opts.count());
396 if (rc >= 0) {
397 return SPI_processed;
398 } else {
399 throw std::runtime_error(cppgres::fmt::format("spi error in `{}`", query));
400 }
401 }
402
403private:
404 ::MemoryContext before_spi;
405 ::MemoryContext spi;
406
407protected:
408 static inline std::stack<spi_executor *> executors;
409 spi_executor(int flags) : before_spi(::CurrentMemoryContext) {
410 ffi_guard{::SPI_connect_ext}(flags);
411 spi = ::CurrentMemoryContext;
412 ::CurrentMemoryContext = before_spi;
413 executors.push(this);
414 }
415};
416
419
420 spi_nonatomic_executor() : spi_executor(SPI_OPT_NONATOMIC) {
421 auto atomic = cppgres::current_postgres_function::atomic();
422 if (atomic.has_value() && atomic.value()) {
423 throw std::runtime_error("must be called in a non-atomic context");
424 }
425 }
426
427 void commit(bool chain = false) {
428 if (executors.top() != this) {
429 throw std::runtime_error("not a current SPI executor");
430 }
431 ffi_guard(chain ? ::SPI_commit_and_chain : ::SPI_commit)();
432 }
433
434 void rollback(bool chain = false) {
435 if (executors.top() != this) {
436 throw std::runtime_error("not a current SPI executor");
437 }
438 ffi_guard(chain ? ::SPI_rollback_and_chain : ::SPI_rollback)();
439 }
440};
441
442} // namespace cppgres
Definition: executor.hpp:59
Definition: record.hpp:440
Definition: datum.hpp:176
Definition: type.hpp:263
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:85
Definition: datum.hpp:60
Definition: datum.hpp:17
Definition: memory.hpp:267
Definition: executor.hpp:275
Definition: executor.hpp:77
Definition: executor.hpp:227
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:321
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:304
Definition: executor.hpp:417
spi_executor()
Creates an SPI executor.
Definition: executor.hpp:71
Definition: executor.hpp:24
Definition: memory.hpp:135
Tuple descriptor operator.
Definition: record.hpp:18
Definition: type.hpp:41
Postgres type.
Definition: type.hpp:20
Definition: value.hpp:8