/*-------------------------------------------------------------------------
 *
 * expandedrecord.h
 *	  Declarations for composite expanded objects.
 *
 * Portions Copyright (c) 1996-2025, 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 functions and macros for expanded record objects */
static inline Datum
ExpandedRecordGetDatum(const ExpandedRecordHeader *erh)
{
	return EOHPGetRWDatum(&erh->hdr);
}

static inline Datum
ExpandedRecordGetRODatum(const ExpandedRecordHeader *erh)
{
	return EOHPGetRODatum(&erh->hdr);
}

#define PG_GETARG_EXPANDED_RECORD(n)  DatumGetExpandedRecord(PG_GETARG_DATUM(n))
#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 */
