11#include "utils/cstring.hpp"
27 spi_plan(
spi_plan &&p) : kept(p.kept), plan(p.plan), ctx(std::move(p.ctx)) { p.kept =
false; }
29 operator ::SPIPlanPtr() {
30 if (ctx.resets() > 0) {
60 spi_plan(::SPIPlanPtr plan)
61 : kept(false), plan(plan), ctx(tracking_memory_context(memory_context::for_pointer(plan))) {}
65 tracking_memory_context<memory_context> ctx;
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>>;
79enum class spi_opt :
int { none = 0, nonatomic = SPI_OPT_NONATOMIC };
81constexpr spi_opt operator|(spi_opt lhs, spi_opt rhs) {
82 return static_cast<spi_opt
>(
static_cast<int>(lhs) |
static_cast<int>(rhs));
109 using iterator_category = std::random_access_iterator_tag;
110 using value_type = T;
111 using difference_type = std::ptrdiff_t;
113 ::SPITupleTable *tuptable;
115 mutable std::vector<std::optional<T>> tuples;
120 : tuptable(tuptable), index(0),
121 tuples(std::vector<std::optional<T>>(tuptable->numvals, std::nullopt)) {
122 tuples.reserve(tuptable->numvals);
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);
130 bool operator==(
size_t end_index)
const {
return index == end_index; }
131 bool operator!=(
size_t end_index)
const {
return index != end_index; }
133 constexpr T &operator*()
const {
return this->operator[](
static_cast<difference_type
>(index)); }
155 constexpr result_iterator operator+(
const difference_type n)
const noexcept {
164 constexpr result_iterator operator-(difference_type n)
const noexcept {
173 constexpr difference_type operator-(
const result_iterator &other)
const noexcept {
174 return index - other.index;
177 T &operator[](difference_type n)
const {
178 if (tuples.at(n).has_value()) {
179 return tuples.at(n).value();
186 ffi_guard{::SPI_getbinval}(tuptable->vals[n], tuptable->tupdesc, 1, &isnull);
187 ::NullableDatum
datum = {.value =
value, .isnull = isnull};
189 ffi_guard{::SPI_gettypeid}(tuptable->tupdesc, 1),
191 tuples.at(n).emplace(ret);
192 return tuples.at(n).value();
193 }
else if (tuptable->tupdesc->natts == 1) {
196 ffi_guard{::SPI_getbinval}(tuptable->vals[n], tuptable->tupdesc, 1, &isnull);
197 ::NullableDatum
datum = {.value =
value, .isnull = isnull};
199 ffi_guard{::SPI_gettypeid}(tuptable->tupdesc, 1),
201 tuples.at(n).emplace(ret);
202 return tuples.at(n).value();
207 for (
int i = 0; i < tuptable->tupdesc->natts; i++) {
210 ffi_guard{::SPI_getbinval}(tuptable->vals[n], tuptable->tupdesc, i + 1, &isnull);
211 ::NullableDatum
datum = {.value =
value, .isnull = isnull};
213 ret.emplace_back(from_nullable_datum<typename T::value_type>(
214 nd,
ffi_guard{::SPI_gettypeid}(tuptable->tupdesc, i + 1),
217 tuples.at(n).emplace(ret);
219 auto ret = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
223 ffi_guard{::SPI_getbinval}(tuptable->vals[n], tuptable->tupdesc, Is + 1, &isnull);
224 ::NullableDatum
datum = {.value =
value, .isnull = isnull};
226 return from_nullable_datum<utils::tuple_element_t<Is, T>>(
227 nd,
ffi_guard{::SPI_gettypeid}(tuptable->tupdesc, Is + 1),
230 }(std::make_index_sequence<utils::tuple_size_v<T>>{});
231 tuples.at(n).emplace(ret);
233 return tuples.at(n).value();
236 constexpr bool operator==(
const result_iterator &other)
const noexcept {
237 return tuptable == other.tuptable && index == other.index;
239 constexpr bool operator!=(
const result_iterator &other)
const noexcept {
240 return !(tuptable == other.tuptable && index == other.index);
242 constexpr bool operator<(
const result_iterator &other)
const noexcept {
243 return index < other.index;
245 constexpr bool operator>(
const result_iterator &other)
const noexcept {
246 return index > other.index;
248 constexpr bool operator<=(
const result_iterator &other)
const noexcept {
249 return index <= other.index;
251 constexpr bool operator>=(
const result_iterator &other)
const noexcept {
252 return index >= other.index;
255 operator const heap_tuple()
const {
return tuptable->vals[index]; }
261 ::SPITupleTable *table;
263 results(::SPITupleTable *table) : table(table) {
264 auto natts = table->tupdesc->natts;
266 for (
int i = 0; i < natts; i++) {
267 auto oid =
ffi_guard{::SPI_gettypeid}(table->tupdesc, i + 1);
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));
276 if (natts != utils::tuple_size_v<Ret>) {
280 throw std::runtime_error(cppgres::fmt::format(
"expected {} return values, got {}",
281 utils::tuple_size_v<Ret>, natts));
284 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
286 auto oid =
ffi_guard{::SPI_gettypeid}(table->tupdesc, Is + 1);
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));
295 }(std::make_index_sequence<utils::tuple_size_v<Ret>>{});
301 size_t end()
const {
return count(); }
303 size_t count()
const {
return table->numvals; }
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) {}
314 bool read_only()
const {
return read_only_; }
315 int count()
const {
return count_; }
338 return this->query<Ret>(
query,
options(), std::forward<Args>(args)...);
355 if (executors.top() !=
this) {
356 throw std::runtime_error(
"not a current SPI executor");
358 constexpr size_t nargs =
sizeof...(Args);
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(),
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
375 throw std::runtime_error(
376 fmt::format(
"spi error in `{}`", std::string_view(utils::to_cstring(
query))));
382 if (executors.top() !=
this) {
383 throw std::runtime_error(
"not a current SPI executor");
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()));
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");
407 ::SPIPrepareOptions options = opts;
408 return spi_plan<>(ffi_guard{::SPI_prepare_extended}(utils::to_cstring(
query), &options));
418 if (executors.empty()) {
419 throw std::runtime_error(
"no SPI executor in scope");
421 return *executors.top();
426 return this->query<Ret, Args...>(
query, options(), std::forward<Args>(args)...);
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");
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(),
441 return results<Ret>(SPI_tuptable);
443 throw std::runtime_error(
"spi error in a query plan");
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)...);
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");
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(),
466 return SPI_processed;
468 throw std::runtime_error(cppgres::fmt::format(
"spi error in `{}`",
query));
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))...};
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()
485 : static_cast<const ::Datum &>(static_cast<const datum &>(values[i]));
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' :
' ';
500 ::MemoryContext before_spi;
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);
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");
523 void commit(
bool chain =
false) {
524 if (executors.top() !=
this) {
525 throw std::runtime_error(
"not a current SPI executor");
527 ffi_guard(chain ? ::SPI_commit_and_chain : ::SPI_commit)();
530 void rollback(
bool chain =
false) {
531 if (executors.top() !=
this) {
532 throw std::runtime_error(
"not a current SPI executor");
534 ffi_guard(chain ? ::SPI_rollback_and_chain : ::SPI_rollback)();
Definition: executor.hpp:71
Definition: record.hpp:451
Definition: datum.hpp:176
Definition: datum.hpp:205
Definition: executor.hpp:21
Definition: datum.hpp:213
Definition: cstring.hpp:21
spi_opt
SPI connection options.
Definition: executor.hpp:79
Definition: executor.hpp:68
Heap tuple convenience wrapper.
Definition: heap_tuple.hpp:11
Definition: memory.hpp:139
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
Postgres type.
Definition: type.hpp:22