1 /*-------------------------------------------------------------------------
2  *
3  * expandedrecord.h
4  *	  Declarations for composite expanded objects.
5  *
6  * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  * src/include/utils/expandedrecord.h
10  *
11  *-------------------------------------------------------------------------
12  */
13 #ifndef EXPANDEDRECORD_H
14 #define EXPANDEDRECORD_H
15 
16 #include "access/htup.h"
17 #include "access/tupdesc.h"
18 #include "fmgr.h"
19 #include "utils/expandeddatum.h"
20 
21 
22 /*
23  * An expanded record is contained within a private memory context (as
24  * all expanded objects must be) and has a control structure as below.
25  *
26  * The expanded record might contain a regular "flat" tuple if that was the
27  * original input and we've not modified it.  Otherwise, the contents are
28  * represented by Datum/isnull arrays plus type information.  We could also
29  * have both forms, if we've deconstructed the original tuple for access
30  * purposes but not yet changed it.  For pass-by-reference field types, the
31  * Datums would point into the flat tuple in this situation.  Once we start
32  * modifying tuple fields, new pass-by-ref fields are separately palloc'd
33  * within the memory context.
34  *
35  * It's possible to build an expanded record that references a "flat" tuple
36  * stored externally, if the caller can guarantee that that tuple will not
37  * change for the lifetime of the expanded record.  (This frammish is mainly
38  * meant to avoid unnecessary data copying in trigger functions.)
39  */
40 #define ER_MAGIC 1384727874		/* ID for debugging crosschecks */
41 
42 typedef struct ExpandedRecordHeader
43 {
44 	/* Standard header for expanded objects */
45 	ExpandedObjectHeader hdr;
46 
47 	/* Magic value identifying an expanded record (for debugging only) */
48 	int			er_magic;
49 
50 	/* Assorted flag bits */
51 	int			flags;
52 #define ER_FLAG_FVALUE_VALID	0x0001	/* fvalue is up to date? */
53 #define ER_FLAG_FVALUE_ALLOCED	0x0002	/* fvalue is local storage? */
54 #define ER_FLAG_DVALUES_VALID	0x0004	/* dvalues/dnulls are up to date? */
55 #define ER_FLAG_DVALUES_ALLOCED	0x0008	/* any field values local storage? */
56 #define ER_FLAG_HAVE_EXTERNAL	0x0010	/* any field values are external? */
57 #define ER_FLAG_TUPDESC_ALLOCED	0x0020	/* tupdesc is local storage? */
58 #define ER_FLAG_IS_DOMAIN		0x0040	/* er_decltypeid is domain? */
59 #define ER_FLAG_IS_DUMMY		0x0080	/* this header is dummy (see below) */
60 /* flag bits that are not to be cleared when replacing tuple data: */
61 #define ER_FLAGS_NON_DATA \
62 	(ER_FLAG_TUPDESC_ALLOCED | ER_FLAG_IS_DOMAIN | ER_FLAG_IS_DUMMY)
63 
64 	/* Declared type of the record variable (could be a domain type) */
65 	Oid			er_decltypeid;
66 
67 	/*
68 	 * Actual composite type/typmod; never a domain (if ER_FLAG_IS_DOMAIN,
69 	 * these identify the composite base type).  These will match
70 	 * er_tupdesc->tdtypeid/tdtypmod, as well as the header fields of
71 	 * composite datums made from or stored in this expanded record.
72 	 */
73 	Oid			er_typeid;		/* type OID of the composite type */
74 	int32		er_typmod;		/* typmod of the composite type */
75 
76 	/*
77 	 * Tuple descriptor, if we have one, else NULL.  This may point to a
78 	 * reference-counted tupdesc originally belonging to the typcache, in
79 	 * which case we use a memory context reset callback to release the
80 	 * refcount.  It can also be locally allocated in this object's private
81 	 * context (in which case ER_FLAG_TUPDESC_ALLOCED is set).
82 	 */
83 	TupleDesc	er_tupdesc;
84 
85 	/*
86 	 * Unique-within-process identifier for the tupdesc (see typcache.h). This
87 	 * field will never be equal to INVALID_TUPLEDESC_IDENTIFIER.
88 	 */
89 	uint64		er_tupdesc_id;
90 
91 	/*
92 	 * If we have a Datum-array representation of the record, it's kept here;
93 	 * else ER_FLAG_DVALUES_VALID is not set, and dvalues/dnulls may be NULL
94 	 * if they've not yet been allocated.  If allocated, the dvalues and
95 	 * dnulls arrays are palloc'd within the object private context, and are
96 	 * of length matching er_tupdesc->natts.  For pass-by-ref field types,
97 	 * dvalues entries might point either into the fstartptr..fendptr area, or
98 	 * to separately palloc'd chunks.
99 	 */
100 	Datum	   *dvalues;		/* array of Datums */
101 	bool	   *dnulls;			/* array of is-null flags for Datums */
102 	int			nfields;		/* length of above arrays */
103 
104 	/*
105 	 * flat_size is the current space requirement for the flat equivalent of
106 	 * the expanded record, if known; otherwise it's 0.  We store this to make
107 	 * consecutive calls of get_flat_size cheap.  If flat_size is not 0, the
108 	 * component values data_len, hoff, and hasnull must be valid too.
109 	 */
110 	Size		flat_size;
111 
112 	Size		data_len;		/* data len within flat_size */
113 	int			hoff;			/* header offset */
114 	bool		hasnull;		/* null bitmap needed? */
115 
116 	/*
117 	 * fvalue points to the flat representation if we have one, else it is
118 	 * NULL.  If the flat representation is valid (up to date) then
119 	 * ER_FLAG_FVALUE_VALID is set.  Even if we've outdated the flat
120 	 * representation due to changes of user fields, it can still be used to
121 	 * fetch system column values.  If we have a flat representation then
122 	 * fstartptr/fendptr point to the start and end+1 of its data area; this
123 	 * is so that we can tell which Datum pointers point into the flat
124 	 * representation rather than being pointers to separately palloc'd data.
125 	 */
126 	HeapTuple	fvalue;			/* might or might not be private storage */
127 	char	   *fstartptr;		/* start of its data area */
128 	char	   *fendptr;		/* end+1 of its data area */
129 
130 	/* Some operations on the expanded record need a short-lived context */
131 	MemoryContext er_short_term_cxt;	/* short-term memory context */
132 
133 	/* Working state for domain checking, used if ER_FLAG_IS_DOMAIN is set */
134 	struct ExpandedRecordHeader *er_dummy_header;	/* dummy record header */
135 	void	   *er_domaininfo;	/* cache space for domain_check() */
136 
137 	/* Callback info (it's active if er_mcb.arg is not NULL) */
138 	MemoryContextCallback er_mcb;
139 } ExpandedRecordHeader;
140 
141 /* fmgr macros for expanded record objects */
142 #define PG_GETARG_EXPANDED_RECORD(n)  DatumGetExpandedRecord(PG_GETARG_DATUM(n))
143 #define ExpandedRecordGetDatum(erh)   EOHPGetRWDatum(&(erh)->hdr)
144 #define ExpandedRecordGetRODatum(erh) EOHPGetRODatum(&(erh)->hdr)
145 #define PG_RETURN_EXPANDED_RECORD(x)  PG_RETURN_DATUM(ExpandedRecordGetDatum(x))
146 
147 /* assorted other macros */
148 #define ExpandedRecordIsEmpty(erh) \
149 	(((erh)->flags & (ER_FLAG_DVALUES_VALID | ER_FLAG_FVALUE_VALID)) == 0)
150 #define ExpandedRecordIsDomain(erh) \
151 	(((erh)->flags & ER_FLAG_IS_DOMAIN) != 0)
152 
153 /* this can substitute for TransferExpandedObject() when we already have erh */
154 #define TransferExpandedRecord(erh, cxt) \
155 	MemoryContextSetParent((erh)->hdr.eoh_context, cxt)
156 
157 /* information returned by expanded_record_lookup_field() */
158 typedef struct ExpandedRecordFieldInfo
159 {
160 	int			fnumber;		/* field's attr number in record */
161 	Oid			ftypeid;		/* field's type/typmod info */
162 	int32		ftypmod;
163 	Oid			fcollation;		/* field's collation if any */
164 } ExpandedRecordFieldInfo;
165 
166 /*
167  * prototypes for functions defined in expandedrecord.c
168  */
169 extern ExpandedRecordHeader *make_expanded_record_from_typeid(Oid type_id, int32 typmod,
170 															  MemoryContext parentcontext);
171 extern ExpandedRecordHeader *make_expanded_record_from_tupdesc(TupleDesc tupdesc,
172 															   MemoryContext parentcontext);
173 extern ExpandedRecordHeader *make_expanded_record_from_exprecord(ExpandedRecordHeader *olderh,
174 																 MemoryContext parentcontext);
175 extern void expanded_record_set_tuple(ExpandedRecordHeader *erh,
176 									  HeapTuple tuple, bool copy, bool expand_external);
177 extern Datum make_expanded_record_from_datum(Datum recorddatum,
178 											 MemoryContext parentcontext);
179 extern TupleDesc expanded_record_fetch_tupdesc(ExpandedRecordHeader *erh);
180 extern HeapTuple expanded_record_get_tuple(ExpandedRecordHeader *erh);
181 extern ExpandedRecordHeader *DatumGetExpandedRecord(Datum d);
182 extern void deconstruct_expanded_record(ExpandedRecordHeader *erh);
183 extern bool expanded_record_lookup_field(ExpandedRecordHeader *erh,
184 										 const char *fieldname,
185 										 ExpandedRecordFieldInfo *finfo);
186 extern Datum expanded_record_fetch_field(ExpandedRecordHeader *erh, int fnumber,
187 										 bool *isnull);
188 extern void expanded_record_set_field_internal(ExpandedRecordHeader *erh,
189 											   int fnumber,
190 											   Datum newValue, bool isnull,
191 											   bool expand_external,
192 											   bool check_constraints);
193 extern void expanded_record_set_fields(ExpandedRecordHeader *erh,
194 									   const Datum *newValues, const bool *isnulls,
195 									   bool expand_external);
196 
197 /* outside code should never call expanded_record_set_field_internal as such */
198 #define expanded_record_set_field(erh, fnumber, newValue, isnull, expand_external) \
199 	expanded_record_set_field_internal(erh, fnumber, newValue, isnull, expand_external, true)
200 
201 /*
202  * Inline-able fast cases.  The expanded_record_fetch_xxx functions above
203  * handle the general cases.
204  */
205 
206 /* Get the tupdesc for the expanded record's actual type */
207 static inline TupleDesc
expanded_record_get_tupdesc(ExpandedRecordHeader * erh)208 expanded_record_get_tupdesc(ExpandedRecordHeader *erh)
209 {
210 	if (likely(erh->er_tupdesc != NULL))
211 		return erh->er_tupdesc;
212 	else
213 		return expanded_record_fetch_tupdesc(erh);
214 }
215 
216 /* Get value of record field */
217 static inline Datum
expanded_record_get_field(ExpandedRecordHeader * erh,int fnumber,bool * isnull)218 expanded_record_get_field(ExpandedRecordHeader *erh, int fnumber,
219 						  bool *isnull)
220 {
221 	if ((erh->flags & ER_FLAG_DVALUES_VALID) &&
222 		likely(fnumber > 0 && fnumber <= erh->nfields))
223 	{
224 		*isnull = erh->dnulls[fnumber - 1];
225 		return erh->dvalues[fnumber - 1];
226 	}
227 	else
228 		return expanded_record_fetch_field(erh, fnumber, isnull);
229 }
230 
231 #endif							/* EXPANDEDRECORD_H */
232