summaryrefslogtreecommitdiffstats
path: root/src/include/utils/expandedrecord.h
blob: 75b578934560ca2bc75deddd71dc9f29052e8304 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
/*-------------------------------------------------------------------------
 *
 * expandedrecord.h
 *	  Declarations for composite expanded objects.
 *
 * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/include/utils/expandedrecord.h
 *
 *-------------------------------------------------------------------------
 */
#ifndef EXPANDEDRECORD_H
#define EXPANDEDRECORD_H

#include "access/htup.h"
#include "access/tupdesc.h"
#include "fmgr.h"
#include "utils/expandeddatum.h"


/*
 * An expanded record is contained within a private memory context (as
 * all expanded objects must be) and has a control structure as below.
 *
 * The expanded record might contain a regular "flat" tuple if that was the
 * original input and we've not modified it.  Otherwise, the contents are
 * represented by Datum/isnull arrays plus type information.  We could also
 * have both forms, if we've deconstructed the original tuple for access
 * purposes but not yet changed it.  For pass-by-reference field types, the
 * Datums would point into the flat tuple in this situation.  Once we start
 * modifying tuple fields, new pass-by-ref fields are separately palloc'd
 * within the memory context.
 *
 * It's possible to build an expanded record that references a "flat" tuple
 * stored externally, if the caller can guarantee that that tuple will not
 * change for the lifetime of the expanded record.  (This frammish is mainly
 * meant to avoid unnecessary data copying in trigger functions.)
 */
#define ER_MAGIC 1384727874		/* ID for debugging crosschecks */

typedef struct ExpandedRecordHeader
{
	/* Standard header for expanded objects */
	ExpandedObjectHeader hdr;

	/* Magic value identifying an expanded record (for debugging only) */
	int			er_magic;

	/* Assorted flag bits */
	int			flags;
#define ER_FLAG_FVALUE_VALID	0x0001	/* fvalue is up to date? */
#define ER_FLAG_FVALUE_ALLOCED	0x0002	/* fvalue is local storage? */
#define ER_FLAG_DVALUES_VALID	0x0004	/* dvalues/dnulls are up to date? */
#define ER_FLAG_DVALUES_ALLOCED	0x0008	/* any field values local storage? */
#define ER_FLAG_HAVE_EXTERNAL	0x0010	/* any field values are external? */
#define ER_FLAG_TUPDESC_ALLOCED	0x0020	/* tupdesc is local storage? */
#define ER_FLAG_IS_DOMAIN		0x0040	/* er_decltypeid is domain? */
#define ER_FLAG_IS_DUMMY		0x0080	/* this header is dummy (see below) */
/* flag bits that are not to be cleared when replacing tuple data: */
#define ER_FLAGS_NON_DATA \
	(ER_FLAG_TUPDESC_ALLOCED | ER_FLAG_IS_DOMAIN | ER_FLAG_IS_DUMMY)

	/* Declared type of the record variable (could be a domain type) */
	Oid			er_decltypeid;

	/*
	 * Actual composite type/typmod; never a domain (if ER_FLAG_IS_DOMAIN,
	 * these identify the composite base type).  These will match
	 * er_tupdesc->tdtypeid/tdtypmod, as well as the header fields of
	 * composite datums made from or stored in this expanded record.
	 */
	Oid			er_typeid;		/* type OID of the composite type */
	int32		er_typmod;		/* typmod of the composite type */

	/*
	 * Tuple descriptor, if we have one, else NULL.  This may point to a
	 * reference-counted tupdesc originally belonging to the typcache, in
	 * which case we use a memory context reset callback to release the
	 * refcount.  It can also be locally allocated in this object's private
	 * context (in which case ER_FLAG_TUPDESC_ALLOCED is set).
	 */
	TupleDesc	er_tupdesc;

	/*
	 * Unique-within-process identifier for the tupdesc (see typcache.h). This
	 * field will never be equal to INVALID_TUPLEDESC_IDENTIFIER.
	 */
	uint64		er_tupdesc_id;

	/*
	 * If we have a Datum-array representation of the record, it's kept here;
	 * else ER_FLAG_DVALUES_VALID is not set, and dvalues/dnulls may be NULL
	 * if they've not yet been allocated.  If allocated, the dvalues and
	 * dnulls arrays are palloc'd within the object private context, and are
	 * of length matching er_tupdesc->natts.  For pass-by-ref field types,
	 * dvalues entries might point either into the fstartptr..fendptr area, or
	 * to separately palloc'd chunks.
	 */
	Datum	   *dvalues;		/* array of Datums */
	bool	   *dnulls;			/* array of is-null flags for Datums */
	int			nfields;		/* length of above arrays */

	/*
	 * flat_size is the current space requirement for the flat equivalent of
	 * the expanded record, if known; otherwise it's 0.  We store this to make
	 * consecutive calls of get_flat_size cheap.  If flat_size is not 0, the
	 * component values data_len, hoff, and hasnull must be valid too.
	 */
	Size		flat_size;

	Size		data_len;		/* data len within flat_size */
	int			hoff;			/* header offset */
	bool		hasnull;		/* null bitmap needed? */

	/*
	 * fvalue points to the flat representation if we have one, else it is
	 * NULL.  If the flat representation is valid (up to date) then
	 * ER_FLAG_FVALUE_VALID is set.  Even if we've outdated the flat
	 * representation due to changes of user fields, it can still be used to
	 * fetch system column values.  If we have a flat representation then
	 * fstartptr/fendptr point to the start and end+1 of its data area; this
	 * is so that we can tell which Datum pointers point into the flat
	 * representation rather than being pointers to separately palloc'd data.
	 */
	HeapTuple	fvalue;			/* might or might not be private storage */
	char	   *fstartptr;		/* start of its data area */
	char	   *fendptr;		/* end+1 of its data area */

	/* Some operations on the expanded record need a short-lived context */
	MemoryContext er_short_term_cxt;	/* short-term memory context */

	/* Working state for domain checking, used if ER_FLAG_IS_DOMAIN is set */
	struct ExpandedRecordHeader *er_dummy_header;	/* dummy record header */
	void	   *er_domaininfo;	/* cache space for domain_check() */

	/* Callback info (it's active if er_mcb.arg is not NULL) */
	MemoryContextCallback er_mcb;
} ExpandedRecordHeader;

/* fmgr macros for expanded record objects */
#define PG_GETARG_EXPANDED_RECORD(n)  DatumGetExpandedRecord(PG_GETARG_DATUM(n))
#define ExpandedRecordGetDatum(erh)   EOHPGetRWDatum(&(erh)->hdr)
#define ExpandedRecordGetRODatum(erh) EOHPGetRODatum(&(erh)->hdr)
#define PG_RETURN_EXPANDED_RECORD(x)  PG_RETURN_DATUM(ExpandedRecordGetDatum(x))

/* assorted other macros */
#define ExpandedRecordIsEmpty(erh) \
	(((erh)->flags & (ER_FLAG_DVALUES_VALID | ER_FLAG_FVALUE_VALID)) == 0)
#define ExpandedRecordIsDomain(erh) \
	(((erh)->flags & ER_FLAG_IS_DOMAIN) != 0)

/* this can substitute for TransferExpandedObject() when we already have erh */
#define TransferExpandedRecord(erh, cxt) \
	MemoryContextSetParent((erh)->hdr.eoh_context, cxt)

/* information returned by expanded_record_lookup_field() */
typedef struct ExpandedRecordFieldInfo
{
	int			fnumber;		/* field's attr number in record */
	Oid			ftypeid;		/* field's type/typmod info */
	int32		ftypmod;
	Oid			fcollation;		/* field's collation if any */
} ExpandedRecordFieldInfo;

/*
 * prototypes for functions defined in expandedrecord.c
 */
extern ExpandedRecordHeader *make_expanded_record_from_typeid(Oid type_id, int32 typmod,
															  MemoryContext parentcontext);
extern ExpandedRecordHeader *make_expanded_record_from_tupdesc(TupleDesc tupdesc,
															   MemoryContext parentcontext);
extern ExpandedRecordHeader *make_expanded_record_from_exprecord(ExpandedRecordHeader *olderh,
																 MemoryContext parentcontext);
extern void expanded_record_set_tuple(ExpandedRecordHeader *erh,
									  HeapTuple tuple, bool copy, bool expand_external);
extern Datum make_expanded_record_from_datum(Datum recorddatum,
											 MemoryContext parentcontext);
extern TupleDesc expanded_record_fetch_tupdesc(ExpandedRecordHeader *erh);
extern HeapTuple expanded_record_get_tuple(ExpandedRecordHeader *erh);
extern ExpandedRecordHeader *DatumGetExpandedRecord(Datum d);
extern void deconstruct_expanded_record(ExpandedRecordHeader *erh);
extern bool expanded_record_lookup_field(ExpandedRecordHeader *erh,
										 const char *fieldname,
										 ExpandedRecordFieldInfo *finfo);
extern Datum expanded_record_fetch_field(ExpandedRecordHeader *erh, int fnumber,
										 bool *isnull);
extern void expanded_record_set_field_internal(ExpandedRecordHeader *erh,
											   int fnumber,
											   Datum newValue, bool isnull,
											   bool expand_external,
											   bool check_constraints);
extern void expanded_record_set_fields(ExpandedRecordHeader *erh,
									   const Datum *newValues, const bool *isnulls,
									   bool expand_external);

/* outside code should never call expanded_record_set_field_internal as such */
#define expanded_record_set_field(erh, fnumber, newValue, isnull, expand_external) \
	expanded_record_set_field_internal(erh, fnumber, newValue, isnull, expand_external, true)

/*
 * Inline-able fast cases.  The expanded_record_fetch_xxx functions above
 * handle the general cases.
 */

/* Get the tupdesc for the expanded record's actual type */
static inline TupleDesc
expanded_record_get_tupdesc(ExpandedRecordHeader *erh)
{
	if (likely(erh->er_tupdesc != NULL))
		return erh->er_tupdesc;
	else
		return expanded_record_fetch_tupdesc(erh);
}

/* Get value of record field */
static inline Datum
expanded_record_get_field(ExpandedRecordHeader *erh, int fnumber,
						  bool *isnull)
{
	if ((erh->flags & ER_FLAG_DVALUES_VALID) &&
		likely(fnumber > 0 && fnumber <= erh->nfields))
	{
		*isnull = erh->dnulls[fnumber - 1];
		return erh->dvalues[fnumber - 1];
	}
	else
		return expanded_record_fetch_field(erh, fnumber, isnull);
}

#endif							/* EXPANDEDRECORD_H */