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 (tuptable->tupdesc->natts == 1) {
150 // if a special case of a directly convertible type
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 }
161 }
162 if constexpr (a_vector<T>) {
163 T ret;
164 for (int i = 0; i < tuptable->tupdesc->natts; i++) {
165 bool isnull;
166 ::Datum value =
167 ffi_guard{::SPI_getbinval}(tuptable->vals[n], tuptable->tupdesc, i + 1, &isnull);
168 ::NullableDatum datum = {.value = value, .isnull = isnull};
169 auto nd = nullable_datum(datum);
170 ret.emplace_back(from_nullable_datum<typename T::value_type>(
171 nd, ffi_guard{::SPI_gettypeid}(tuptable->tupdesc, i + 1),
172 memory_context(tuptable->tuptabcxt)));
173 }
174 tuples.emplace(std::next(tuples.begin(), n), std::in_place, ret);
175 } else {
176 auto ret = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
177 return T{([&] {
178 bool isnull;
179 ::Datum value =
180 ffi_guard{::SPI_getbinval}(tuptable->vals[n], tuptable->tupdesc, Is + 1, &isnull);
181 ::NullableDatum datum = {.value = value, .isnull = isnull};
182 auto nd = nullable_datum(datum);
183 return from_nullable_datum<utils::tuple_element_t<Is, T>>(
184 nd, ffi_guard{::SPI_gettypeid}(tuptable->tupdesc, Is + 1),
185 memory_context(tuptable->tuptabcxt));
186 }())...};
187 }(std::make_index_sequence<utils::tuple_size_v<T>>{});
188 tuples.emplace(std::next(tuples.begin(), n), std::in_place, ret);
189 }
190 return tuples.at(n).value();
191 }
192
193 constexpr bool operator==(const result_iterator &other) const noexcept {
194 return tuptable == other.tuptable && index == other.index;
195 }
196 constexpr bool operator!=(const result_iterator &other) const noexcept {
197 return !(tuptable == other.tuptable && index == other.index);
198 }
199 constexpr bool operator<(const result_iterator &other) const noexcept {
200 return index < other.index;
201 }
202 constexpr bool operator>(const result_iterator &other) const noexcept {
203 return index > other.index;
204 }
205 constexpr bool operator<=(const result_iterator &other) const noexcept {
206 return index <= other.index;
207 }
208 constexpr bool operator>=(const result_iterator &other) const noexcept {
209 return index >= other.index;
210 }
211
212 operator const heap_tuple() const { return tuptable->vals[index]; }
213
214 private:
215 };
216
217 template <typename Ret> struct results {
218 ::SPITupleTable *table;
219
220 results(::SPITupleTable *table) : table(table) {
221 auto natts = table->tupdesc->natts;
222 if constexpr (a_vector<Ret>) {
223 for (int i = 0; i < natts; i++) {
224 auto oid = ffi_guard{::SPI_gettypeid}(table->tupdesc, i + 1);
225 auto t = type{.oid = oid};
227 throw std::invalid_argument(
228 cppgres::fmt::format("invalid return type in position {} ({}), got OID {}", i,
229 utils::type_name<typename Ret::value_type>(), oid));
230 }
231 }
232 } else {
233 if (natts != utils::tuple_size_v<Ret>) {
234 if (natts == 1 && convertible_from_datum<Ret>) {
235 // okay, this is just a type we can convert
236 } else {
237 throw std::runtime_error(cppgres::fmt::format("expected {} return values, got {}",
238 utils::tuple_size_v<Ret>, natts));
239 }
240 } else {
241 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
242 (([&] {
243 auto oid = ffi_guard{::SPI_gettypeid}(table->tupdesc, Is + 1);
244 auto t = type{.oid = oid};
245 if (!type_traits<utils::tuple_element_t<Is, Ret>>().is(t)) {
246 throw std::invalid_argument(cppgres::fmt::format(
247 "invalid return type in position {} ({}), got OID {}", Is,
248 utils::type_name<utils::tuple_element_t<Is, Ret>>(), oid));
249 }
250 }()),
251 ...);
252 }(std::make_index_sequence<utils::tuple_size_v<Ret>>{});
253 }
254 }
255 }
256
257 result_iterator<Ret> begin() const { return result_iterator<Ret>(table); }
258 size_t end() const { return count(); }
259
260 size_t count() const { return table->numvals; }
261
262 tuple_descriptor get_tuple_descriptor() const { return table->tupdesc; }
263 };
264
265 struct options {
266 explicit options() : read_only_(false), count_(0) {}
267 options(bool read_only) : read_only_(read_only), count_(0) {}
268 options(int count) : read_only_(false), count_(count) {}
269 options(bool read_only, int count) : read_only_(read_only), count_(count) {}
270
271 bool read_only() const { return read_only_; }
272 int count() const { return count_; }
273
274 private:
275 bool read_only_;
276 int count_;
277 };
278
293 template <typename Ret, convertible_into_nullable_datum_and_has_a_type... Args>
295 return this->query<Ret>(query, options(), std::forward<Args>(args)...);
296 }
297
310 template <typename Ret, convertible_into_nullable_datum_and_has_a_type... Args>
312 if (executors.top() != this) {
313 throw std::runtime_error("not a current SPI executor");
314 }
315 constexpr size_t nargs = sizeof...(Args);
316 std::array<::Oid, nargs> types = {type_traits<Args>(args...).type_for().oid...};
317 std::array<::Datum, nargs> datums = {into_nullable_datum(args)...};
318 std::array<const char, nargs> nulls = {into_nullable_datum(args).is_null() ? 'n' : ' ' ...};
319 auto rc = ffi_guard{::SPI_execute_with_args}(utils::to_cstring(query), nargs, types.data(),
320 datums.data(), nulls.data(), opts.read_only(),
321 opts.count());
322 if (rc > 0) {
323 // static_assert(std::random_access_iterator<result_iterator<Ret>>);
324 return results<Ret>(SPI_tuptable);
325 } else {
326 throw std::runtime_error("spi error");
327 }
328 }
329
331 spi_plan<Args...> plan(utils::convertible_to_cstring auto query) {
332 if (executors.top() != this) {
333 throw std::runtime_error("not a current SPI executor");
334 }
335 constexpr size_t nargs = sizeof...(Args);
336 std::array<::Oid, nargs> types = {type_traits<Args>().type_for().oid...};
337 return spi_plan<Args...>(
338 ffi_guard{::SPI_prepare}(utils::to_cstring(query), nargs, types.data()));
339 }
340
341 template <typename Ret, convertible_into_nullable_datum... Args>
342 results<Ret> query(spi_plan<Args...> &query, Args &&...args) {
343 return this->query<Ret, Args...>(query, options(), std::forward<Args>(args)...);
344 }
345
346 template <typename Ret, convertible_into_nullable_datum... Args>
347 results<Ret> query(spi_plan<Args...> &query, options &&opts, Args &&...args) {
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<::Datum, nargs> datums = {into_nullable_datum(args)...};
353 std::array<const char, nargs> nulls = {into_nullable_datum(args).is_null() ? 'n' : ' ' ...};
354 auto rc = ffi_guard{::SPI_execute_plan}(query, datums.data(), nulls.data(), opts.read_only(),
355 opts.count());
356 if (rc > 0) {
357 // static_assert(std::random_access_iterator<result_iterator<Ret>>);
358 return results<Ret>(SPI_tuptable);
359 } else {
360 throw std::runtime_error("spi error");
361 }
362 }
363
364 template <convertible_into_nullable_datum_and_has_a_type... Args>
365 uint64_t execute(std::string_view query, Args &&...args) {
366 return execute(query, options(), std::forward<Args>(args)...);
367 }
368
369 template <convertible_into_nullable_datum_and_has_a_type... Args>
370 uint64_t execute(std::string_view query, options &&opts, Args &&...args) {
371 if (executors.top() != this) {
372 throw std::runtime_error("not a current SPI executor");
373 }
374 constexpr size_t nargs = sizeof...(Args);
375 std::array<::Oid, nargs> types = {type_traits<Args>(args...).type_for().oid...};
376 std::array<::Datum, nargs> datums = {into_nullable_datum(args)...};
377 std::array<const char, nargs> nulls = {into_nullable_datum(args).is_null() ? 'n' : ' ' ...};
378 auto rc = ffi_guard{::SPI_execute_with_args}(query.data(), nargs, types.data(), datums.data(),
379 nulls.data(), opts.read_only(), opts.count());
380 if (rc >= 0) {
381 return SPI_processed;
382 } else {
383 throw std::runtime_error(cppgres::fmt::format("spi error"));
384 }
385 }
386
387private:
388 ::MemoryContext before_spi;
389 ::MemoryContext spi;
390
391protected:
392 static inline std::stack<spi_executor *> executors;
393 spi_executor(int flags) : before_spi(::CurrentMemoryContext) {
394 ffi_guard{::SPI_connect_ext}(flags);
395 spi = ::CurrentMemoryContext;
396 ::CurrentMemoryContext = before_spi;
397 executors.push(this);
398 }
399};
400
403
404 spi_nonatomic_executor() : spi_executor(SPI_OPT_NONATOMIC) {
405 auto atomic = cppgres::current_postgres_function::atomic();
406 if (atomic.has_value() && atomic.value()) {
407 throw std::runtime_error("must be called in a non-atomic context");
408 }
409 }
410
411 void commit(bool chain = false) {
412 if (executors.top() != this) {
413 throw std::runtime_error("not a current SPI executor");
414 }
415 ffi_guard(chain ? ::SPI_commit_and_chain : ::SPI_commit)();
416 }
417
418 void rollback(bool chain = false) {
419 if (executors.top() != this) {
420 throw std::runtime_error("not a current SPI executor");
421 }
422 ffi_guard(chain ? ::SPI_rollback_and_chain : ::SPI_rollback)();
423 }
424};
425
426} // namespace cppgres
Definition: executor.hpp:59
Definition: datum.hpp:160
Definition: type.hpp:237
Definition: cstring.hpp:21
Definition: datum.hpp:35
Definition: executor.hpp:56
Definition: guard.hpp:19
Heap tuple convenience wrapper.
Definition: heap_tuple.hpp:11
Definition: memory.hpp:61
Definition: datum.hpp:56
Definition: datum.hpp:17
Definition: memory.hpp:243
Definition: executor.hpp:265
Definition: executor.hpp:77
Definition: executor.hpp:217
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:311
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:294
Definition: executor.hpp:401
spi_executor()
Creates an SPI executor.
Definition: executor.hpp:71
Definition: executor.hpp:24
Definition: memory.hpp:111
Tuple descriptor operator.
Definition: record.hpp:18
Definition: type.hpp:41
Postgres type.
Definition: type.hpp:20
Definition: value.hpp:8