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
53 : tupdesc(ffi_guard{::CreateTupleDescCopyConstr}(other.populate_compact_attribute())),
54 blessed(other.blessed), owned(other.owned) {}
55
60 : tupdesc(other.tupdesc), blessed(other.blessed), owned(other.owned) {}
61
68 tupdesc = ffi_guard{::CreateTupleDescCopyConstr}(other.populate_compact_attribute());
69 blessed = other.blessed;
70 return *this;
71 }
72
76 int attributes() const { return tupdesc->natts; }
77
83 ::FormData_pg_attribute &operator[](int n) const {
84 check_bounds(n);
85 return *TupleDescAttr(tupdesc, n);
86 }
87
88 type get_type(int n) const {
89 auto &att = operator[](n);
90 return {att.atttypid};
91 }
92
101 void set_type(int n, const type &type) {
102 check_not_blessed();
104 auto &att = operator[](n);
105 att.atttypid = (*typ).oid;
106 att.attcollation = (*typ).typcollation;
107 att.attlen = (*typ).typlen;
108 att.attstorage = (*typ).typstorage;
109 att.attalign = (*typ).typalign;
110 att.atttypmod = (*typ).typtypmod;
111 att.attbyval = (*typ).typbyval;
112 }
113
114 std::string_view get_name(int n) {
115 auto &att = operator[](n);
116 return NameStr(att.attname);
117 }
118
127 void set_name(int n, const name &name) {
128 check_not_blessed();
129 auto &att = operator[](n);
130 att.attname = name;
131 }
132
138 operator TupleDesc() {
139 populate_compact_attribute();
140 if (!blessed) {
141 tupdesc = ffi_guard{::BlessTupleDesc}(tupdesc);
142 blessed = true;
143 }
144 return tupdesc;
145 }
146
147#if PG_MAJORVERSION_NUM > 16
154 bool equal_row_types(const tuple_descriptor &other) {
155 return ffi_guard{::equalRowTypes}(tupdesc, other.tupdesc);
156 }
157#endif
158
165 bool equal_types(const tuple_descriptor &other) {
166 if (tupdesc->natts != other.tupdesc->natts)
167 return false;
168 if (tupdesc->tdtypeid != other.tupdesc->tdtypeid)
169 return false;
170
171 for (int i = 0; i < other.attributes(); i++) {
172 FormData_pg_attribute &att1 = operator[](i);
173 FormData_pg_attribute &att2 = other[i];
174
175 if (att1.atttypid != att2.atttypid || att1.atttypmod != att2.atttypmod ||
176 att1.attcollation != att2.attcollation || att1.attisdropped != att2.attisdropped ||
177 att1.attlen != att2.attlen || att1.attalign != att2.attalign) {
178 return false;
179 }
180 }
181
182 return true;
183 }
184
190 bool operator==(const tuple_descriptor &other) {
191 return ffi_guard{::equalTupleDescs}(tupdesc, other.tupdesc);
192 }
193
197 bool is_blessed() const { return blessed; }
198
199 operator TupleDesc() const { return tupdesc; }
200
201private:
202 inline void check_bounds(int n) const {
203 if (n + 1 > tupdesc->natts || n < 0) {
204 throw std::out_of_range(cppgres::fmt::format(
205 "attribute index {} is out of bounds for the tuple descriptor with the size of {}", n,
206 tupdesc->natts));
207 }
208 }
209 inline void check_not_blessed() const {
210 if (blessed) {
211 throw std::runtime_error("tuple_descriptor already blessed");
212 }
213 }
214
215 TupleDesc populate_compact_attribute() const {
216#if PG_MAJORVERSION_NUM >= 18
217 for (int i = 0; i < tupdesc->natts; i++) {
218 ffi_guard{::populate_compact_attribute}(tupdesc, i);
219 }
220#endif
221 return tupdesc;
222 }
223
224 TupleDesc tupdesc;
225 bool blessed;
226 bool owned;
227};
228
229static_assert(std::copy_constructible<tuple_descriptor>);
230static_assert(std::move_constructible<tuple_descriptor>);
231static_assert(std::is_copy_assignable_v<tuple_descriptor>);
232
239struct record {
240
241 friend struct datum_conversion<record>;
242
243 record(HeapTupleHeader heap_tuple, abstract_memory_context &ctx)
244 : tupdesc(/* FIXME: can we use the non-copy version with refcounting? */ ffi_guard{
245 ::lookup_rowtype_tupdesc_copy}(HeapTupleHeaderGetTypeId(heap_tuple),
246 HeapTupleHeaderGetTypMod(heap_tuple))),
247 tuple(ctx.template alloc<HeapTupleData>()) {
248#if PG_MAJORVERSION_NUM < 18
249 tuple->t_len = HeapTupleHeaderGetDatumLength(tupdesc.operator TupleDesc());
250#else
251 tuple->t_len = HeapTupleHeaderGetDatumLength(heap_tuple);
252#endif
253 tuple->t_data = heap_tuple;
254 }
255 record(HeapTupleHeader heap_tuple, abstract_memory_context &&ctx) : record(heap_tuple, ctx) {}
256
257 template <std::input_iterator Iter>
259 record(tuple_descriptor &tupdesc, Iter begin, Iter end)
260 : tupdesc(tupdesc), tuple([&]() {
261 std::vector<::Datum> values;
262 std::vector<uint8_t> nulls;
263 for (auto it = begin; it != end; ++it) {
264 auto nd = into_nullable_datum(*it);
265 values.push_back(nd.is_null() ? ::Datum(0) : nd);
266 nulls.push_back(nd.is_null() ? 1 : 0);
267 }
268 return ffi_guard{::heap_form_tuple}(this->tupdesc, values.data(),
269 reinterpret_cast<bool *>(nulls.data()));
270 }()) {}
271
272 template <convertible_into_nullable_datum... D>
273 record(tuple_descriptor &tupdesc, D &&...args)
274 : tupdesc(tupdesc), tuple([&]() {
275 std::array<nullable_datum, sizeof...(D)> datums = {
276 cppgres::into_nullable_datum(std::move(args))...};
277 std::array<bool, sizeof...(D)> nulls;
278 std::ranges::copy(datums | std::views::transform([](auto &v) { return v.is_null(); }),
279 nulls.begin());
280 std::array<::Datum, sizeof...(D)> values;
281 std::ranges::copy(
282 datums | std::views::transform([](auto &v) { return v.is_null() ? ::Datum(0) : v; }),
283 values.begin());
284 return ffi_guard{::heap_form_tuple}(this->tupdesc, values.data(), nulls.data());
285 }()) {}
286
290 int attributes() const { return tupdesc.operator TupleDesc()->natts; }
291
297 type attribute_type(int n) const {
298 check_bounds(n);
299 return {.oid = TupleDescAttr(tupdesc.operator TupleDesc(), n)->atttypid};
300 }
301
307 std::string_view attribute_name(int n) const {
308 check_bounds(n);
309 return {NameStr(TupleDescAttr(tupdesc.operator TupleDesc(), n)->attname)};
310 }
311
318 bool isnull;
319 check_bounds(n);
320 auto _heap_getattr = ffi_guard{
321#if PG_MAJORVERSION_NUM < 15
322 // Handle the fact that it is a macro
323 [](::HeapTuple tup, int attnum, ::TupleDesc tupleDesc, bool *isnull) {
324 return heap_getattr(tup, attnum, tupleDesc, isnull);
325 }
326#else
327 ::heap_getattr
328#endif
329 };
330 datum d(_heap_getattr(tuple, n + 1, tupdesc.operator TupleDesc(), &isnull));
331 return isnull ? nullable_datum() : nullable_datum(d);
332 }
333
339 nullable_datum operator[](std::string_view name) {
340 for (int i = 0; i < attributes(); i++) {
341 if (attribute_name(i) == name) {
342 return get_attribute(i);
343 }
344 }
345 throw std::out_of_range(cppgres::fmt::format("no attribute by the name of {}", name));
346 }
347
354
355 operator HeapTuple() const { return tuple; }
356
360 tuple_descriptor get_tuple_descriptor() const { return tupdesc; }
361
362 record(const record &other) : tupdesc(other.tupdesc), tuple(other.tuple) {}
363 record(const record &&other) : tupdesc(std::move(other.tupdesc)), tuple(other.tuple) {}
364 record &operator=(const record &other) {
365 tupdesc = other.tupdesc;
366 tuple = other.tuple;
367 return *this;
368 }
369
370private:
371 inline void check_bounds(int n) const {
372 if (n + 1 > attributes() || n < 0) {
373 throw std::out_of_range(cppgres::fmt::format(
374 "attribute index {} is out of bounds for record with the size of {}", n, attributes()));
375 }
376 }
377
378 tuple_descriptor tupdesc;
379 HeapTuple tuple;
380};
381static_assert(std::copy_constructible<record>);
382static_assert(std::move_constructible<record>);
383static_assert(std::is_copy_assignable_v<record>);
384
385template <> struct datum_conversion<record> {
386 static record from_datum(const datum &d, oid, std::optional<memory_context> ctx) {
387 return {reinterpret_cast<HeapTupleHeader>(ffi_guard{::pg_detoast_datum}(
388 reinterpret_cast<struct ::varlena *>(d.operator const ::Datum &()))),
389 ctx.has_value() ? ctx.value() : memory_context()};
390 }
391
392 static datum into_datum(const record &t) { return datum(PointerGetDatum(t.tuple)); }
393};
394
395template <> struct type_traits<record> {
396 bool is(const type &t) {
397 if (t.oid == RECORDOID)
398 return true;
399 // Check if it is a composite type and therefore can be coerced to a record
400 syscache<Form_pg_type, oid> cache(t.oid);
401 return (*cache).typtype == 'c';
402 }
403 constexpr type type_for() { return {.oid = RECORDOID}; }
404};
405
406} // namespace cppgres
Definition: memory.hpp:14
A trait to convert from and into a cppgres::datum.
Definition: datum.hpp:115
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:35
Definition: guard.hpp:19
Definition: memory.hpp:193
Definition: memory.hpp:61
Definition: name.hpp:7
Definition: datum.hpp:56
Definition: datum.hpp:17
Runtime-typed value of record type.
Definition: record.hpp:239
tuple_descriptor get_tuple_descriptor() const
Returns tuple descriptor.
Definition: record.hpp:360
nullable_datum operator[](std::string_view name)
Get attribute by name.
Definition: record.hpp:339
std::string_view attribute_name(int n) const
Name of attribute using a 0-based index.
Definition: record.hpp:307
nullable_datum get_attribute(int n)
Get attribute value (datum) using a 0-based index.
Definition: record.hpp:317
nullable_datum operator[](int n)
Get attribute by 0-based index.
Definition: record.hpp:353
int attributes() const
Number of attributes in the record.
Definition: record.hpp:290
type attribute_type(int n) const
Type of attribute using a 0-based index.
Definition: record.hpp:297
Definition: syscache.hpp:26
Tuple descriptor operator.
Definition: record.hpp:18
bool equal_types(const tuple_descriptor &other)
Determines whether two tuple descriptors have equal row types.
Definition: record.hpp:165
void set_name(int n, const name &name)
Set attribute name.
Definition: record.hpp:127
int attributes() const
Number of attributes.
Definition: record.hpp:76
bool is_blessed() const
Returns true if the tuple descriptor is blessed.
Definition: record.hpp:197
tuple_descriptor(tuple_descriptor &&other)
Move constructor.
Definition: record.hpp:59
tuple_descriptor(tuple_descriptor &other)
Copy constructor.
Definition: record.hpp:52
::FormData_pg_attribute & operator[](int n) const
Get a reference to Form_pg_attribute
Definition: record.hpp:83
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:190
void set_type(int n, const type &type)
Set attribute type.
Definition: record.hpp:101
tuple_descriptor & operator=(const tuple_descriptor &other)
Copy assignment.
Definition: record.hpp:67
Definition: type.hpp:41
Postgres type.
Definition: type.hpp:20
Definition: type.hpp:98