Cppgres
Build Postgres extensions in C++
Loading...
Searching...
No Matches
function.hpp
Go to the documentation of this file.
1
4#pragma once
5
6#include "datum.hpp"
7#include "guard.hpp"
8#include "imports.h"
9#include "record.hpp"
10#include "set.hpp"
11#include "types.hpp"
13#include "utils/utils.hpp"
14#include "value.hpp"
15
16#include <array>
17#include <complex>
18#include <iostream>
19#include <stack>
20#include <tuple>
21#include <typeinfo>
22
23namespace cppgres {
24
25template <typename T>
28
37template <typename Func>
39 requires { typename utils::function_traits::function_traits<Func>::argument_types; } &&
42 requires(Func f) {
43 {
44 std::apply(
45 f,
47 } -> convertible_into_nullable_datum_or_set_iterator_or_void;
48 };
49
51 function_call_info(::FunctionCallInfo info) : info_(info) {}
52
53 operator FunctionCallInfo() const { return info_; }
54
58 short nargs() const { return info_->nargs; }
59
64 auto args() const {
65 return std::views::iota(0, nargs()) | std::views::transform([this](int i) -> nullable_datum {
66 return nullable_datum(info_->args[i]);
67 });
68 }
69
73 auto arg_types() const {
74 return std::views::iota(0, nargs()) | std::views::transform([this](int i) -> type {
75 return {.oid = ffi_guard{::get_fn_expr_argtype}(info_->flinfo, i)};
76 });
77 }
78
83 auto arg_values() const {
84 return std::views::iota(0, nargs()) | std::views::transform([this](int i) -> value {
85 return value(nullable_datum(info_->args[i]),
86 {.oid = ffi_guard{::get_fn_expr_argtype}(info_->flinfo, i)});
87 });
88 }
89
93 oid called_function_oid() const { return info_->flinfo->fn_oid; }
94
98 type return_type() const { return {.oid = ffi_guard{::get_fn_expr_rettype}(info_->flinfo)}; }
99
100private:
101 ::FunctionCallInfo info_;
102};
103
105
106 static std::optional<bool> atomic() {
107 if (!calls.empty()) {
108 auto ctx = calls.top()->context;
109 if (ctx != nullptr && IsA(ctx, CallContext)) {
110 return reinterpret_cast<::CallContext *>(ctx)->atomic;
111 }
112 }
113
114 return std::nullopt;
115 }
116
117 static std::optional<function_call_info> call_info() {
118 if (!calls.empty()) {
119 return calls.top();
120 }
121
122 return std::nullopt;
123 }
124
125 template <datumable_function Func> friend struct postgres_function;
126
127private:
128 struct handle {
129 ~handle() { calls.pop(); }
130
131 friend struct current_postgres_function;
132
133 private:
134 handle() {}
135 };
136
137 static handle push(::FunctionCallInfo fcinfo) {
138 calls.push(fcinfo);
139 return handle{};
140 }
141 static void pop() { calls.pop(); }
142
143 static inline std::stack<::FunctionCallInfo> calls;
144};
145
156template <datumable_function Func> struct postgres_function {
157 Func func;
158
159 explicit postgres_function(Func f) : func(f) {}
160
161 // Use the function_traits to extract argument types.
163 using argument_types = typename traits::argument_types;
164 using return_type = utils::function_traits::invoke_result_from_tuple_t<Func, argument_types>;
165 static constexpr std::size_t arity = traits::arity;
166
170 auto operator()(FunctionCallInfo fc) -> ::Datum {
171
172 return exception_guard([&] {
173 // return type
174 auto rettype = type{.oid = ffi_guard{::get_fn_expr_rettype}(fc->flinfo)};
175 auto retset = fc->flinfo->fn_retset;
176
177 if constexpr (datumable_iterator<return_type>) {
178 // Check this before checking the type of `retset`
179 auto rsinfo = reinterpret_cast<::ReturnSetInfo *>(fc->resultinfo);
180 if (rsinfo == nullptr) {
181 report(ERROR, "caller is not expecting a set");
182 }
183 }
184
185 if (!OidIsValid(rettype.oid)) {
186 // TODO: not very efficient to look it up every time
187 syscache<Form_pg_proc, oid> cache(fc->flinfo->fn_oid);
188 rettype = type{.oid = (*cache).prorettype};
189 retset = (*cache).proretset;
190 }
191
192 if (retset) {
193 if constexpr (datumable_iterator<return_type>) {
194 using set_value_type = set_iterator_traits<return_type>::value_type;
195 if (!type_traits<set_value_type>().is(rettype)) {
196 report(ERROR, "unexpected set's return type, can't convert `%s` into `%.*s`",
197 rettype.name().data(), utils::type_name<set_value_type>().length(),
198 utils::type_name<set_value_type>().data());
199 }
200 } else {
201 report(ERROR,
202 "unexpected return type, set is expected, but `%.*s` does not conform to "
203 "`cppgres::datumable_iterator`",
204 utils::type_name<return_type>().length(), utils::type_name<return_type>().data());
205 }
206 } else if (!type_traits<return_type>().is(rettype)) {
207 report(ERROR, "unexpected return type, can't convert `%s` into `%.*s`",
208 rettype.name().data(), utils::type_name<return_type>().length(),
209 utils::type_name<return_type>().data());
210 }
211
212 // arguments
213 short accounted_for_args = 0;
214 auto t = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
215 return argument_types{([&]() -> utils::tuple_element_t<Is, argument_types> {
216 using ptyp = utils::tuple_element_t<Is, argument_types>;
217 auto typ = type{.oid = ffi_guard{::get_fn_expr_argtype}(fc->flinfo, Is)};
218 if (!OidIsValid(typ.oid)) {
219 // TODO: not very efficient to look it up every time
220 syscache<Form_pg_proc, oid> cache(fc->flinfo->fn_oid);
221 if ((*cache).proargtypes.dim1 > Is) {
222 typ = type{.oid = (*cache).proargtypes.values[Is]};
223 }
224 }
225 if (!type_traits<ptyp>().is(typ)) {
226 report(ERROR, "unexpected type in position %d, can't convert `%s` into `%.*s`", Is,
227 typ.name().data(), utils::type_name<ptyp>().length(),
228 utils::type_name<ptyp>().data());
229 }
230 accounted_for_args++;
231 return from_nullable_datum<ptyp>(nullable_datum(fc->args[Is]), typ.oid);
232 }())...};
233 }(std::make_index_sequence<utils::tuple_size_v<argument_types>>{});
234
235 if (arity != accounted_for_args) {
236 report(ERROR, "expected %d arguments, got %d instead", arity, accounted_for_args);
237 }
238
239 auto call_handle = current_postgres_function::push(fc);
240
241 if constexpr (datumable_iterator<return_type>) {
242 auto rsinfo = reinterpret_cast<::ReturnSetInfo *>(fc->resultinfo);
243 // TODO: For now, let's assume materialized model
244 using set_value_type = set_iterator_traits<return_type>::value_type;
245 if constexpr (std::same_as<set_value_type, record>) {
246
247 auto natts = rsinfo->expectedDesc == nullptr ? -1 : rsinfo->expectedDesc->natts;
248
249 rsinfo->returnMode = SFRM_Materialize;
250
251 memory_context_scope scope(memory_context(rsinfo->econtext->ecxt_per_query_memory));
252
253 ::Tuplestorestate *tupstore = ffi_guard{::tuplestore_begin_heap}(
254 (rsinfo->allowedModes & SFRM_Materialize_Random) == SFRM_Materialize_Random, false,
255 work_mem);
256 rsinfo->setResult = tupstore;
257
258 auto res = std::apply(func, t);
259
260 bool checked = false;
261 for (auto r : res) {
262 auto nargs = r.attributes();
263 if (!checked) {
264 if (rsinfo->expectedDesc != nullptr && nargs != natts) {
265 throw std::runtime_error(
266 cppgres::fmt::format("expected record with {} value{}, got {} instead", nargs,
267 nargs == 1 ? "" : "s", natts));
268 }
269 if (rsinfo->expectedDesc != nullptr &&
270 !r.get_tuple_descriptor().equal_types(
271 cppgres::tuple_descriptor(rsinfo->expectedDesc))) {
272 throw std::runtime_error("expected and returned records do not match");
273 }
274 checked = true;
275 }
276
277 ffi_guard{::tuplestore_puttuple}(tupstore, r);
278 }
279 fc->isnull = true;
280 return ::Datum(0);
281 } else {
282 constexpr auto nargs = utils::tuple_size_v<set_value_type>;
283
284 auto natts = rsinfo->expectedDesc->natts;
285
286 if (nargs != natts) {
287 throw std::runtime_error(cppgres::fmt::format("expected set with {} value{}, got {} instead",
288 nargs, nargs == 1 ? "" : "s", natts));
289 }
290
291 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
292 (([&] {
293 auto oid = ffi_guard{::SPI_gettypeid}(rsinfo->expectedDesc, Is + 1);
294 auto t = type{.oid = oid};
295 using typ = utils::tuple_element_t<Is, set_value_type>;
296 if (!type_traits<typ>().is(t)) {
297 throw std::invalid_argument(
298 cppgres::fmt::format("invalid type in record's position {} ({}), got OID {}", Is,
299 utils::type_name<typ>(), oid));
300 }
301 }()),
302 ...);
303 }(std::make_index_sequence<nargs>{});
304
305 rsinfo->returnMode = SFRM_Materialize;
306
307 memory_context_scope scope(memory_context(rsinfo->econtext->ecxt_per_query_memory));
308
309 ::Tuplestorestate *tupstore = ffi_guard{::tuplestore_begin_heap}(
310 (rsinfo->allowedModes & SFRM_Materialize_Random) == SFRM_Materialize_Random, false,
311 work_mem);
312 rsinfo->setResult = tupstore;
313
314 auto result = std::apply(func, t);
315
316 for (auto it : result) {
317 CHECK_FOR_INTERRUPTS();
318 std::array<::Datum, nargs> values = std::apply(
319 [](auto &&...elems) -> std::array<::Datum, sizeof...(elems)> {
320 return {into_nullable_datum(elems)...};
321 },
322 utils::tie(it));
323 std::array<bool, nargs> isnull = std::apply(
324 [](auto &&...elems) -> std::array<bool, sizeof...(elems)> {
325 return {into_nullable_datum(elems).is_null()...};
326 },
327 utils::tie(it));
328 ffi_guard{::tuplestore_putvalues}(tupstore, rsinfo->expectedDesc, values.data(),
329 isnull.data());
330 }
331
332 fc->isnull = true;
333 return ::Datum(0);
334 }
335 } else {
336 if constexpr (std::same_as<return_type, void>) {
337 std::apply(func, t);
338 return ::Datum(0);
339 } else {
340 auto result = std::apply(func, t);
341 nullable_datum nd = into_nullable_datum(result);
342 if (nd.is_null()) {
343 fc->isnull = true;
344 return ::Datum(0);
345 }
346 return nd.operator const ::Datum &();
347 }
348 }
349 })();
350 __builtin_unreachable();
351 }
352};
353
354} // namespace cppgres
Function that operates on values of Postgres types.
Definition: function.hpp:38
Definition: set.hpp:15
#define postgres_function(name, function)
Export a C++ function as a Postgres function.
Definition: cppgres.hpp:98
Definition: datum.hpp:201
Definition: function.hpp:104
Wraps a C++ function to catch exceptions and report them as Postgres errors.
Definition: guard.hpp:64
Definition: guard.hpp:19
Definition: function.hpp:50
auto arg_types() const
argument types
Definition: function.hpp:73
auto args() const
passed arguments
Definition: function.hpp:64
auto arg_values() const
typed passed argument
Definition: function.hpp:83
type return_type() const
return type
Definition: function.hpp:98
short nargs() const
number of arguments actually passed
Definition: function.hpp:58
oid called_function_oid() const
called function OID
Definition: function.hpp:93
Definition: memory.hpp:193
Definition: memory.hpp:61
Definition: datum.hpp:56
Definition: datum.hpp:17
Postgres function implemented in C++.
Definition: function.hpp:156
auto operator()(FunctionCallInfo fc) -> ::Datum
Definition: function.hpp:170
Definition: syscache.hpp:27
Tuple descriptor operator.
Definition: record.hpp:18
Definition: type.hpp:41
Postgres type.
Definition: type.hpp:20
Definition: function_traits.hpp:37
Definition: value.hpp:8