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 "syscache.hpp"
12#include "types.hpp"
14#include "utils/utils.hpp"
15#include "value.hpp"
16
17#include <array>
18#include <complex>
19#include <iostream>
20#include <stack>
21#include <tuple>
22#include <typeinfo>
23
24namespace cppgres {
25
26template <typename T>
29
38template <typename Func>
40 requires { typename utils::function_traits::function_traits<Func>::argument_types; } &&
44 { std::apply(f, args) } -> convertible_into_nullable_datum_or_set_iterator_or_void;
45 };
46
48 function_call_info(::FunctionCallInfo info) : info_(info) {}
49
50 operator FunctionCallInfo() const { return info_; }
51
55 short nargs() const { return info_->nargs; }
56
61 auto args() const {
62 return std::views::iota(0, nargs()) | std::views::transform([this](int i) -> nullable_datum {
63 return nullable_datum(info_->args[i]);
64 });
65 }
66
70 auto arg_types() const {
71 return std::views::iota(0, nargs()) | std::views::transform([this](int i) -> type {
72 return {.oid = ffi_guard{::get_fn_expr_argtype}(info_->flinfo, i)};
73 });
74 }
75
80 auto arg_values() const {
81 return std::views::iota(0, nargs()) | std::views::transform([this](int i) -> value {
82 return value(nullable_datum(info_->args[i]),
83 {.oid = ffi_guard{::get_fn_expr_argtype}(info_->flinfo, i)});
84 });
85 }
86
90 oid called_function_oid() const { return info_->flinfo->fn_oid; }
91
95 type return_type() const { return {.oid = ffi_guard{::get_fn_expr_rettype}(info_->flinfo)}; }
96
97 oid collation() const { return info_->fncollation; }
98
99private:
100 ::FunctionCallInfo info_;
101};
102
104
105 static std::optional<bool> atomic() {
106 if (!calls.empty()) {
107 auto ctx = calls.top()->context;
108 if (ctx != nullptr && IsA(ctx, CallContext)) {
109 return reinterpret_cast<::CallContext *>(ctx)->atomic;
110 }
111 }
112
113 return std::nullopt;
114 }
115
116 static std::optional<function_call_info> call_info() {
117 if (!calls.empty()) {
118 return calls.top();
119 }
120
121 return std::nullopt;
122 }
123
124 template <datumable_function Func> friend struct postgres_function;
125
126private:
127 struct handle {
128 ~handle() { calls.pop(); }
129
130 friend struct current_postgres_function;
131
132 private:
133 handle() {}
134 };
135
136 static handle push(::FunctionCallInfo fcinfo) {
137 calls.push(fcinfo);
138 return handle{};
139 }
140 static void pop() { calls.pop(); }
141
142 static inline std::stack<::FunctionCallInfo> calls;
143};
144
155template <datumable_function Func> struct postgres_function {
156 Func func;
157
158 explicit postgres_function(Func f) : func(f) {}
159
160 // Use the function_traits to extract argument types.
162 using argument_types = typename traits::argument_types;
163 using return_type = utils::function_traits::invoke_result_from_tuple_t<Func, argument_types>;
164 static constexpr std::size_t arity = traits::arity;
165
169 auto operator()(FunctionCallInfo fc) -> ::Datum {
170
171 return exception_guard([&] {
172 // return type
173 auto rettype = type{.oid = ffi_guard{::get_fn_expr_rettype}(fc->flinfo)};
174 auto retset = fc->flinfo->fn_retset;
175
176 if constexpr (datumable_iterator<return_type>) {
177 // Check this before checking the type of `retset`
178 auto rsinfo = reinterpret_cast<::ReturnSetInfo *>(fc->resultinfo);
179 if (rsinfo == nullptr) {
180 report(ERROR, "caller is not expecting a set");
181 }
182 }
183
184 if (!OidIsValid(rettype.oid)) {
185 // TODO: not very efficient to look it up every time
186 syscache<Form_pg_proc, oid> cache(fc->flinfo->fn_oid);
187 rettype = type{.oid = (*cache).prorettype};
188 retset = (*cache).proretset;
189 }
190
191 if (retset) {
192 if constexpr (datumable_iterator<return_type>) {
193 using set_value_type = set_iterator_traits<return_type>::value_type;
194 if (!type_traits<set_value_type>().is(rettype)) {
195 report(ERROR, "unexpected set's return type, can't convert `%s` into `%.*s`",
196 rettype.name().data(), utils::type_name<set_value_type>().length(),
197 utils::type_name<set_value_type>().data());
198 }
199 } else {
200 report(ERROR,
201 "unexpected return type, set is expected, but `%.*s` does not conform to "
202 "`cppgres::datumable_iterator`",
203 utils::type_name<return_type>().length(), utils::type_name<return_type>().data());
204 }
205 } else if (!type_traits<return_type>().is(rettype)) {
206 report(ERROR, "unexpected return type, can't convert `%s` into `%.*s`",
207 rettype.name().data(), utils::type_name<return_type>().length(),
208 utils::type_name<return_type>().data());
209 }
210
211 // arguments
212 if (fc->nargs < arity) {
213 report(ERROR, "expected %d arguments, got %d instead", arity, fc->nargs);
214 }
215 short accounted_for_args = 0;
216 auto t = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
217 return argument_types{([&]() -> utils::tuple_element_t<Is, argument_types> {
218 using ptyp = utils::tuple_element_t<Is, argument_types>;
219 auto typ = type{.oid = ffi_guard{::get_fn_expr_argtype}(fc->flinfo, Is)};
220 if (!OidIsValid(typ.oid)) {
221 // TODO: not very efficient to look it up every time
222 syscache<Form_pg_proc, oid> cache(fc->flinfo->fn_oid);
223 if ((*cache).proargtypes.dim1 > Is) {
224 typ = type{.oid = (*cache).proargtypes.values[Is]};
225 }
226 }
227 if (!type_traits<ptyp>().is(typ)) {
228 report(ERROR, "unexpected type in position %d, can't convert `%s` into `%.*s`", Is,
229 typ.name().data(), utils::type_name<ptyp>().length(),
230 utils::type_name<ptyp>().data());
231 }
232 accounted_for_args++;
233 return from_nullable_datum<ptyp>(nullable_datum(fc->args[Is]), typ.oid);
234 }())...};
235 }(std::make_index_sequence<utils::tuple_size_v<argument_types>>{});
236
237 if (arity != accounted_for_args) {
238 report(ERROR, "expected %d arguments, got %d instead", arity, accounted_for_args);
239 }
240
241 auto call_handle = current_postgres_function::push(fc);
242
243 if constexpr (datumable_iterator<return_type>) {
244 auto rsinfo = reinterpret_cast<::ReturnSetInfo *>(fc->resultinfo);
245 // TODO: For now, let's assume materialized model
246 using set_value_type = set_iterator_traits<return_type>::value_type;
247 if constexpr (std::same_as<set_value_type, record>) {
248
249 auto natts = rsinfo->expectedDesc == nullptr ? -1 : rsinfo->expectedDesc->natts;
250
251 rsinfo->returnMode = SFRM_Materialize;
252
253 memory_context_scope scope(memory_context(rsinfo->econtext->ecxt_per_query_memory));
254
255 ::Tuplestorestate *tupstore = ffi_guard{::tuplestore_begin_heap}(
256 (rsinfo->allowedModes & SFRM_Materialize_Random) == SFRM_Materialize_Random, false,
257 work_mem);
258 rsinfo->setResult = tupstore;
259
260 auto res = std::apply(func, t);
261
262 bool checked = false;
263 for (auto r : res) {
264 auto nargs = r.attributes();
265 if (!checked) {
266 if (rsinfo->expectedDesc != nullptr && nargs != natts) {
267 throw std::runtime_error(
268 cppgres::fmt::format("expected record with {} value{}, got {} instead", nargs,
269 nargs == 1 ? "" : "s", natts));
270 }
271 if (rsinfo->expectedDesc != nullptr &&
272 !r.get_tuple_descriptor().equal_types(
273 cppgres::tuple_descriptor(rsinfo->expectedDesc))) {
274 throw std::runtime_error("expected and returned records do not match");
275 }
276 checked = true;
277 }
278
279 ffi_guard{::tuplestore_puttuple}(tupstore, r);
280 }
281 fc->isnull = true;
282 return ::Datum(0);
283 } else {
284 constexpr auto nargs = utils::tuple_size_v<set_value_type>;
285
286 auto natts = rsinfo->expectedDesc->natts;
287
288 if (nargs != natts) {
289 throw std::runtime_error(cppgres::fmt::format("expected set with {} value{}, got {} instead",
290 nargs, nargs == 1 ? "" : "s", natts));
291 }
292
293 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
294 (([&] {
295 auto oid = ffi_guard{::SPI_gettypeid}(rsinfo->expectedDesc, Is + 1);
296 auto t = type{.oid = oid};
297 using typ = utils::tuple_element_t<Is, set_value_type>;
298 if (!type_traits<typ>().is(t)) {
299 throw std::invalid_argument(
300 cppgres::fmt::format("invalid type in record's position {} ({}), got OID {}", Is,
301 utils::type_name<typ>(), oid));
302 }
303 }()),
304 ...);
305 }(std::make_index_sequence<nargs>{});
306
307 rsinfo->returnMode = SFRM_Materialize;
308
309 memory_context_scope scope(memory_context(rsinfo->econtext->ecxt_per_query_memory));
310
311 ::Tuplestorestate *tupstore = ffi_guard{::tuplestore_begin_heap}(
312 (rsinfo->allowedModes & SFRM_Materialize_Random) == SFRM_Materialize_Random, false,
313 work_mem);
314 rsinfo->setResult = tupstore;
315
316 auto result = std::apply(func, t);
317
318 for (auto it : result) {
319 CHECK_FOR_INTERRUPTS();
320 std::array<::Datum, nargs> values = std::apply(
321 [](auto &&...elems) -> std::array<::Datum, sizeof...(elems)> {
322 return {into_nullable_datum(elems)...};
323 },
324 utils::tie(it));
325 std::array<bool, nargs> isnull = std::apply(
326 [](auto &&...elems) -> std::array<bool, sizeof...(elems)> {
327 return {into_nullable_datum(elems).is_null()...};
328 },
329 utils::tie(it));
330 ffi_guard{::tuplestore_putvalues}(tupstore, rsinfo->expectedDesc, values.data(),
331 isnull.data());
332 }
333
334 fc->isnull = true;
335 return ::Datum(0);
336 }
337 } else {
338 if constexpr (std::same_as<return_type, void>) {
339 std::apply(func, t);
340 return ::Datum(0);
341 } else {
342 auto result = std::apply(func, t);
343 nullable_datum nd = into_nullable_datum(result);
344 if (nd.is_null()) {
345 fc->isnull = true;
346 return ::Datum(0);
347 }
348 return nd.operator const ::Datum &();
349 }
350 }
351 })();
352 __builtin_unreachable();
353 }
354};
355
356template <has_type_traits ret_type, has_type_traits... arg_types> struct function {
357
358 function() = delete;
359
360 function(const char *schema, const char *name)
361 : function([schema, name]() -> oid {
362 return alloc_set_memory_context()([&schema, &name]() {
363 ::List *fname = list_make2(::makeString(const_cast<char *>(schema)),
364 ::makeString(const_cast<char *>(name)));
365 std::array<::Oid, sizeof...(arg_types)> argtypes = {
366 type_traits<arg_types>().type_for().oid...};
367 return ffi_guard{::LookupFuncName}(fname, static_cast<int>(sizeof...(arg_types)),
368 argtypes.data(), false);
369 });
370 }()) {}
371 function(std::string &schema, std::string &name) : function(schema.c_str(), name.c_str()) {}
372 explicit function(const char *name)
373 : function([name]() -> oid {
374 return alloc_set_memory_context()([&name]() {
375 ::List *fname = list_make1(::makeString(const_cast<char *>(name)));
376 std::array<::Oid, sizeof...(arg_types)> argtypes = {
377 type_traits<arg_types>().type_for().oid...};
378 return ffi_guard{::LookupFuncName}(fname, static_cast<int>(sizeof...(arg_types)),
379 argtypes.data(), false);
380 });
381 }()) {}
382 function(std::string &name) : function(name.c_str()) {}
383 function(oid oid) : oid_(oid) {
384 syscache<Form_pg_proc, decltype(oid)> p(oid_);
385 // Check arguments
386 auto &argtypes = (*p).proargtypes;
387 std::array<type, sizeof...(arg_types)> types = {type_traits<arg_types>().type_for()...};
388 if (argtypes.dim1 != sizeof...(arg_types)) {
389 throw std::runtime_error(cppgres::fmt::format("expected {} argument{}, got {} instead",
390 sizeof...(arg_types),
391 sizeof...(arg_types) == 1 ? "" : "s",
392 argtypes.dim1));
393 }
394 for (int i = 0; i < argtypes.dim1; i++) {
395 cppgres::oid arg(argtypes.values[i]);
396 if (types[i] != type{UNKNOWNOID} /* FIXME: figure out how to avoid this special case */ &&
397 arg != types[i].oid) {
398 throw std::runtime_error(cppgres::fmt::format("expected type {} for argument {}, got {}",
399 types[i].name(), i, type{.oid = arg}.name()));
400 }
401 }
402 // Check return type
403 rettype_ = (*p).prorettype;
404 if (type_traits<ret_type>().type_for().oid !=
405 UNKNOWNOID /* FIXME: figure out how to avoid this special case */
406 && rettype_ != type_traits<ret_type>().type_for().oid) {
407 throw std::runtime_error(cppgres::fmt::format("expected return type {}, got {}",
408 type_traits<ret_type>().type_for().name(),
409 type{.oid = rettype_}.name()));
410 }
411 strict_ = (*p).proisstrict;
412 }
413
414 using self = function<ret_type, arg_types...>;
415 template <typename... Args> static constexpr bool convertible_args() {
416 if constexpr (sizeof...(Args) != sizeof...(arg_types)) {
417 return false;
418 } else {
419 return []<std::size_t... I>(std::index_sequence<I...>) {
420 return (std::convertible_to<std::decay_t<Args>,
421 std::tuple_element_t<I, std::tuple<arg_types...>>> &&
422 ...);
423 }(std::make_index_sequence<sizeof...(Args)>{});
424 }
425 }
426 ret_type operator()(auto... args) requires(self::convertible_args<decltype(args)...>())
427 {
428 bool any_nulls = false;
429 auto optval = []<std::size_t I>(auto arg) -> ::Datum {
430 using nth_type = std::tuple_element_t<I, std::tuple<arg_types...>>;
431 if constexpr (std::same_as<std::nullopt_t, decltype(arg)>) {
432 return datum(0);
433 } else if constexpr (!utils::is_optional<decltype(arg)>) {
435 } else {
436 return arg.has_value() ? datum_conversion<nth_type>().into_datum(arg.value()) : datum(0);
437 }
438 return datum(0);
439 };
440 auto isnull = [&any_nulls](auto arg) {
441 if constexpr (std::same_as<std::nullopt_t, decltype(arg)>) {
442 any_nulls = true;
443 return true;
444 } else if constexpr (!utils::is_optional<decltype(arg)>) {
445 return false;
446 } else {
447 any_nulls = any_nulls || !arg.has_value();
448 return !arg.has_value();
449 }
450 return false;
451 };
452 return [&]<std::size_t... I>(std::index_sequence<I...>) -> ret_type {
453 LOCAL_FCINFO(fcinfo, sizeof...(args));
454 ::FmgrInfo flinfo;
455
456 ffi_guard{::fmgr_info}(oid_, &flinfo);
457
458 InitFunctionCallInfoData(*fcinfo, &flinfo, sizeof...(args), InvalidOid, nullptr, nullptr);
459
460 ((fcinfo->args[I].value = optval.template operator()<I>(args)), ...);
461 ((fcinfo->args[I].isnull = isnull(args)), ...);
462
463 if (any_nulls && strict_) {
464 if constexpr (utils::is_optional<ret_type>) {
465 return std::nullopt;
466 } else {
467 throw null_datum_exception();
468 }
469 }
470
471 nullable_datum result(ffi_guard{[&fcinfo]() { return FunctionCallInvoke(fcinfo); }}());
472 if (fcinfo->isnull) {
473 result = nullable_datum();
474 }
475
476 return datum_conversion<ret_type>().from_nullable_datum(result, rettype_);
477 }(std::make_index_sequence<sizeof...(args)>{});
478 }
479
480 const oid &function_oid() const { return oid_; }
481
482private:
483 oid oid_;
484 oid rettype_;
485 bool strict_;
486};
487
488template <has_type_traits ret, has_type_traits... Args>
489struct datum_conversion<function<ret, Args...>> : default_datum_conversion<function<ret, Args...>> {
490 static function<ret, Args...> from_datum(const datum &d, oid, std::optional<memory_context> ctx) {
491 return {oid(d)};
492 }
493
494 static datum into_datum(const function<ret, Args...> &t) {
495 return datum_conversion<oid>::into_datum(t.function_oid());
496 }
497};
498
499template <has_type_traits ret, has_type_traits... Args> struct type_traits<function<ret, Args...>> {
500 static bool is(const type &t) {
501 return t.oid == REGPROCEDUREOID || t.oid == OIDOID || t.oid == REGPROCOID;
502 }
503 static constexpr type type_for() { return type{.oid = REGPROCEDUREOID}; }
504};
505
506template <has_type_traits T> function<const char *, T> output_function() {
507 ::Oid foutoid;
508 bool typisvarlena;
509 ffi_guard{::getTypeOutputInfo}(type_traits<T>().type_for().oid, &foutoid, &typisvarlena);
510 return function<const char *, T>(foutoid);
511}
512
513template <has_type_traits T> function<const char *, T> output_function(T &&v) {
514 ::Oid foutoid;
515 bool typisvarlena;
516 ffi_guard{::getTypeOutputInfo}(type_traits<T>(v).type_for().oid, &foutoid, &typisvarlena);
517 return function<const char *, T>(foutoid);
518}
519
520template <has_type_traits T> function<const char *, T> output_function(T &v) {
521 ::Oid foutoid;
522 bool typisvarlena;
523 ffi_guard{::getTypeOutputInfo}(type_traits<T>(v).type_for().oid, &foutoid, &typisvarlena);
524 return function<const char *, T>(foutoid);
525}
526
527static function<const char *, cppgres::value> output_function(const type &t) {
528 ::Oid foutoid;
529 bool typisvarlena;
530 ffi_guard{::getTypeOutputInfo}(t.oid, &foutoid, &typisvarlena);
531 return function<const char *, cppgres::value>(foutoid);
532}
533
534} // namespace cppgres
Definition: datum.hpp:56
Function that operates on values of Postgres types.
Definition: function.hpp:39
Definition: set.hpp:15
Definition: type.hpp:50
Definition: utils.hpp:39
#define postgres_function(name, function)
Export a C++ function as a Postgres function.
Definition: cppgres.hpp:100
Definition: datum.hpp:217
Definition: memory.hpp:136
Definition: collation.hpp:8
Definition: function.hpp:103
A trait to convert from and into a cppgres::datum.
Definition: datum.hpp:114
static T from_nullable_datum(const nullable_datum &d, const oid oid, std::optional< memory_context > context=std::nullopt)=delete
Convert from a nullable datum.
static datum into_datum(const T &d)=delete
Convert datum into a type.
Definition: datum.hpp:39
Definition: datum.hpp:146
Wraps a C++ function to catch exceptions and report them as Postgres errors.
Definition: guard.hpp:64
Definition: guard.hpp:19
Definition: function.hpp:47
auto arg_types() const
argument types
Definition: function.hpp:70
auto args() const
passed arguments
Definition: function.hpp:61
auto arg_values() const
typed passed argument
Definition: function.hpp:80
type return_type() const
return type
Definition: function.hpp:95
short nargs() const
number of arguments actually passed
Definition: function.hpp:55
oid called_function_oid() const
called function OID
Definition: function.hpp:90
Definition: function.hpp:356
Definition: memory.hpp:240
Definition: memory.hpp:103
Definition: name.hpp:7
Definition: datum.hpp:60
Definition: datum.hpp:17
Postgres function implemented in C++.
Definition: function.hpp:155
auto operator()(FunctionCallInfo fc) -> ::Datum
Definition: function.hpp:169
Definition: syscache.hpp:27
Tuple descriptor operator.
Definition: record.hpp:21
Definition: type.hpp:42
Postgres type.
Definition: type.hpp:21
Definition: function_traits.hpp:37
Definition: value.hpp:8