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 <algorithm>
10#include <memory>
11#include <ranges>
12#include <vector>
13
14namespace cppgres {
15
22
30 : tupdesc(([&]() {
32 auto res = ffi_guard{::CreateTemplateTupleDesc}(nattrs);
33 return res;
34 }())),
35 blessed(false), owned(true) {
36 for (int i = 0; i < nattrs; i++) {
37 operator[](i).attcollation = InvalidOid;
38 operator[](i).attisdropped = false;
39#if PG_MAJORVERSION_NUM < 18
40 // The template descriptor is zero-filled, and an attcacheoff of 0 means
41 // "cached at offset 0" — every attribute after the first then reads the
42 // first attribute's bytes (or worse). -1 is "not computed"; Postgres 18
43 // removed the field.
44 operator[](i).attcacheoff = -1;
45#endif
46 }
47 }
54 tuple_descriptor(TupleDesc tupdesc, bool blessed = true)
55 : tupdesc(tupdesc), blessed(blessed), owned(false) {}
56
61 : tuple_descriptor([&]() {
63 if ((*syscache).typtype != TYPTYPE_COMPOSITE) {
64 throw std::invalid_argument("not a composite type");
65 }
66 return ffi_guard{::lookup_rowtype_tupdesc_copy}(t.oid, (*syscache).typtypmod);
67 }()) {}
68
75 : tupdesc(ffi_guard{::CreateTupleDescCopyConstr}(other.populate_compact_attribute())),
76 blessed(other.blessed), owned(other.owned) {}
77
82 : tupdesc(other.tupdesc), blessed(other.blessed), owned(other.owned) {}
83
90 tupdesc = ffi_guard{::CreateTupleDescCopyConstr}(other.populate_compact_attribute());
91 blessed = other.blessed;
92 return *this;
93 }
94
99 if (this != &other) {
100 tupdesc = other.tupdesc;
101 blessed = other.blessed;
102 owned = other.owned;
103 }
104 return *this;
105 }
106
110 int attributes() const noexcept { return tupdesc->natts; }
111
117 ::FormData_pg_attribute &operator[](int n) const {
118 check_bounds(n);
119 return *TupleDescAttr(tupdesc, n);
120 }
121
122 type get_type(int n) const {
123 auto &att = operator[](n);
124 return {att.atttypid};
125 }
126
135 void set_type(int n, const type &type) {
136 check_not_blessed();
138 auto &att = operator[](n);
139 att.atttypid = (*typ).oid;
140 att.attcollation = (*typ).typcollation;
141 att.attlen = (*typ).typlen;
142 att.attstorage = (*typ).typstorage;
143 att.attalign = (*typ).typalign;
144 att.atttypmod = (*typ).typtypmod;
145 att.attbyval = (*typ).typbyval;
146 }
147
148 std::string_view get_name(int n) {
149 auto &att = operator[](n);
150 return NameStr(att.attname);
151 }
152
161 void set_name(int n, const name &name) {
162 check_not_blessed();
163 auto &att = operator[](n);
164 att.attname = name;
165 }
166
172 operator TupleDesc() {
173 populate_compact_attribute();
174 if (!blessed) {
175 tupdesc = ffi_guard{::BlessTupleDesc}(tupdesc);
176 blessed = true;
177 }
178 return tupdesc;
179 }
180
181#if PG_MAJORVERSION_NUM > 16
188 bool equal_row_types(const tuple_descriptor &other) {
189 return ffi_guard{::equalRowTypes}(tupdesc, other.tupdesc);
190 }
191#endif
192
199 bool equal_types(const tuple_descriptor &other) {
200 if (tupdesc->natts != other.tupdesc->natts)
201 return false;
202 if (tupdesc->tdtypeid != other.tupdesc->tdtypeid)
203 return false;
204
205 for (int i = 0; i < other.attributes(); i++) {
206 FormData_pg_attribute &att1 = operator[](i);
207 FormData_pg_attribute &att2 = other[i];
208
209 if (att1.atttypid != att2.atttypid || att1.atttypmod != att2.atttypmod ||
210 att1.attcollation != att2.attcollation || att1.attisdropped != att2.attisdropped ||
211 att1.attlen != att2.attlen || att1.attalign != att2.attalign) {
212 return false;
213 }
214 }
215
216 return true;
217 }
218
224 bool operator==(const tuple_descriptor &other) {
225 return ffi_guard{::equalTupleDescs}(tupdesc, other.tupdesc);
226 }
227
231 bool is_blessed() const noexcept { return blessed; }
232
233 operator TupleDesc() const { return tupdesc; }
234
235private:
236 inline void check_bounds(int n) const {
237 if (n + 1 > tupdesc->natts || n < 0) {
238 throw std::out_of_range(cppgres::fmt::format(
239 "attribute index {} is out of bounds for the tuple descriptor with the size of {}", n,
240 tupdesc->natts));
241 }
242 }
243 inline void check_not_blessed() const {
244 if (blessed) {
245 throw std::runtime_error("tuple_descriptor already blessed");
246 }
247 }
248
249 TupleDesc populate_compact_attribute() const {
250#if PG_MAJORVERSION_NUM >= 18
251 for (int i = 0; i < tupdesc->natts; i++) {
252 ffi_guard{::populate_compact_attribute}(tupdesc, i);
253 }
254#endif
255 return tupdesc;
256 }
257
258 TupleDesc tupdesc;
259 bool blessed;
260 bool owned;
261};
262
263static_assert(std::copy_constructible<tuple_descriptor>);
264static_assert(std::move_constructible<tuple_descriptor>);
265static_assert(std::is_copy_assignable_v<tuple_descriptor>);
266
273struct record {
274
275 friend struct datum_conversion<record>;
276
277 record(HeapTupleHeader heap_tuple, abstract_memory_context &ctx)
278 : tupdesc(/* FIXME: can we use the non-copy version with refcounting? */ ffi_guard{
279 ::lookup_rowtype_tupdesc_copy}(HeapTupleHeaderGetTypeId(heap_tuple),
280 HeapTupleHeaderGetTypMod(heap_tuple))),
281 tuple(ctx.template alloc<HeapTupleData>()) {
282#if PG_MAJORVERSION_NUM < 18
283 tuple->t_len = HeapTupleHeaderGetDatumLength(tupdesc.operator TupleDesc());
284#else
285 tuple->t_len = HeapTupleHeaderGetDatumLength(heap_tuple);
286#endif
287 tuple->t_data = heap_tuple;
288 }
289 record(HeapTupleHeader heap_tuple, abstract_memory_context &&ctx) : record(heap_tuple, ctx) {}
290
291 template <std::input_iterator Iter>
293 record(tuple_descriptor &tupdesc, Iter begin, Iter end)
294 : tupdesc(tupdesc), tuple([&]() {
295 std::vector<::Datum> values;
296 std::vector<bool> null_flags;
297 for (auto it = begin; it != end; ++it) {
298 auto nd = into_nullable_datum(*it);
299 values.push_back(nd.is_null() ? ::Datum(0) : nd);
300 null_flags.push_back(nd.is_null());
301 }
302 auto nulls = std::make_unique<bool[]>(null_flags.size());
303 std::ranges::copy(null_flags, nulls.get());
304 return ffi_guard{::heap_form_tuple}(this->tupdesc, values.data(), nulls.get());
305 }()) {}
306
307 template <convertible_into_nullable_datum... D>
308 record(tuple_descriptor &tupdesc, D &&...args)
309 : tupdesc(tupdesc), tuple([&]() {
310 std::array<nullable_datum, sizeof...(D)> datums = {
311 cppgres::into_nullable_datum(std::move(args))...};
312 std::array<bool, sizeof...(D)> nulls;
313 std::ranges::copy(datums | std::views::transform([](auto &v) { return v.is_null(); }),
314 nulls.begin());
315 std::array<::Datum, sizeof...(D)> values;
316 std::ranges::copy(
317 datums | std::views::transform([](auto &v) { return v.is_null() ? ::Datum(0) : v; }),
318 values.begin());
319 return ffi_guard{::heap_form_tuple}(this->tupdesc, values.data(), nulls.data());
320 }()) {}
321
325 int attributes() const { return tupdesc.operator TupleDesc()->natts; }
326
332 type attribute_type(int n) const {
333 check_bounds(n);
334 return {.oid = TupleDescAttr(tupdesc.operator TupleDesc(), n)->atttypid};
335 }
336
342 std::string_view attribute_name(int n) const {
343 check_bounds(n);
344 return {NameStr(TupleDescAttr(tupdesc.operator TupleDesc(), n)->attname)};
345 }
346
353 bool isnull;
354 check_bounds(n);
355 auto _heap_getattr = ffi_guard{
356#if PG_MAJORVERSION_NUM < 15
357 // Handle the fact that it is a macro
358 [](::HeapTuple tup, int attnum, ::TupleDesc tupleDesc, bool *isnull) {
359 return heap_getattr(tup, attnum, tupleDesc, isnull);
360 }
361#else
362 ::heap_getattr
363#endif
364 };
365 datum d(_heap_getattr(tuple, n + 1, tupdesc.operator TupleDesc(), &isnull));
366 return isnull ? nullable_datum() : nullable_datum(d);
367 }
368
374 nullable_datum operator[](std::string_view name) {
375 for (int i = 0; i < attributes(); i++) {
376 if (attribute_name(i) == name) {
377 return get_attribute(i);
378 }
379 }
380 throw std::out_of_range(cppgres::fmt::format("no attribute by the name of {}", name));
381 }
382
389
390 operator HeapTuple() const noexcept { return tuple; }
391 operator TupleDesc() const noexcept { return tupdesc; }
392
396 tuple_descriptor get_tuple_descriptor() const noexcept { return tupdesc; }
397
398 record(const record &other) : tupdesc(other.tupdesc), tuple(other.tuple) {}
399 record(record &&other) noexcept : tupdesc(std::move(other.tupdesc)), tuple(other.tuple) {}
400 record &operator=(const record &other) {
401 tupdesc = other.tupdesc;
402 tuple = other.tuple;
403 return *this;
404 }
405
406 record &operator=(record &&other) noexcept {
407 if (this != &other) {
408 tupdesc = std::move(other.tupdesc);
409 tuple = other.tuple;
410 }
411 return *this;
412 }
413
414private:
415 inline void check_bounds(int n) const {
416 if (n + 1 > attributes() || n < 0) {
417 throw std::out_of_range(cppgres::fmt::format(
418 "attribute index {} is out of bounds for record with the size of {}", n, attributes()));
419 }
420 }
421
422 tuple_descriptor tupdesc;
423 HeapTuple tuple;
424};
425static_assert(std::copy_constructible<record>);
426static_assert(std::move_constructible<record>);
427static_assert(std::is_copy_assignable_v<record>);
428
429template <> struct datum_conversion<record> : default_datum_conversion<record> {
430 static record from_datum(const datum &d, oid, std::optional<memory_context> ctx) {
431 return {reinterpret_cast<HeapTupleHeader>(ffi_guard{::pg_detoast_datum}(
432 reinterpret_cast<struct ::varlena *>(d.operator const ::Datum &()))),
433 ctx.has_value() ? ctx.value() : memory_context()};
434 }
435
436 static datum into_datum(const record &t) { return datum(PointerGetDatum(t.tuple)); }
437};
438
439template <> struct type_traits<record> {
440 bool is(const type &t) {
441 if (t.oid == RECORDOID)
442 return true;
443 // Check if it is a composite type and therefore can be coerced to a record
444 syscache<Form_pg_type, oid> cache(t.oid);
445 return (*cache).typtype == 'c';
446 }
447 constexpr type type_for() { return {.oid = RECORDOID}; }
448};
449
450template <typename T>
451concept composite_type = requires {
452 { T::composite_type() } -> std::same_as<type>;
453};
454
455template <composite_type T> struct datum_conversion<T> : default_datum_conversion<T> {
456 static T from_datum(const datum &d, oid oid_, std::optional<memory_context> ctx) {
457 if (oid_ != T::composite_type().oid) {
458 throw std::runtime_error(fmt::format("invalid type: expected composite type {} got {}",
459 T::composite_type().name(), type{.oid = oid_}.name()));
460 }
461 auto mctx = ctx.has_value() ? ctx.value() : memory_context();
462 record rec{reinterpret_cast<HeapTupleHeader>(ffi_guard{::pg_detoast_datum}(
463 reinterpret_cast<struct ::varlena *>(d.operator const ::Datum &()))),
464 mctx};
465 return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
466 return T{([&] {
467 return from_nullable_datum<utils::tuple_element_t<Is, T>>(rec.get_attribute(Is),
468 rec.attribute_type(Is).oid, mctx);
469 }())...};
470 }(std::make_index_sequence<utils::tuple_size_v<T>>{});
471 }
472
473 static datum into_datum(const T &t) {
474 auto res = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
475 tuple_descriptor td(T::composite_type());
476 record rec{td, utils::get<Is>(t)...};
477 return datum(ffi_guard{::heap_copy_tuple_as_datum}(rec, rec));
478 }(std::make_index_sequence<utils::tuple_size_v<T>>{});
479 return res;
480 }
481};
482template <composite_type T> struct type_traits<T> {
483 bool is(const type &t) {
484 if (t == T::composite_type())
485 return true;
486 // Check if it is a composite type and therefore can be coerced to a record
487 syscache<Form_pg_type, oid> cache(t.oid);
488 return (*cache).typtype == 'c';
489 }
490 constexpr type type_for() { return T::composite_type(); }
491};
492
493} // namespace cppgres
Definition: record.hpp:451
Definition: memory.hpp:15
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:20
Heap tuple convenience wrapper.
Definition: heap_tuple.hpp:11
Definition: memory.hpp:276
Definition: memory.hpp:139
Definition: name.hpp:7
Definition: datum.hpp:60
Definition: datum.hpp:17
Runtime-typed value of record type.
Definition: record.hpp:273
nullable_datum operator[](std::string_view name)
Get attribute by name.
Definition: record.hpp:374
tuple_descriptor get_tuple_descriptor() const noexcept
Returns tuple descriptor.
Definition: record.hpp:396
std::string_view attribute_name(int n) const
Name of attribute using a 0-based index.
Definition: record.hpp:342
nullable_datum get_attribute(int n)
Get attribute value (datum) using a 0-based index.
Definition: record.hpp:352
nullable_datum operator[](int n)
Get attribute by 0-based index.
Definition: record.hpp:388
int attributes() const
Number of attributes in the record.
Definition: record.hpp:325
type attribute_type(int n) const
Type of attribute using a 0-based index.
Definition: record.hpp:332
Definition: syscache.hpp:27
Tuple descriptor operator.
Definition: record.hpp:21
tuple_descriptor(tuple_descriptor &&other) noexcept
Move constructor.
Definition: record.hpp:81
bool equal_types(const tuple_descriptor &other)
Determines whether two tuple descriptors have equal row types.
Definition: record.hpp:199
void set_name(int n, const name &name)
Set attribute name.
Definition: record.hpp:161
tuple_descriptor(tuple_descriptor &other)
Copy constructor.
Definition: record.hpp:74
::FormData_pg_attribute & operator[](int n) const
Get a reference to Form_pg_attribute
Definition: record.hpp:117
tuple_descriptor & operator=(tuple_descriptor &&other) noexcept
Move assignment.
Definition: record.hpp:98
tuple_descriptor(TupleDesc tupdesc, bool blessed=true)
Create a tuple descriptor for a given TupleDesc
Definition: record.hpp:54
tuple_descriptor(int nattrs, memory_context ctx=memory_context())
Create a tuple descriptor for a given number of attributes.
Definition: record.hpp:29
bool operator==(const tuple_descriptor &other)
Compare two TupleDesc structures for logical equality.
Definition: record.hpp:224
int attributes() const noexcept
Number of attributes.
Definition: record.hpp:110
tuple_descriptor(type t)
Create a tuple description for a given composite type.
Definition: record.hpp:60
bool is_blessed() const noexcept
Returns true if the tuple descriptor is blessed.
Definition: record.hpp:231
void set_type(int n, const type &type)
Set attribute type.
Definition: record.hpp:135
tuple_descriptor & operator=(const tuple_descriptor &other)
Copy assignment.
Definition: record.hpp:89
Definition: type.hpp:43
Postgres type.
Definition: type.hpp:22
Definition: type.hpp:106