Cppgres
Build Postgres extensions in C++
Loading...
Searching...
No Matches
record.hpp
1#pragma once
2
3#include "imports.h"
4#include "name.hpp"
5#include "syscache.hpp"
6#include "type.hpp"
7#include "types.hpp"
8
9#include <ranges>
10
11namespace cppgres {
12
19
27 : tupdesc(([&]() {
29 auto res = ffi_guard{::CreateTemplateTupleDesc}(nattrs);
30 return res;
31 }())),
32 blessed(false), owned(true) {
33 for (int i = 0; i < nattrs; i++) {
34 operator[](i).attcollation = InvalidOid;
35 operator[](i).attisdropped = false;
36 }
37 }
44 tuple_descriptor(TupleDesc tupdesc, bool blessed = true)
45 : tupdesc(tupdesc), blessed(blessed), owned(false) {}
46
51 : tuple_descriptor([&]() {
53 if ((*syscache).typtype != TYPTYPE_COMPOSITE) {
54 throw std::invalid_argument("not a composite type");
55 }
56 return ffi_guard{::lookup_rowtype_tupdesc_copy}(t.oid, (*syscache).typtypmod);
57 }()) {}
58
65 : tupdesc(ffi_guard{::CreateTupleDescCopyConstr}(other.populate_compact_attribute())),
66 blessed(other.blessed), owned(other.owned) {}
67
72 : tupdesc(other.tupdesc), blessed(other.blessed), owned(other.owned) {}
73
80 tupdesc = ffi_guard{::CreateTupleDescCopyConstr}(other.populate_compact_attribute());
81 blessed = other.blessed;
82 return *this;
83 }
84
89 if (this != &other) {
90 tupdesc = other.tupdesc;
91 blessed = other.blessed;
92 owned = other.owned;
93 }
94 return *this;
95 }
96
100 int attributes() const noexcept { return tupdesc->natts; }
101
107 ::FormData_pg_attribute &operator[](int n) const {
108 check_bounds(n);
109 return *TupleDescAttr(tupdesc, n);
110 }
111
112 type get_type(int n) const {
113 auto &att = operator[](n);
114 return {att.atttypid};
115 }
116
125 void set_type(int n, const type &type) {
126 check_not_blessed();
128 auto &att = operator[](n);
129 att.atttypid = (*typ).oid;
130 att.attcollation = (*typ).typcollation;
131 att.attlen = (*typ).typlen;
132 att.attstorage = (*typ).typstorage;
133 att.attalign = (*typ).typalign;
134 att.atttypmod = (*typ).typtypmod;
135 att.attbyval = (*typ).typbyval;
136 }
137
138 std::string_view get_name(int n) {
139 auto &att = operator[](n);
140 return NameStr(att.attname);
141 }
142
151 void set_name(int n, const name &name) {
152 check_not_blessed();
153 auto &att = operator[](n);
154 att.attname = name;
155 }
156
162 operator TupleDesc() {
163 populate_compact_attribute();
164 if (!blessed) {
165 tupdesc = ffi_guard{::BlessTupleDesc}(tupdesc);
166 blessed = true;
167 }
168 return tupdesc;
169 }
170
171#if PG_MAJORVERSION_NUM > 16
178 bool equal_row_types(const tuple_descriptor &other) {
179 return ffi_guard{::equalRowTypes}(tupdesc, other.tupdesc);
180 }
181#endif
182
189 bool equal_types(const tuple_descriptor &other) {
190 if (tupdesc->natts != other.tupdesc->natts)
191 return false;
192 if (tupdesc->tdtypeid != other.tupdesc->tdtypeid)
193 return false;
194
195 for (int i = 0; i < other.attributes(); i++) {
196 FormData_pg_attribute &att1 = operator[](i);
197 FormData_pg_attribute &att2 = other[i];
198
199 if (att1.atttypid != att2.atttypid || att1.atttypmod != att2.atttypmod ||
200 att1.attcollation != att2.attcollation || att1.attisdropped != att2.attisdropped ||
201 att1.attlen != att2.attlen || att1.attalign != att2.attalign) {
202 return false;
203 }
204 }
205
206 return true;
207 }
208
214 bool operator==(const tuple_descriptor &other) {
215 return ffi_guard{::equalTupleDescs}(tupdesc, other.tupdesc);
216 }
217
221 bool is_blessed() const noexcept { return blessed; }
222
223 operator TupleDesc() const { return tupdesc; }
224
225private:
226 inline void check_bounds(int n) const {
227 if (n + 1 > tupdesc->natts || n < 0) {
228 throw std::out_of_range(cppgres::fmt::format(
229 "attribute index {} is out of bounds for the tuple descriptor with the size of {}", n,
230 tupdesc->natts));
231 }
232 }
233 inline void check_not_blessed() const {
234 if (blessed) {
235 throw std::runtime_error("tuple_descriptor already blessed");
236 }
237 }
238
239 TupleDesc populate_compact_attribute() const {
240#if PG_MAJORVERSION_NUM >= 18
241 for (int i = 0; i < tupdesc->natts; i++) {
242 ffi_guard{::populate_compact_attribute}(tupdesc, i);
243 }
244#endif
245 return tupdesc;
246 }
247
248 TupleDesc tupdesc;
249 bool blessed;
250 bool owned;
251};
252
253static_assert(std::copy_constructible<tuple_descriptor>);
254static_assert(std::move_constructible<tuple_descriptor>);
255static_assert(std::is_copy_assignable_v<tuple_descriptor>);
256
263struct record {
264
265 friend struct datum_conversion<record>;
266
267 record(HeapTupleHeader heap_tuple, abstract_memory_context &ctx)
268 : tupdesc(/* FIXME: can we use the non-copy version with refcounting? */ ffi_guard{
269 ::lookup_rowtype_tupdesc_copy}(HeapTupleHeaderGetTypeId(heap_tuple),
270 HeapTupleHeaderGetTypMod(heap_tuple))),
271 tuple(ctx.template alloc<HeapTupleData>()) {
272#if PG_MAJORVERSION_NUM < 18
273 tuple->t_len = HeapTupleHeaderGetDatumLength(tupdesc.operator TupleDesc());
274#else
275 tuple->t_len = HeapTupleHeaderGetDatumLength(heap_tuple);
276#endif
277 tuple->t_data = heap_tuple;
278 }
279 record(HeapTupleHeader heap_tuple, abstract_memory_context &&ctx) : record(heap_tuple, ctx) {}
280
281 template <std::input_iterator Iter>
283 record(tuple_descriptor &tupdesc, Iter begin, Iter end)
284 : tupdesc(tupdesc), tuple([&]() {
285 std::vector<::Datum> values;
286 std::vector<uint8_t> nulls;
287 for (auto it = begin; it != end; ++it) {
288 auto nd = into_nullable_datum(*it);
289 values.push_back(nd.is_null() ? ::Datum(0) : nd);
290 nulls.push_back(nd.is_null() ? 1 : 0);
291 }
292 return ffi_guard{::heap_form_tuple}(this->tupdesc, values.data(),
293 reinterpret_cast<bool *>(nulls.data()));
294 }()) {}
295
296 template <convertible_into_nullable_datum... D>
297 record(tuple_descriptor &tupdesc, D &&...args)
298 : tupdesc(tupdesc), tuple([&]() {
299 std::array<nullable_datum, sizeof...(D)> datums = {
300 cppgres::into_nullable_datum(std::move(args))...};
301 std::array<bool, sizeof...(D)> nulls;
302 std::ranges::copy(datums | std::views::transform([](auto &v) { return v.is_null(); }),
303 nulls.begin());
304 std::array<::Datum, sizeof...(D)> values;
305 std::ranges::copy(
306 datums | std::views::transform([](auto &v) { return v.is_null() ? ::Datum(0) : v; }),
307 values.begin());
308 return ffi_guard{::heap_form_tuple}(this->tupdesc, values.data(), nulls.data());
309 }()) {}
310
314 int attributes() const { return tupdesc.operator TupleDesc()->natts; }
315
321 type attribute_type(int n) const {
322 check_bounds(n);
323 return {.oid = TupleDescAttr(tupdesc.operator TupleDesc(), n)->atttypid};
324 }
325
331 std::string_view attribute_name(int n) const {
332 check_bounds(n);
333 return {NameStr(TupleDescAttr(tupdesc.operator TupleDesc(), n)->attname)};
334 }
335
342 bool isnull;
343 check_bounds(n);
344 auto _heap_getattr = ffi_guard{
345#if PG_MAJORVERSION_NUM < 15
346 // Handle the fact that it is a macro
347 [](::HeapTuple tup, int attnum, ::TupleDesc tupleDesc, bool *isnull) {
348 return heap_getattr(tup, attnum, tupleDesc, isnull);
349 }
350#else
351 ::heap_getattr
352#endif
353 };
354 datum d(_heap_getattr(tuple, n + 1, tupdesc.operator TupleDesc(), &isnull));
355 return isnull ? nullable_datum() : nullable_datum(d);
356 }
357
363 nullable_datum operator[](std::string_view name) {
364 for (int i = 0; i < attributes(); i++) {
365 if (attribute_name(i) == name) {
366 return get_attribute(i);
367 }
368 }
369 throw std::out_of_range(cppgres::fmt::format("no attribute by the name of {}", name));
370 }
371
378
379 operator HeapTuple() const noexcept { return tuple; }
380 operator TupleDesc() const noexcept { return tupdesc; }
381
385 tuple_descriptor get_tuple_descriptor() const noexcept { return tupdesc; }
386
387 record(const record &other) : tupdesc(other.tupdesc), tuple(other.tuple) {}
388 record(record &&other) noexcept : tupdesc(std::move(other.tupdesc)), tuple(other.tuple) {}
389 record &operator=(const record &other) {
390 tupdesc = other.tupdesc;
391 tuple = other.tuple;
392 return *this;
393 }
394
395 record &operator=(record &&other) noexcept {
396 if (this != &other) {
397 tupdesc = std::move(other.tupdesc);
398 tuple = other.tuple;
399 }
400 return *this;
401 }
402
403private:
404 inline void check_bounds(int n) const {
405 if (n + 1 > attributes() || n < 0) {
406 throw std::out_of_range(cppgres::fmt::format(
407 "attribute index {} is out of bounds for record with the size of {}", n, attributes()));
408 }
409 }
410
411 tuple_descriptor tupdesc;
412 HeapTuple tuple;
413};
414static_assert(std::copy_constructible<record>);
415static_assert(std::move_constructible<record>);
416static_assert(std::is_copy_assignable_v<record>);
417
418template <> struct datum_conversion<record> : default_datum_conversion<record> {
419 static record from_datum(const datum &d, oid, std::optional<memory_context> ctx) {
420 return {reinterpret_cast<HeapTupleHeader>(ffi_guard{::pg_detoast_datum}(
421 reinterpret_cast<struct ::varlena *>(d.operator const ::Datum &()))),
422 ctx.has_value() ? ctx.value() : memory_context()};
423 }
424
425 static datum into_datum(const record &t) { return datum(PointerGetDatum(t.tuple)); }
426};
427
428template <> struct type_traits<record> {
429 bool is(const type &t) {
430 if (t.oid == RECORDOID)
431 return true;
432 // Check if it is a composite type and therefore can be coerced to a record
433 syscache<Form_pg_type, oid> cache(t.oid);
434 return (*cache).typtype == 'c';
435 }
436 constexpr type type_for() { return {.oid = RECORDOID}; }
437};
438
439template <typename T>
440concept composite_type = requires {
441 { T::composite_type() } -> std::same_as<type>;
442};
443
444template <composite_type T> struct datum_conversion<T> : default_datum_conversion<T> {
445 static T from_datum(const datum &d, oid oid_, std::optional<memory_context> ctx) {
446 if (oid_ != T::composite_type().oid) {
447 throw std::runtime_error(fmt::format("invalid type: expected composite type {} got {}",
448 T::composite_type().name(), type{.oid = oid_}.name()));
449 }
450 auto mctx = ctx.has_value() ? ctx.value() : memory_context();
451 record rec{reinterpret_cast<HeapTupleHeader>(ffi_guard{::pg_detoast_datum}(
452 reinterpret_cast<struct ::varlena *>(d.operator const ::Datum &()))),
453 mctx};
454 return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
455 return T{([&] {
456 return from_nullable_datum<utils::tuple_element_t<Is, T>>(rec.get_attribute(Is),
457 rec.attribute_type(Is).oid, mctx);
458 }())...};
459 }(std::make_index_sequence<utils::tuple_size_v<T>>{});
460 }
461
462 static datum into_datum(const T &t) {
463 auto res = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
464 tuple_descriptor td(T::composite_type());
465 record rec{td, utils::get<Is>(t)...};
466 return datum(ffi_guard{::heap_copy_tuple_as_datum}(rec, rec));
467 }(std::make_index_sequence<utils::tuple_size_v<T>>{});
468 return res;
469 }
470};
471template <composite_type T> struct type_traits<T> {
472 bool is(const type &t) {
473 if (t == T::composite_type())
474 return true;
475 // Check if it is a composite type and therefore can be coerced to a record
476 syscache<Form_pg_type, oid> cache(t.oid);
477 return (*cache).typtype == 'c';
478 }
479 constexpr type type_for() { return T::composite_type(); }
480};
481
482} // namespace cppgres
Definition: record.hpp:440
Definition: memory.hpp:14
A trait to convert from and into a cppgres::datum.
Definition: datum.hpp:114
static T from_datum(const datum &, const oid, std::optional< memory_context > context=std::nullopt)=delete
Convert from a datum.
static datum into_datum(const T &d)=delete
Convert datum into a type.
Definition: datum.hpp:39
Definition: datum.hpp:146
Definition: guard.hpp:19
Heap tuple convenience wrapper.
Definition: heap_tuple.hpp:11
Definition: memory.hpp:217
Definition: memory.hpp:85
Definition: name.hpp:7
Definition: datum.hpp:60
Definition: datum.hpp:17
Runtime-typed value of record type.
Definition: record.hpp:263
nullable_datum operator[](std::string_view name)
Get attribute by name.
Definition: record.hpp:363
tuple_descriptor get_tuple_descriptor() const noexcept
Returns tuple descriptor.
Definition: record.hpp:385
std::string_view attribute_name(int n) const
Name of attribute using a 0-based index.
Definition: record.hpp:331
nullable_datum get_attribute(int n)
Get attribute value (datum) using a 0-based index.
Definition: record.hpp:341
nullable_datum operator[](int n)
Get attribute by 0-based index.
Definition: record.hpp:377
int attributes() const
Number of attributes in the record.
Definition: record.hpp:314
type attribute_type(int n) const
Type of attribute using a 0-based index.
Definition: record.hpp:321
Definition: syscache.hpp:27
Tuple descriptor operator.
Definition: record.hpp:18
tuple_descriptor(tuple_descriptor &&other) noexcept
Move constructor.
Definition: record.hpp:71
bool equal_types(const tuple_descriptor &other)
Determines whether two tuple descriptors have equal row types.
Definition: record.hpp:189
void set_name(int n, const name &name)
Set attribute name.
Definition: record.hpp:151
tuple_descriptor(tuple_descriptor &other)
Copy constructor.
Definition: record.hpp:64
::FormData_pg_attribute & operator[](int n) const
Get a reference to Form_pg_attribute
Definition: record.hpp:107
tuple_descriptor & operator=(tuple_descriptor &&other) noexcept
Move assignment.
Definition: record.hpp:88
tuple_descriptor(TupleDesc tupdesc, bool blessed=true)
Create a tuple descriptor for a given TupleDesc
Definition: record.hpp:44
tuple_descriptor(int nattrs, memory_context ctx=memory_context())
Create a tuple descriptor for a given number of attributes.
Definition: record.hpp:26
bool operator==(const tuple_descriptor &other)
Compare two TupleDesc structures for logical equality.
Definition: record.hpp:214
int attributes() const noexcept
Number of attributes.
Definition: record.hpp:100
tuple_descriptor(type t)
Create a tuple description for a given composite type.
Definition: record.hpp:50
bool is_blessed() const noexcept
Returns true if the tuple descriptor is blessed.
Definition: record.hpp:221
void set_type(int n, const type &type)
Set attribute type.
Definition: record.hpp:125
tuple_descriptor & operator=(const tuple_descriptor &other)
Copy assignment.
Definition: record.hpp:79
Definition: type.hpp:41
Postgres type.
Definition: type.hpp:20
Definition: type.hpp:104