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 private:
213 };
214
215 template <typename Ret> struct results {
216 ::SPITupleTable *table;
217
218 results(::SPITupleTable *table) : table(table) {
219 auto natts = table->tupdesc->natts;
220 if constexpr (a_vector<Ret>) {
221 for (int i = 0; i < natts; i++) {
222 auto oid = ffi_guard{::SPI_gettypeid}(table->tupdesc, i + 1);
223 auto t = type{.oid = oid};
225 throw std::invalid_argument(
226 cppgres::fmt::format("invalid return type in position {} ({}), got OID {}", i,
227 utils::type_name<typename Ret::value_type>(), oid));
228 }
229 }
230 } else {
231 if (natts != utils::tuple_size_v<Ret>) {
232 if (natts == 1 && convertible_from_datum<Ret>) {
233 // okay, this is just a type we can convert
234 } else {
235 throw std::runtime_error(cppgres::fmt::format("expected {} return values, got {}",
236 utils::tuple_size_v<Ret>, natts));
237 }
238 } else {
239 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
240 (([&] {
241 auto oid = ffi_guard{::SPI_gettypeid}(table->tupdesc, Is + 1);
242 auto t = type{.oid = oid};
243 if (!type_traits<utils::tuple_element_t<Is, Ret>>().is(t)) {
244 throw std::invalid_argument(cppgres::fmt::format(
245 "invalid return type in position {} ({}), got OID {}", Is,
246 utils::type_name<utils::tuple_element_t<Is, Ret>>(), oid));
247 }
248 }()),
249 ...);
250 }(std::make_index_sequence<utils::tuple_size_v<Ret>>{});
251 }
252 }
253 }
254
255 result_iterator<Ret> begin() const { return result_iterator<Ret>(table); }
256 size_t end() const { return count(); }
257
258 size_t count() const { return table->numvals; }
259
260 tuple_descriptor get_tuple_descriptor() const { return table->tupdesc; }
261 };
262
263 struct options {
264 explicit options() : read_only_(false), count_(0) {}
265 options(bool read_only) : read_only_(read_only), count_(0) {}
266 options(int count) : read_only_(false), count_(count) {}
267 options(bool read_only, int count) : read_only_(read_only), count_(count) {}
268
269 bool read_only() const { return read_only_; }
270 int count() const { return count_; }
271
272 private:
273 bool read_only_;
274 int count_;
275 };
276
291 template <typename Ret, convertible_into_nullable_datum_and_has_a_type... Args>
293 return this->query<Ret>(query, options(), std::forward<Args>(args)...);
294 }
295
308 template <typename Ret, convertible_into_nullable_datum_and_has_a_type... Args>
310 if (executors.top() != this) {
311 throw std::runtime_error("not a current SPI executor");
312 }
313 constexpr size_t nargs = sizeof...(Args);
314 std::array<::Oid, nargs> types = {type_traits<Args>(args...).type_for().oid...};
315 std::array<::Datum, nargs> datums = {into_nullable_datum(args)...};
316 std::array<const char, nargs> nulls = {into_nullable_datum(args).is_null() ? 'n' : ' ' ...};
317 auto rc = ffi_guard{::SPI_execute_with_args}(utils::to_cstring(query), nargs, types.data(),
318 datums.data(), nulls.data(), opts.read_only(),
319 opts.count());
320 if (rc > 0) {
321 // static_assert(std::random_access_iterator<result_iterator<Ret>>);
322 return results<Ret>(SPI_tuptable);
323 } else {
324 throw std::runtime_error("spi error");
325 }
326 }
327
329 spi_plan<Args...> plan(utils::convertible_to_cstring auto query) {
330 if (executors.top() != this) {
331 throw std::runtime_error("not a current SPI executor");
332 }
333 constexpr size_t nargs = sizeof...(Args);
334 std::array<::Oid, nargs> types = {type_traits<Args>().type_for().oid...};
335 return spi_plan<Args...>(
336 ffi_guard{::SPI_prepare}(utils::to_cstring(query), nargs, types.data()));
337 }
338
339 template <typename Ret, convertible_into_nullable_datum... Args>
340 results<Ret> query(spi_plan<Args...> &query, Args &&...args) {
341 return this->query<Ret, Args...>(query, options(), std::forward<Args>(args)...);
342 }
343
344 template <typename Ret, convertible_into_nullable_datum... Args>
345 results<Ret> query(spi_plan<Args...> &query, options &&opts, Args &&...args) {
346 if (executors.top() != this) {
347 throw std::runtime_error("not a current SPI executor");
348 }
349 constexpr size_t nargs = sizeof...(Args);
350 std::array<::Datum, nargs> datums = {into_nullable_datum(args)...};
351 std::array<const char, nargs> nulls = {into_nullable_datum(args).is_null() ? 'n' : ' ' ...};
352 auto rc = ffi_guard{::SPI_execute_plan}(query, datums.data(), nulls.data(), opts.read_only(),
353 opts.count());
354 if (rc > 0) {
355 // static_assert(std::random_access_iterator<result_iterator<Ret>>);
356 return results<Ret>(SPI_tuptable);
357 } else {
358 throw std::runtime_error("spi error");
359 }
360 }
361
362 template <convertible_into_nullable_datum_and_has_a_type... Args>
363 uint64_t execute(std::string_view query, Args &&...args) {
364 return execute(query, options(), std::forward<Args>(args)...);
365 }
366
367 template <convertible_into_nullable_datum_and_has_a_type... Args>
368 uint64_t execute(std::string_view query, options &&opts, Args &&...args) {
369 if (executors.top() != this) {
370 throw std::runtime_error("not a current SPI executor");
371 }
372 constexpr size_t nargs = sizeof...(Args);
373 std::array<::Oid, nargs> types = {type_traits<Args>(args...).type_for().oid...};
374 std::array<::Datum, nargs> datums = {into_nullable_datum(args)...};
375 std::array<const char, nargs> nulls = {into_nullable_datum(args).is_null() ? 'n' : ' ' ...};
376 auto rc = ffi_guard{::SPI_execute_with_args}(query.data(), nargs, types.data(), datums.data(),
377 nulls.data(), opts.read_only(), opts.count());
378 if (rc >= 0) {
379 return SPI_processed;
380 } else {
381 throw std::runtime_error(cppgres::fmt::format("spi error"));
382 }
383 }
384
385private:
386 ::MemoryContext before_spi;
387 ::MemoryContext spi;
388
389protected:
390 static inline std::stack<spi_executor *> executors;
391 spi_executor(int flags) : before_spi(::CurrentMemoryContext) {
392 ffi_guard{::SPI_connect_ext}(flags);
393 spi = ::CurrentMemoryContext;
394 ::CurrentMemoryContext = before_spi;
395 executors.push(this);
396 }
397};
398
401
402 spi_nonatomic_executor() : spi_executor(SPI_OPT_NONATOMIC) {
403 auto atomic = cppgres::current_postgres_function::atomic();
404 if (atomic.has_value() && atomic.value()) {
405 throw std::runtime_error("must be called in a non-atomic context");
406 }
407 }
408
409 void commit(bool chain = false) {
410 if (executors.top() != this) {
411 throw std::runtime_error("not a current SPI executor");
412 }
413 ffi_guard(chain ? ::SPI_commit_and_chain : ::SPI_commit)();
414 }
415
416 void rollback(bool chain = false) {
417 if (executors.top() != this) {
418 throw std::runtime_error("not a current SPI executor");
419 }
420 ffi_guard(chain ? ::SPI_rollback_and_chain : ::SPI_rollback)();
421 }
422};
423
424} // namespace cppgres
Definition: executor.hpp:59
Definition: datum.hpp:138
Definition: type.hpp:237
Definition: cstring.hpp:21
Definition: datum.hpp:35
Definition: executor.hpp:56
Definition: guard.hpp:19
Definition: memory.hpp:61
Definition: datum.hpp:56
Definition: datum.hpp:17
Definition: memory.hpp:243
Definition: executor.hpp:263
Definition: executor.hpp:77
Definition: executor.hpp:215
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:309
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:292
Definition: executor.hpp:399
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