| /*------------------------------------------------------------------------- |
| * |
| * heaptuple.c |
| * This file contains heap tuple accessor and mutator routines, as well |
| * as various tuple utilities. |
| * |
| * Some notes about varlenas and this code: |
| * |
| * Before Postgres 8.3 varlenas always had a 4-byte length header, and |
| * therefore always needed 4-byte alignment (at least). This wasted space |
| * for short varlenas, for example CHAR(1) took 5 bytes and could need up to |
| * 3 additional padding bytes for alignment. |
| * |
| * Now, a short varlena (up to 126 data bytes) is reduced to a 1-byte header |
| * and we don't align it. To hide this from datatype-specific functions that |
| * don't want to deal with it, such a datum is considered "toasted" and will |
| * be expanded back to the normal 4-byte-header format by pg_detoast_datum. |
| * (In performance-critical code paths we can use pg_detoast_datum_packed |
| * and the appropriate access macros to avoid that overhead.) Note that this |
| * conversion is performed directly in heap_form_tuple, without invoking |
| * heaptoast.c. |
| * |
| * This change will break any code that assumes it needn't detoast values |
| * that have been put into a tuple but never sent to disk. Hopefully there |
| * are few such places. |
| * |
| * Varlenas still have alignment INT (or DOUBLE) in pg_type/pg_attribute, since |
| * that's the normal requirement for the untoasted format. But we ignore that |
| * for the 1-byte-header format. This means that the actual start position |
| * of a varlena datum may vary depending on which format it has. To determine |
| * what is stored, we have to require that alignment padding bytes be zero. |
| * (Postgres actually has always zeroed them, but now it's required!) Since |
| * the first byte of a 1-byte-header varlena can never be zero, we can examine |
| * the first byte after the previous datum to tell if it's a pad byte or the |
| * start of a 1-byte-header varlena. |
| * |
| * Note that while formerly we could rely on the first varlena column of a |
| * system catalog to be at the offset suggested by the C struct for the |
| * catalog, this is now risky: it's only safe if the preceding field is |
| * word-aligned, so that there will never be any padding. |
| * |
| * We don't pack varlenas whose attstorage is PLAIN, since the data type |
| * isn't expecting to have to detoast values. This is used in particular |
| * by oidvector and int2vector, which are used in the system catalogs |
| * and we'd like to still refer to them via C struct offsets. |
| * |
| * |
| * Portions Copyright (c) 2006-2009, Greenplum inc |
| * Portions Copyright (c) 2012-Present VMware, Inc. or its affiliates. |
| * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * |
| * IDENTIFICATION |
| * src/backend/access/common/heaptuple.c |
| * |
| *------------------------------------------------------------------------- |
| */ |
| |
| #include "postgres.h" |
| |
| #include "access/heaptoast.h" |
| #include "access/sysattr.h" |
| #include "access/tupdesc_details.h" |
| #include "common/hashfn.h" |
| #include "executor/tuptable.h" |
| #include "foreign/foreign.h" |
| #include "utils/datum.h" |
| #include "utils/expandeddatum.h" |
| #include "utils/hsearch.h" |
| #include "utils/memutils.h" |
| |
| #include "catalog/pg_type.h" |
| #include "cdb/cdbvars.h" /* GpIdentity.segindex */ |
| |
| /* |
| * Does att's datatype allow packing into the 1-byte-header varlena format? |
| * While functions that use TupleDescAttr() and assign attstorage = |
| * TYPSTORAGE_PLAIN cannot use packed varlena headers, functions that call |
| * TupleDescInitEntry() use typeForm->typstorage (TYPSTORAGE_EXTENDED) and |
| * can use packed varlena headers, e.g.: |
| * CREATE TABLE test(a VARCHAR(10000) STORAGE PLAIN); |
| * INSERT INTO test VALUES (repeat('A',10)); |
| * This can be verified with pageinspect. |
| */ |
| #define ATT_IS_PACKABLE(att) \ |
| ((att)->attlen == -1 && (att)->attstorage != TYPSTORAGE_PLAIN) |
| /* Use this if it's already known varlena */ |
| #define VARLENA_ATT_IS_PACKABLE(att) \ |
| ((att)->attstorage != TYPSTORAGE_PLAIN) |
| |
| /* |
| * Setup for cacheing pass-by-ref missing attributes in a way that survives |
| * tupleDesc destruction. |
| */ |
| |
| typedef struct |
| { |
| int len; |
| Datum value; |
| } missing_cache_key; |
| |
| static HTAB *missing_cache = NULL; |
| |
| static uint32 |
| missing_hash(const void *key, Size keysize) |
| { |
| const missing_cache_key *entry = (missing_cache_key *) key; |
| |
| return hash_bytes((const unsigned char *) entry->value, entry->len); |
| } |
| |
| static int |
| missing_match(const void *key1, const void *key2, Size keysize) |
| { |
| const missing_cache_key *entry1 = (missing_cache_key *) key1; |
| const missing_cache_key *entry2 = (missing_cache_key *) key2; |
| |
| if (entry1->len != entry2->len) |
| return entry1->len > entry2->len ? 1 : -1; |
| |
| return memcmp(DatumGetPointer(entry1->value), |
| DatumGetPointer(entry2->value), |
| entry1->len); |
| } |
| |
| static void |
| init_missing_cache() |
| { |
| HASHCTL hash_ctl; |
| |
| hash_ctl.keysize = sizeof(missing_cache_key); |
| hash_ctl.entrysize = sizeof(missing_cache_key); |
| hash_ctl.hcxt = TopMemoryContext; |
| hash_ctl.hash = missing_hash; |
| hash_ctl.match = missing_match; |
| missing_cache = |
| hash_create("Missing Values Cache", |
| 32, |
| &hash_ctl, |
| HASH_ELEM | HASH_CONTEXT | HASH_FUNCTION | HASH_COMPARE); |
| } |
| |
| /* ---------------------------------------------------------------- |
| * misc support routines |
| * ---------------------------------------------------------------- |
| */ |
| |
| /* |
| * Return the missing value of an attribute, or NULL if there isn't one. |
| */ |
| Datum |
| getmissingattr(TupleDesc tupleDesc, |
| int attnum, bool *isnull) |
| { |
| Form_pg_attribute att; |
| |
| Assert(attnum <= tupleDesc->natts); |
| Assert(attnum > 0); |
| |
| att = TupleDescAttr(tupleDesc, attnum - 1); |
| |
| if (att->atthasmissing) |
| { |
| AttrMissing *attrmiss; |
| |
| Assert(tupleDesc->constr); |
| Assert(tupleDesc->constr->missing); |
| |
| attrmiss = tupleDesc->constr->missing + (attnum - 1); |
| |
| if (attrmiss->am_present) |
| { |
| missing_cache_key key; |
| missing_cache_key *entry; |
| bool found; |
| MemoryContext oldctx; |
| |
| *isnull = false; |
| |
| /* no need to cache by-value attributes */ |
| if (att->attbyval) |
| return attrmiss->am_value; |
| |
| /* set up cache if required */ |
| if (missing_cache == NULL) |
| init_missing_cache(); |
| |
| /* check if there's a cache entry */ |
| Assert(att->attlen > 0 || att->attlen == -1); |
| if (att->attlen > 0) |
| key.len = att->attlen; |
| else |
| key.len = VARSIZE_ANY(attrmiss->am_value); |
| key.value = attrmiss->am_value; |
| |
| entry = hash_search(missing_cache, &key, HASH_ENTER, &found); |
| |
| if (!found) |
| { |
| /* cache miss, so we need a non-transient copy of the datum */ |
| oldctx = MemoryContextSwitchTo(TopMemoryContext); |
| entry->value = |
| datumCopy(attrmiss->am_value, false, att->attlen); |
| MemoryContextSwitchTo(oldctx); |
| } |
| |
| return entry->value; |
| } |
| } |
| |
| *isnull = true; |
| return PointerGetDatum(NULL); |
| } |
| |
| /* |
| * heap_compute_data_size |
| * Determine size of the data area of a tuple to be constructed |
| */ |
| Size |
| heap_compute_data_size(TupleDesc tupleDesc, |
| Datum *values, |
| bool *isnull) |
| { |
| Size data_length = 0; |
| int i; |
| int numberOfAttributes = tupleDesc->natts; |
| |
| for (i = 0; i < numberOfAttributes; i++) |
| { |
| Datum val; |
| Form_pg_attribute atti; |
| |
| if (isnull[i]) |
| continue; |
| |
| val = values[i]; |
| atti = TupleDescAttr(tupleDesc, i); |
| |
| if (ATT_IS_PACKABLE(atti) && |
| VARATT_CAN_MAKE_SHORT(DatumGetPointer(val))) |
| { |
| /* |
| * we're anticipating converting to a short varlena header, so |
| * adjust length and don't count any alignment |
| */ |
| data_length += VARATT_CONVERTED_SHORT_SIZE(DatumGetPointer(val)); |
| } |
| else if (atti->attlen == -1 && |
| VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(val))) |
| { |
| /* |
| * we want to flatten the expanded value so that the constructed |
| * tuple doesn't depend on it |
| */ |
| data_length = att_align_nominal(data_length, atti->attalign); |
| data_length += EOH_get_flat_size(DatumGetEOHP(val)); |
| } |
| else |
| { |
| data_length = att_align_datum(data_length, atti->attalign, |
| atti->attlen, val); |
| data_length = att_addlength_datum(data_length, atti->attlen, |
| val); |
| } |
| } |
| |
| return data_length; |
| } |
| |
| /* |
| * Per-attribute helper for heap_fill_tuple and other routines building tuples. |
| * |
| * Fill in either a data value or a bit in the null bitmask |
| */ |
| static inline void |
| fill_val(Form_pg_attribute att, |
| bits8 **bit, |
| int *bitmask, |
| char **dataP, |
| uint16 *infomask, |
| Datum datum, |
| bool isnull) |
| { |
| Size data_length; |
| char *data = *dataP; |
| |
| /* |
| * If we're building a null bitmap, set the appropriate bit for the |
| * current column value here. |
| */ |
| if (bit != NULL) |
| { |
| if (*bitmask != HIGHBIT) |
| *bitmask <<= 1; |
| else |
| { |
| *bit += 1; |
| **bit = 0x0; |
| *bitmask = 1; |
| } |
| |
| if (isnull) |
| { |
| *infomask |= HEAP_HASNULL; |
| return; |
| } |
| |
| **bit |= *bitmask; |
| } |
| |
| /* |
| * XXX we use the att_align macros on the pointer value itself, not on an |
| * offset. This is a bit of a hack. |
| */ |
| if (att->attbyval) |
| { |
| /* pass-by-value */ |
| data = (char *) att_align_nominal(data, att->attalign); |
| store_att_byval(data, datum, att->attlen); |
| data_length = att->attlen; |
| } |
| else if (att->attlen == -1) |
| { |
| /* varlena */ |
| Pointer val = DatumGetPointer(datum); |
| |
| *infomask |= HEAP_HASVARWIDTH; |
| if (VARATT_IS_EXTERNAL(val)) |
| { |
| if (VARATT_IS_EXTERNAL_EXPANDED(val)) |
| { |
| /* |
| * we want to flatten the expanded value so that the |
| * constructed tuple doesn't depend on it |
| */ |
| ExpandedObjectHeader *eoh = DatumGetEOHP(datum); |
| |
| data = (char *) att_align_nominal(data, |
| att->attalign); |
| data_length = EOH_get_flat_size(eoh); |
| EOH_flatten_into(eoh, data, data_length); |
| } |
| else |
| { |
| *infomask |= HEAP_HASEXTERNAL; |
| /* no alignment, since it's short by definition */ |
| data_length = VARSIZE_EXTERNAL(val); |
| memcpy(data, val, data_length); |
| } |
| } |
| else if (VARATT_IS_SHORT(val)) |
| { |
| /* no alignment for short varlenas */ |
| data_length = VARSIZE_SHORT(val); |
| memcpy(data, val, data_length); |
| } |
| else if (VARLENA_ATT_IS_PACKABLE(att) && |
| VARATT_CAN_MAKE_SHORT(val)) |
| { |
| /* convert to short varlena -- no alignment */ |
| data_length = VARATT_CONVERTED_SHORT_SIZE(val); |
| SET_VARSIZE_SHORT(data, data_length); |
| memcpy(data + 1, VARDATA(val), data_length - 1); |
| } |
| else |
| { |
| /* full 4-byte header varlena */ |
| data = (char *) att_align_nominal(data, |
| att->attalign); |
| data_length = VARSIZE(val); |
| memcpy(data, val, data_length); |
| } |
| } |
| else if (att->attlen == -2) |
| { |
| /* cstring ... never needs alignment */ |
| *infomask |= HEAP_HASVARWIDTH; |
| Assert(att->attalign == TYPALIGN_CHAR); |
| data_length = strlen(DatumGetCString(datum)) + 1; |
| memcpy(data, DatumGetPointer(datum), data_length); |
| } |
| else |
| { |
| /* fixed-length pass-by-reference */ |
| data = (char *) att_align_nominal(data, att->attalign); |
| Assert(att->attlen > 0); |
| data_length = att->attlen; |
| memcpy(data, DatumGetPointer(datum), data_length); |
| } |
| |
| data += data_length; |
| *dataP = data; |
| } |
| |
| /* |
| * heap_fill_tuple |
| * Load data portion of a tuple from values/isnull arrays |
| * |
| * We also fill the null bitmap (if any) and set the infomask bits |
| * that reflect the tuple's data contents. |
| * |
| * NOTE: it is now REQUIRED that the caller have pre-zeroed the data area. |
| * |
| * |
| * @param isnull will only be used if <code>bit</code> is non-NULL |
| * @param bit should be non-NULL (refer to td->t_bits) if isnull is set and contains non-null values |
| */ |
| Size |
| heap_fill_tuple(TupleDesc tupleDesc, |
| Datum *values, bool *isnull, |
| char *data, Size data_size, |
| uint16 *infomask, bits8 *bit) |
| { |
| bits8 *bitP; |
| int bitmask; |
| int i; |
| int numberOfAttributes = tupleDesc->natts; |
| |
| #ifdef USE_ASSERT_CHECKING |
| char *start = data; |
| #endif |
| |
| if (bit != NULL) |
| { |
| bitP = &bit[-1]; |
| bitmask = HIGHBIT; |
| } |
| else |
| { |
| /* just to keep compiler quiet */ |
| bitP = NULL; |
| bitmask = 0; |
| } |
| |
| *infomask &= ~(HEAP_HASNULL | HEAP_HASVARWIDTH | HEAP_HASEXTERNAL); |
| |
| for (i = 0; i < numberOfAttributes; i++) |
| { |
| Form_pg_attribute attr = TupleDescAttr(tupleDesc, i); |
| |
| fill_val(attr, |
| bitP ? &bitP : NULL, |
| &bitmask, |
| &data, |
| infomask, |
| values ? values[i] : PointerGetDatum(NULL), |
| isnull ? isnull[i] : true); |
| } |
| |
| Assert((data - start) == data_size); |
| |
| return data_size; |
| } |
| |
| /* ---------------------------------------------------------------- |
| * heap tuple interface |
| * ---------------------------------------------------------------- |
| */ |
| |
| /* ---------------- |
| * heap_attisnull - returns true iff tuple attribute is not present |
| * ---------------- |
| */ |
| bool |
| heap_attisnull(HeapTuple tup, int attnum, TupleDesc tupleDesc) |
| { |
| /* |
| * We allow a NULL tupledesc for relations not expected to have missing |
| * values, such as catalog relations and indexes. |
| */ |
| Assert(!tupleDesc || attnum <= tupleDesc->natts); |
| if (attnum > (int) HeapTupleHeaderGetNatts(tup->t_data)) |
| { |
| if (tupleDesc && TupleDescAttr(tupleDesc, attnum - 1)->atthasmissing) |
| return false; |
| else |
| return true; |
| } |
| |
| if (attnum > 0) |
| { |
| if (HeapTupleNoNulls(tup)) |
| return false; |
| return att_isnull(attnum - 1, tup->t_data->t_bits); |
| } |
| |
| switch (attnum) |
| { |
| case TableOidAttributeNumber: |
| case SelfItemPointerAttributeNumber: |
| case MinTransactionIdAttributeNumber: |
| case MinCommandIdAttributeNumber: |
| case MaxTransactionIdAttributeNumber: |
| case MaxCommandIdAttributeNumber: |
| case GpSegmentIdAttributeNumber: /*CDB*/ |
| case GpForeignServerAttributeNumber: |
| /* these are never null */ |
| break; |
| |
| default: |
| elog(ERROR, "invalid attnum: %d", attnum); |
| } |
| |
| return false; |
| } |
| |
| /* ---------------- |
| * nocachegetattr |
| * |
| * This only gets called from fastgetattr() macro, in cases where |
| * we can't use a cacheoffset and the value is not null. |
| * |
| * This caches attribute offsets in the attribute descriptor. |
| * |
| * An alternative way to speed things up would be to cache offsets |
| * with the tuple, but that seems more difficult unless you take |
| * the storage hit of actually putting those offsets into the |
| * tuple you send to disk. Yuck. |
| * |
| * This scheme will be slightly slower than that, but should |
| * perform well for queries which hit large #'s of tuples. After |
| * you cache the offsets once, examining all the other tuples using |
| * the same attribute descriptor will go much quicker. -cim 5/4/91 |
| * |
| * NOTE: if you need to change this code, see also heap_deform_tuple. |
| * Also see nocache_index_getattr, which is the same code for index |
| * tuples. |
| * ---------------- |
| */ |
| Datum |
| nocachegetattr(HeapTuple tup, |
| int attnum, |
| TupleDesc tupleDesc) |
| { |
| HeapTupleHeader td = tup->t_data; |
| char *tp; /* ptr to data part of tuple */ |
| bits8 *bp = td->t_bits; /* ptr to null bitmap in tuple */ |
| bool slow = false; /* do we have to walk attrs? */ |
| int off; /* current offset within data */ |
| |
| /* ---------------- |
| * Three cases: |
| * |
| * 1: No nulls and no variable-width attributes. |
| * 2: Has a null or a var-width AFTER att. |
| * 3: Has nulls or var-widths BEFORE att. |
| * ---------------- |
| */ |
| |
| attnum--; |
| |
| if (!HeapTupleNoNulls(tup)) |
| { |
| /* |
| * there's a null somewhere in the tuple |
| * |
| * check to see if any preceding bits are null... |
| */ |
| int byte = attnum >> 3; |
| int finalbit = attnum & 0x07; |
| |
| /* check for nulls "before" final bit of last byte */ |
| if ((~bp[byte]) & ((1 << finalbit) - 1)) |
| slow = true; |
| else |
| { |
| /* check for nulls in any "earlier" bytes */ |
| int i; |
| |
| for (i = 0; i < byte; i++) |
| { |
| if (bp[i] != 0xFF) |
| { |
| slow = true; |
| break; |
| } |
| } |
| } |
| } |
| |
| tp = (char *) td + td->t_hoff; |
| |
| if (!slow) |
| { |
| Form_pg_attribute att; |
| |
| /* |
| * If we get here, there are no nulls up to and including the target |
| * attribute. If we have a cached offset, we can use it. |
| */ |
| att = TupleDescAttr(tupleDesc, attnum); |
| if (att->attcacheoff >= 0) |
| return fetchatt(att, tp + att->attcacheoff); |
| |
| /* |
| * Otherwise, check for non-fixed-length attrs up to and including |
| * target. If there aren't any, it's safe to cheaply initialize the |
| * cached offsets for these attrs. |
| */ |
| if (HeapTupleHasVarWidth(tup)) |
| { |
| int j; |
| |
| for (j = 0; j <= attnum; j++) |
| { |
| if (TupleDescAttr(tupleDesc, j)->attlen <= 0) |
| { |
| slow = true; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (!slow) |
| { |
| int natts = tupleDesc->natts; |
| int j = 1; |
| |
| /* |
| * If we get here, we have a tuple with no nulls or var-widths up to |
| * and including the target attribute, so we can use the cached offset |
| * ... only we don't have it yet, or we'd not have got here. Since |
| * it's cheap to compute offsets for fixed-width columns, we take the |
| * opportunity to initialize the cached offsets for *all* the leading |
| * fixed-width columns, in hope of avoiding future visits to this |
| * routine. |
| */ |
| TupleDescAttr(tupleDesc, 0)->attcacheoff = 0; |
| |
| /* we might have set some offsets in the slow path previously */ |
| while (j < natts && TupleDescAttr(tupleDesc, j)->attcacheoff > 0) |
| j++; |
| |
| off = TupleDescAttr(tupleDesc, j - 1)->attcacheoff + |
| TupleDescAttr(tupleDesc, j - 1)->attlen; |
| |
| for (; j < natts; j++) |
| { |
| Form_pg_attribute att = TupleDescAttr(tupleDesc, j); |
| |
| if (att->attlen <= 0) |
| break; |
| |
| off = att_align_nominal(off, att->attalign); |
| |
| att->attcacheoff = off; |
| |
| off += att->attlen; |
| } |
| |
| Assert(j > attnum); |
| |
| off = TupleDescAttr(tupleDesc, attnum)->attcacheoff; |
| } |
| else |
| { |
| bool usecache = true; |
| int i; |
| |
| /* |
| * Now we know that we have to walk the tuple CAREFULLY. But we still |
| * might be able to cache some offsets for next time. |
| * |
| * Note - This loop is a little tricky. For each non-null attribute, |
| * we have to first account for alignment padding before the attr, |
| * then advance over the attr based on its length. Nulls have no |
| * storage and no alignment padding either. We can use/set |
| * attcacheoff until we reach either a null or a var-width attribute. |
| */ |
| off = 0; |
| for (i = 0;; i++) /* loop exit is at "break" */ |
| { |
| Form_pg_attribute att = TupleDescAttr(tupleDesc, i); |
| |
| if (HeapTupleHasNulls(tup) && att_isnull(i, bp)) |
| { |
| usecache = false; |
| continue; /* this cannot be the target att */ |
| } |
| |
| /* If we know the next offset, we can skip the rest */ |
| if (usecache && att->attcacheoff >= 0) |
| off = att->attcacheoff; |
| else if (att->attlen == -1) |
| { |
| /* |
| * We can only cache the offset for a varlena attribute if the |
| * offset is already suitably aligned, so that there would be |
| * no pad bytes in any case: then the offset will be valid for |
| * either an aligned or unaligned value. |
| */ |
| if (usecache && |
| off == att_align_nominal(off, att->attalign)) |
| att->attcacheoff = off; |
| else |
| { |
| off = att_align_pointer(off, att->attalign, -1, |
| tp + off); |
| usecache = false; |
| } |
| } |
| else |
| { |
| /* not varlena, so safe to use att_align_nominal */ |
| off = att_align_nominal(off, att->attalign); |
| |
| if (usecache) |
| att->attcacheoff = off; |
| } |
| |
| if (i == attnum) |
| break; |
| |
| off = att_addlength_pointer(off, att->attlen, tp + off); |
| |
| if (usecache && att->attlen <= 0) |
| usecache = false; |
| } |
| } |
| |
| return fetchatt(TupleDescAttr(tupleDesc, attnum), tp + off); |
| } |
| |
| /* ---------------- |
| * heap_getsysattr |
| * |
| * Fetch the value of a system attribute for a tuple. |
| * |
| * This is a support routine for the heap_getattr macro. The macro |
| * has already determined that the attnum refers to a system attribute. |
| * ---------------- |
| */ |
| Datum |
| heap_getsysattr(HeapTuple tup, int attnum, TupleDesc tupleDesc, bool *isnull) |
| { |
| Datum result; |
| |
| Assert(tup); |
| |
| /* Currently, no sys attribute ever reads as NULL. */ |
| *isnull = false; |
| |
| switch (attnum) |
| { |
| case SelfItemPointerAttributeNumber: |
| /* pass-by-reference datatype */ |
| result = PointerGetDatum(&(tup->t_self)); |
| break; |
| case MinTransactionIdAttributeNumber: |
| result = TransactionIdGetDatum(HeapTupleHeaderGetRawXmin(tup->t_data)); |
| break; |
| case MaxTransactionIdAttributeNumber: |
| result = TransactionIdGetDatum(HeapTupleHeaderGetRawXmax(tup->t_data)); |
| break; |
| case MinCommandIdAttributeNumber: |
| case MaxCommandIdAttributeNumber: |
| |
| /* |
| * cmin and cmax are now both aliases for the same field, which |
| * can in fact also be a combo command id. XXX perhaps we should |
| * return the "real" cmin or cmax if possible, that is if we are |
| * inside the originating transaction? |
| */ |
| result = CommandIdGetDatum(HeapTupleHeaderGetRawCommandId(tup->t_data)); |
| break; |
| case TableOidAttributeNumber: |
| result = ObjectIdGetDatum(tup->t_tableOid); |
| break; |
| case GpSegmentIdAttributeNumber: /*CDB*/ |
| result = Int32GetDatum(GpIdentity.segindex); |
| break; |
| case GpForeignServerAttributeNumber: |
| result = ObjectIdGetDatum(GetForeignServerSegByRelid(tup->t_tableOid)); |
| break; |
| default: |
| elog(ERROR, "invalid attnum: %d", attnum); |
| result = 0; /* keep compiler quiet */ |
| break; |
| } |
| return result; |
| } |
| |
| /* ---------------- |
| * heap_copytuple |
| * |
| * returns a copy of an entire tuple |
| * |
| * The HeapTuple struct, tuple header, and tuple data are all allocated |
| * as a single palloc() block. |
| * ---------------- |
| */ |
| HeapTuple |
| heaptuple_copy_to(HeapTuple tuple, HeapTuple dest, uint32 *destlen) |
| { |
| HeapTuple newTuple; |
| uint32 len; |
| |
| if (!HeapTupleIsValid(tuple) || tuple->t_data == NULL) |
| return NULL; |
| |
| len = HEAPTUPLESIZE + tuple->t_len; |
| if(destlen && *destlen < len) |
| { |
| *destlen = len; |
| return NULL; |
| } |
| |
| if(destlen) |
| { |
| *destlen = len; |
| newTuple = dest; |
| } |
| else |
| newTuple = (HeapTuple) palloc(HEAPTUPLESIZE + tuple->t_len); |
| |
| newTuple->t_len = tuple->t_len; |
| newTuple->t_self = tuple->t_self; |
| newTuple->t_tableOid = tuple->t_tableOid; |
| newTuple->t_data = (HeapTupleHeader) ((char *) newTuple + HEAPTUPLESIZE); |
| memcpy((char *) newTuple->t_data, (char *) tuple->t_data, tuple->t_len); |
| return newTuple; |
| } |
| |
| /* ---------------- |
| * heap_copytuple_with_tuple |
| * |
| * copy a tuple into a caller-supplied HeapTuple management struct |
| * |
| * Note that after calling this function, the "dest" HeapTuple will not be |
| * allocated as a single palloc() block (unlike with heap_copytuple()). |
| * ---------------- |
| */ |
| void |
| heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest) |
| { |
| if (!HeapTupleIsValid(src) || src->t_data == NULL) |
| { |
| dest->t_data = NULL; |
| return; |
| } |
| |
| dest->t_len = src->t_len; |
| dest->t_self = src->t_self; |
| dest->t_tableOid = src->t_tableOid; |
| dest->t_data = (HeapTupleHeader) palloc(src->t_len); |
| memcpy((char *) dest->t_data, (char *) src->t_data, src->t_len); |
| } |
| |
| /* |
| * Expand a tuple which has fewer attributes than required. For each attribute |
| * not present in the sourceTuple, if there is a missing value that will be |
| * used. Otherwise the attribute will be set to NULL. |
| * |
| * The source tuple must have fewer attributes than the required number. |
| * |
| * Only one of targetHeapTuple and targetMinimalTuple may be supplied. The |
| * other argument must be NULL. |
| */ |
| static void |
| expand_tuple(HeapTuple *targetHeapTuple, |
| MinimalTuple *targetMinimalTuple, |
| HeapTuple sourceTuple, |
| TupleDesc tupleDesc) |
| { |
| AttrMissing *attrmiss = NULL; |
| int attnum; |
| int firstmissingnum; |
| bool hasNulls = HeapTupleHasNulls(sourceTuple); |
| HeapTupleHeader targetTHeader; |
| HeapTupleHeader sourceTHeader = sourceTuple->t_data; |
| int sourceNatts = HeapTupleHeaderGetNatts(sourceTHeader); |
| int natts = tupleDesc->natts; |
| int sourceNullLen; |
| int targetNullLen; |
| Size sourceDataLen = sourceTuple->t_len - sourceTHeader->t_hoff; |
| Size targetDataLen; |
| Size len; |
| int hoff; |
| bits8 *nullBits = NULL; |
| int bitMask = 0; |
| char *targetData; |
| uint16 *infoMask; |
| |
| Assert((targetHeapTuple && !targetMinimalTuple) |
| || (!targetHeapTuple && targetMinimalTuple)); |
| |
| Assert(sourceNatts < natts); |
| |
| sourceNullLen = (hasNulls ? BITMAPLEN(sourceNatts) : 0); |
| |
| targetDataLen = sourceDataLen; |
| |
| if (tupleDesc->constr && |
| tupleDesc->constr->missing) |
| { |
| /* |
| * If there are missing values we want to put them into the tuple. |
| * Before that we have to compute the extra length for the values |
| * array and the variable length data. |
| */ |
| attrmiss = tupleDesc->constr->missing; |
| |
| /* |
| * Find the first item in attrmiss for which we don't have a value in |
| * the source. We can ignore all the missing entries before that. |
| */ |
| for (firstmissingnum = sourceNatts; |
| firstmissingnum < natts; |
| firstmissingnum++) |
| { |
| if (attrmiss[firstmissingnum].am_present) |
| break; |
| else |
| hasNulls = true; |
| } |
| |
| /* |
| * Now walk the missing attributes. If there is a missing value make |
| * space for it. Otherwise, it's going to be NULL. |
| */ |
| for (attnum = firstmissingnum; |
| attnum < natts; |
| attnum++) |
| { |
| if (attrmiss[attnum].am_present) |
| { |
| Form_pg_attribute att = TupleDescAttr(tupleDesc, attnum); |
| |
| targetDataLen = att_align_datum(targetDataLen, |
| att->attalign, |
| att->attlen, |
| attrmiss[attnum].am_value); |
| |
| targetDataLen = att_addlength_pointer(targetDataLen, |
| att->attlen, |
| attrmiss[attnum].am_value); |
| } |
| else |
| { |
| /* no missing value, so it must be null */ |
| hasNulls = true; |
| } |
| } |
| } /* end if have missing values */ |
| else |
| { |
| /* |
| * If there are no missing values at all then NULLS must be allowed, |
| * since some of the attributes are known to be absent. |
| */ |
| hasNulls = true; |
| } |
| |
| len = 0; |
| |
| if (hasNulls) |
| { |
| targetNullLen = BITMAPLEN(natts); |
| len += targetNullLen; |
| } |
| else |
| targetNullLen = 0; |
| |
| /* |
| * Allocate and zero the space needed. Note that the tuple body and |
| * HeapTupleData management structure are allocated in one chunk. |
| */ |
| if (targetHeapTuple) |
| { |
| len += offsetof(HeapTupleHeaderData, t_bits); |
| hoff = len = MAXALIGN(len); /* align user data safely */ |
| len += targetDataLen; |
| |
| *targetHeapTuple = (HeapTuple) palloc0(HEAPTUPLESIZE + len); |
| (*targetHeapTuple)->t_data |
| = targetTHeader |
| = (HeapTupleHeader) ((char *) *targetHeapTuple + HEAPTUPLESIZE); |
| (*targetHeapTuple)->t_len = len; |
| (*targetHeapTuple)->t_tableOid = sourceTuple->t_tableOid; |
| (*targetHeapTuple)->t_self = sourceTuple->t_self; |
| |
| targetTHeader->t_infomask = sourceTHeader->t_infomask; |
| targetTHeader->t_hoff = hoff; |
| HeapTupleHeaderSetNatts(targetTHeader, natts); |
| HeapTupleHeaderSetDatumLength(targetTHeader, len); |
| HeapTupleHeaderSetTypeId(targetTHeader, tupleDesc->tdtypeid); |
| HeapTupleHeaderSetTypMod(targetTHeader, tupleDesc->tdtypmod); |
| /* We also make sure that t_ctid is invalid unless explicitly set */ |
| ItemPointerSetInvalid(&(targetTHeader->t_ctid)); |
| if (targetNullLen > 0) |
| nullBits = (bits8 *) ((char *) (*targetHeapTuple)->t_data |
| + offsetof(HeapTupleHeaderData, t_bits)); |
| targetData = (char *) (*targetHeapTuple)->t_data + hoff; |
| infoMask = &(targetTHeader->t_infomask); |
| } |
| else |
| { |
| len += SizeofMinimalTupleHeader; |
| hoff = len = MAXALIGN(len); /* align user data safely */ |
| len += targetDataLen; |
| |
| *targetMinimalTuple = (MinimalTuple) palloc0(len); |
| (*targetMinimalTuple)->t_len = len; |
| (*targetMinimalTuple)->t_hoff = hoff + MINIMAL_TUPLE_OFFSET; |
| (*targetMinimalTuple)->t_infomask = sourceTHeader->t_infomask; |
| /* Same macro works for MinimalTuples */ |
| HeapTupleHeaderSetNatts(*targetMinimalTuple, natts); |
| if (targetNullLen > 0) |
| nullBits = (bits8 *) ((char *) *targetMinimalTuple |
| + offsetof(MinimalTupleData, t_bits)); |
| targetData = (char *) *targetMinimalTuple + hoff; |
| infoMask = &((*targetMinimalTuple)->t_infomask); |
| } |
| |
| if (targetNullLen > 0) |
| { |
| if (sourceNullLen > 0) |
| { |
| /* if bitmap pre-existed copy in - all is set */ |
| memcpy(nullBits, |
| ((char *) sourceTHeader) |
| + offsetof(HeapTupleHeaderData, t_bits), |
| sourceNullLen); |
| nullBits += sourceNullLen - 1; |
| } |
| else |
| { |
| sourceNullLen = BITMAPLEN(sourceNatts); |
| /* Set NOT NULL for all existing attributes */ |
| memset(nullBits, 0xff, sourceNullLen); |
| |
| nullBits += sourceNullLen - 1; |
| |
| if (sourceNatts & 0x07) |
| { |
| /* build the mask (inverted!) */ |
| bitMask = 0xff << (sourceNatts & 0x07); |
| /* Voila */ |
| *nullBits = ~bitMask; |
| } |
| } |
| |
| bitMask = (1 << ((sourceNatts - 1) & 0x07)); |
| } /* End if have null bitmap */ |
| |
| memcpy(targetData, |
| ((char *) sourceTuple->t_data) + sourceTHeader->t_hoff, |
| sourceDataLen); |
| |
| targetData += sourceDataLen; |
| |
| /* Now fill in the missing values */ |
| for (attnum = sourceNatts; attnum < natts; attnum++) |
| { |
| |
| Form_pg_attribute attr = TupleDescAttr(tupleDesc, attnum); |
| |
| if (attrmiss && attrmiss[attnum].am_present) |
| { |
| fill_val(attr, |
| nullBits ? &nullBits : NULL, |
| &bitMask, |
| &targetData, |
| infoMask, |
| attrmiss[attnum].am_value, |
| false); |
| } |
| else |
| { |
| fill_val(attr, |
| &nullBits, |
| &bitMask, |
| &targetData, |
| infoMask, |
| (Datum) 0, |
| true); |
| } |
| } /* end loop over missing attributes */ |
| } |
| |
| /* |
| * Fill in the missing values for a minimal HeapTuple |
| */ |
| MinimalTuple |
| minimal_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc) |
| { |
| MinimalTuple minimalTuple; |
| |
| expand_tuple(NULL, &minimalTuple, sourceTuple, tupleDesc); |
| return minimalTuple; |
| } |
| |
| /* |
| * Fill in the missing values for an ordinary HeapTuple |
| */ |
| HeapTuple |
| heap_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc) |
| { |
| HeapTuple heapTuple; |
| |
| expand_tuple(&heapTuple, NULL, sourceTuple, tupleDesc); |
| return heapTuple; |
| } |
| |
| /* ---------------- |
| * heap_copy_tuple_as_datum |
| * |
| * copy a tuple as a composite-type Datum |
| * ---------------- |
| */ |
| Datum |
| heap_copy_tuple_as_datum(HeapTuple tuple, TupleDesc tupleDesc) |
| { |
| HeapTupleHeader td; |
| |
| /* |
| * If the tuple contains any external TOAST pointers, we have to inline |
| * those fields to meet the conventions for composite-type Datums. |
| */ |
| if (HeapTupleHasExternal(tuple)) |
| return toast_flatten_tuple_to_datum(tuple->t_data, |
| tuple->t_len, |
| tupleDesc); |
| |
| /* |
| * Fast path for easy case: just make a palloc'd copy and insert the |
| * correct composite-Datum header fields (since those may not be set if |
| * the given tuple came from disk, rather than from heap_form_tuple). |
| */ |
| td = (HeapTupleHeader) palloc(tuple->t_len); |
| memcpy((char *) td, (char *) tuple->t_data, tuple->t_len); |
| |
| HeapTupleHeaderSetDatumLength(td, tuple->t_len); |
| HeapTupleHeaderSetTypeId(td, tupleDesc->tdtypeid); |
| HeapTupleHeaderSetTypMod(td, tupleDesc->tdtypmod); |
| |
| return PointerGetDatum(td); |
| } |
| |
| /* |
| * heap_form_tuple |
| * construct a tuple from the given values[] and isnull[] arrays, |
| * which are of the length indicated by tupleDescriptor->natts |
| * |
| * The result is allocated in the current memory context. |
| */ |
| HeapTuple |
| heaptuple_form_to(TupleDesc tupleDescriptor, Datum *values, bool *isnull, HeapTuple dst, uint32 *dstlen) |
| { |
| HeapTuple tuple; /* return tuple */ |
| HeapTupleHeader td; /* tuple data */ |
| Size len, |
| data_len; |
| int hoff; |
| bool hasnull = false; |
| int numberOfAttributes = tupleDescriptor->natts; |
| int i; |
| |
| if (numberOfAttributes > MaxTupleAttributeNumber) |
| ereport(ERROR, |
| (errcode(ERRCODE_TOO_MANY_COLUMNS), |
| errmsg("number of columns (%d) exceeds limit (%d)", |
| numberOfAttributes, MaxTupleAttributeNumber))); |
| |
| /* |
| * Check for nulls |
| */ |
| for (i = 0; i < numberOfAttributes; i++) |
| { |
| if (isnull[i]) |
| { |
| hasnull = true; |
| break; |
| } |
| } |
| |
| /* |
| * Determine total space needed |
| */ |
| len = offsetof(HeapTupleHeaderData, t_bits); |
| |
| if (hasnull) |
| len += BITMAPLEN(numberOfAttributes); |
| |
| hoff = len = MAXALIGN(len); /* align user data safely */ |
| |
| data_len = heap_compute_data_size(tupleDescriptor, values, isnull); |
| |
| len += data_len; |
| |
| if (dstlen && (*dstlen) < (HEAPTUPLESIZE + len)) |
| { |
| *dstlen = HEAPTUPLESIZE + len; |
| return NULL; |
| } |
| |
| if (dstlen) |
| { |
| *dstlen = HEAPTUPLESIZE + len; |
| tuple = dst; |
| memset(tuple, 0, HEAPTUPLESIZE + len); |
| } |
| else |
| tuple = (HeapTuple) palloc0(HEAPTUPLESIZE + len); |
| |
| /* |
| * Allocate and zero the space needed. Note that the tuple body and |
| * HeapTupleData management structure are allocated in one chunk. |
| */ |
| tuple->t_data = td = (HeapTupleHeader) ((char *) tuple + HEAPTUPLESIZE); |
| |
| /* |
| * And fill in the information. Note we fill the Datum fields even though |
| * this tuple may never become a Datum. This lets HeapTupleHeaderGetDatum |
| * identify the tuple type if needed. |
| */ |
| tuple->t_len = len; |
| ItemPointerSetInvalid(&(tuple->t_self)); |
| tuple->t_tableOid = InvalidOid; |
| |
| HeapTupleHeaderSetDatumLength(td, len); |
| HeapTupleHeaderSetTypeId(td, tupleDescriptor->tdtypeid); |
| HeapTupleHeaderSetTypMod(td, tupleDescriptor->tdtypmod); |
| /* We also make sure that t_ctid is invalid unless explicitly set */ |
| ItemPointerSetInvalid(&(td->t_ctid)); |
| |
| HeapTupleHeaderSetNatts(td, numberOfAttributes); |
| td->t_hoff = hoff; |
| |
| heap_fill_tuple(tupleDescriptor, |
| values, |
| isnull, |
| (char *) td + hoff, |
| data_len, |
| &td->t_infomask, |
| (hasnull ? td->t_bits : NULL)); |
| |
| return tuple; |
| } |
| |
| /* |
| * heap_modify_tuple |
| * form a new tuple from an old tuple and a set of replacement values. |
| * |
| * The replValues, replIsnull, and doReplace arrays must be of the length |
| * indicated by tupleDesc->natts. The new tuple is constructed using the data |
| * from replValues/replIsnull at columns where doReplace is true, and using |
| * the data from the old tuple at columns where doReplace is false. |
| * |
| * The result is allocated in the current memory context. |
| */ |
| HeapTuple |
| heap_modify_tuple(HeapTuple tuple, |
| TupleDesc tupleDesc, |
| Datum *replValues, |
| bool *replIsnull, |
| bool *doReplace) |
| { |
| int numberOfAttributes = tupleDesc->natts; |
| int attoff; |
| Datum *values; |
| bool *isnull; |
| HeapTuple newTuple; |
| |
| /* |
| * allocate and fill values and isnull arrays from either the tuple or the |
| * repl information, as appropriate. |
| * |
| * NOTE: it's debatable whether to use heap_deform_tuple() here or just |
| * heap_getattr() only the non-replaced columns. The latter could win if |
| * there are many replaced columns and few non-replaced ones. However, |
| * heap_deform_tuple costs only O(N) while the heap_getattr way would cost |
| * O(N^2) if there are many non-replaced columns, so it seems better to |
| * err on the side of linear cost. |
| */ |
| values = (Datum *) palloc(numberOfAttributes * sizeof(Datum)); |
| isnull = (bool *) palloc(numberOfAttributes * sizeof(bool)); |
| |
| heap_deform_tuple(tuple, tupleDesc, values, isnull); |
| |
| for (attoff = 0; attoff < numberOfAttributes; attoff++) |
| { |
| if (doReplace[attoff]) |
| { |
| values[attoff] = replValues[attoff]; |
| isnull[attoff] = replIsnull[attoff]; |
| } |
| } |
| |
| /* |
| * create a new tuple from the values and isnull arrays |
| */ |
| newTuple = heap_form_tuple(tupleDesc, values, isnull); |
| |
| pfree(values); |
| pfree(isnull); |
| |
| /* |
| * copy the identification info of the old tuple: t_ctid, t_self |
| */ |
| newTuple->t_data->t_ctid = tuple->t_data->t_ctid; |
| newTuple->t_self = tuple->t_self; |
| newTuple->t_tableOid = tuple->t_tableOid; |
| |
| return newTuple; |
| } |
| |
| /* |
| * heap_modify_tuple_by_cols |
| * form a new tuple from an old tuple and a set of replacement values. |
| * |
| * This is like heap_modify_tuple, except that instead of specifying which |
| * column(s) to replace by a boolean map, an array of target column numbers |
| * is used. This is often more convenient when a fixed number of columns |
| * are to be replaced. The replCols, replValues, and replIsnull arrays must |
| * be of length nCols. Target column numbers are indexed from 1. |
| * |
| * The result is allocated in the current memory context. |
| */ |
| HeapTuple |
| heap_modify_tuple_by_cols(HeapTuple tuple, |
| TupleDesc tupleDesc, |
| int nCols, |
| int *replCols, |
| Datum *replValues, |
| bool *replIsnull) |
| { |
| int numberOfAttributes = tupleDesc->natts; |
| Datum *values; |
| bool *isnull; |
| HeapTuple newTuple; |
| int i; |
| |
| /* |
| * allocate and fill values and isnull arrays from the tuple, then replace |
| * selected columns from the input arrays. |
| */ |
| values = (Datum *) palloc(numberOfAttributes * sizeof(Datum)); |
| isnull = (bool *) palloc(numberOfAttributes * sizeof(bool)); |
| |
| heap_deform_tuple(tuple, tupleDesc, values, isnull); |
| |
| for (i = 0; i < nCols; i++) |
| { |
| int attnum = replCols[i]; |
| |
| if (attnum <= 0 || attnum > numberOfAttributes) |
| elog(ERROR, "invalid column number %d", attnum); |
| values[attnum - 1] = replValues[i]; |
| isnull[attnum - 1] = replIsnull[i]; |
| } |
| |
| /* |
| * create a new tuple from the values and isnull arrays |
| */ |
| newTuple = heap_form_tuple(tupleDesc, values, isnull); |
| |
| pfree(values); |
| pfree(isnull); |
| |
| /* |
| * copy the identification info of the old tuple: t_ctid, t_self |
| */ |
| newTuple->t_data->t_ctid = tuple->t_data->t_ctid; |
| newTuple->t_self = tuple->t_self; |
| newTuple->t_tableOid = tuple->t_tableOid; |
| |
| return newTuple; |
| } |
| |
| /* |
| * heap_deform_tuple |
| * Given a tuple, extract data into values/isnull arrays; this is |
| * the inverse of heap_form_tuple. |
| * |
| * Storage for the values/isnull arrays is provided by the caller; |
| * it should be sized according to tupleDesc->natts not |
| * HeapTupleHeaderGetNatts(tuple->t_data). |
| * |
| * Note that for pass-by-reference datatypes, the pointer placed |
| * in the Datum will point into the given tuple. |
| * |
| * When all or most of a tuple's fields need to be extracted, |
| * this routine will be significantly quicker than a loop around |
| * heap_getattr; the loop will become O(N^2) as soon as any |
| * noncacheable attribute offsets are involved. |
| */ |
| void |
| heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc, |
| Datum *values, bool *isnull) |
| { |
| HeapTupleHeader tup = tuple->t_data; |
| bool hasnulls = HeapTupleHasNulls(tuple); |
| int tdesc_natts = tupleDesc->natts; |
| int natts; /* number of atts to extract */ |
| int attnum; |
| char *tp; /* ptr to tuple data */ |
| uint32 off; /* offset in tuple data */ |
| bits8 *bp = tup->t_bits; /* ptr to null bitmap in tuple */ |
| bool slow = false; /* can we use/set attcacheoff? */ |
| |
| natts = HeapTupleHeaderGetNatts(tup); |
| |
| /* |
| * In inheritance situations, it is possible that the given tuple actually |
| * has more fields than the caller is expecting. Don't run off the end of |
| * the caller's arrays. |
| */ |
| natts = Min(natts, tdesc_natts); |
| |
| tp = (char *) tup + tup->t_hoff; |
| |
| off = 0; |
| |
| for (attnum = 0; attnum < natts; attnum++) |
| { |
| Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum); |
| |
| if (hasnulls && att_isnull(attnum, bp)) |
| { |
| values[attnum] = (Datum) 0; |
| isnull[attnum] = true; |
| slow = true; /* can't use attcacheoff anymore */ |
| continue; |
| } |
| |
| isnull[attnum] = false; |
| |
| if (!slow && thisatt->attcacheoff >= 0) |
| off = thisatt->attcacheoff; |
| else if (thisatt->attlen == -1) |
| { |
| /* |
| * We can only cache the offset for a varlena attribute if the |
| * offset is already suitably aligned, so that there would be no |
| * pad bytes in any case: then the offset will be valid for either |
| * an aligned or unaligned value. |
| */ |
| if (!slow && |
| off == att_align_nominal(off, thisatt->attalign)) |
| thisatt->attcacheoff = off; |
| else |
| { |
| off = att_align_pointer(off, thisatt->attalign, -1, |
| tp + off); |
| slow = true; |
| } |
| } |
| else |
| { |
| /* not varlena, so safe to use att_align_nominal */ |
| off = att_align_nominal(off, thisatt->attalign); |
| |
| if (!slow) |
| thisatt->attcacheoff = off; |
| |
| } |
| if (!slow && thisatt->attlen < 0) |
| slow = true; |
| |
| values[attnum] = fetchatt(thisatt, tp + off); |
| |
| off = att_addlength_pointer(off, thisatt->attlen, tp + off); |
| } |
| |
| /* |
| * If tuple doesn't have all the atts indicated by tupleDesc, read the |
| * rest as nulls or missing values as appropriate. |
| */ |
| for (; attnum < tdesc_natts; attnum++) |
| values[attnum] = getmissingattr(tupleDesc, attnum + 1, &isnull[attnum]); |
| } |
| |
| /* |
| * heap_freetuple |
| */ |
| void |
| heap_freetuple(HeapTuple htup) |
| { |
| pfree(htup); |
| } |
| |
| |
| /* |
| * heap_form_minimal_tuple |
| * construct a MinimalTuple from the given values[] and isnull[] arrays, |
| * which are of the length indicated by tupleDescriptor->natts |
| * |
| * This is exactly like heap_form_tuple() except that the result is a |
| * "minimal" tuple lacking a HeapTupleData header as well as room for system |
| * columns. |
| * |
| * The result is allocated in the current memory context. |
| */ |
| MinimalTuple |
| heap_form_minimal_tuple(TupleDesc tupleDescriptor, |
| Datum *values, |
| bool *isnull) |
| { |
| MinimalTuple tuple; /* return tuple */ |
| Size len, |
| data_len; |
| int hoff; |
| bool hasnull = false; |
| int numberOfAttributes = tupleDescriptor->natts; |
| int i; |
| |
| if (numberOfAttributes > MaxTupleAttributeNumber) |
| ereport(ERROR, |
| (errcode(ERRCODE_TOO_MANY_COLUMNS), |
| errmsg("number of columns (%d) exceeds limit (%d)", |
| numberOfAttributes, MaxTupleAttributeNumber))); |
| |
| /* |
| * Check for nulls |
| */ |
| for (i = 0; i < numberOfAttributes; i++) |
| { |
| if (isnull[i]) |
| { |
| hasnull = true; |
| break; |
| } |
| } |
| |
| /* |
| * Determine total space needed |
| */ |
| len = SizeofMinimalTupleHeader; |
| |
| if (hasnull) |
| len += BITMAPLEN(numberOfAttributes); |
| |
| hoff = len = MAXALIGN(len); /* align user data safely */ |
| |
| data_len = heap_compute_data_size(tupleDescriptor, values, isnull); |
| |
| len += data_len; |
| |
| /* |
| * Allocate and zero the space needed. |
| */ |
| tuple = (MinimalTuple) palloc0(len); |
| |
| /* |
| * And fill in the information. |
| */ |
| tuple->t_len = len; |
| HeapTupleHeaderSetNatts(tuple, numberOfAttributes); |
| tuple->t_hoff = hoff + MINIMAL_TUPLE_OFFSET; |
| |
| heap_fill_tuple(tupleDescriptor, |
| values, |
| isnull, |
| (char *) tuple + hoff, |
| data_len, |
| &tuple->t_infomask, |
| (hasnull ? tuple->t_bits : NULL)); |
| |
| return tuple; |
| } |
| |
| /* |
| * heap_free_minimal_tuple |
| */ |
| void |
| heap_free_minimal_tuple(MinimalTuple mtup) |
| { |
| pfree(mtup); |
| } |
| |
| /* |
| * heap_copy_minimal_tuple |
| * copy a MinimalTuple |
| * |
| * The result is allocated in the current memory context. |
| */ |
| MinimalTuple |
| heap_copy_minimal_tuple(MinimalTuple mtup) |
| { |
| MinimalTuple result; |
| |
| result = (MinimalTuple) palloc(mtup->t_len); |
| memcpy(result, mtup, mtup->t_len); |
| return result; |
| } |
| |
| /* |
| * heap_tuple_from_minimal_tuple |
| * create a HeapTuple by copying from a MinimalTuple; |
| * system columns are filled with zeroes |
| * |
| * The result is allocated in the current memory context. |
| * The HeapTuple struct, tuple header, and tuple data are all allocated |
| * as a single palloc() block. |
| */ |
| HeapTuple |
| heap_tuple_from_minimal_tuple(MinimalTuple mtup) |
| { |
| HeapTuple result; |
| uint32 len = mtup->t_len + MINIMAL_TUPLE_OFFSET; |
| |
| result = (HeapTuple) palloc(HEAPTUPLESIZE + len); |
| result->t_len = len; |
| ItemPointerSetInvalid(&(result->t_self)); |
| result->t_tableOid = InvalidOid; |
| result->t_data = (HeapTupleHeader) ((char *) result + HEAPTUPLESIZE); |
| memcpy((char *) result->t_data + MINIMAL_TUPLE_OFFSET, mtup, mtup->t_len); |
| memset(result->t_data, 0, offsetof(HeapTupleHeaderData, t_infomask2)); |
| return result; |
| } |
| |
| /* |
| * minimal_tuple_from_heap_tuple |
| * create a MinimalTuple by copying from a HeapTuple |
| * |
| * The result is allocated in the current memory context. |
| */ |
| MinimalTuple |
| minimal_tuple_from_heap_tuple(HeapTuple htup) |
| { |
| MinimalTuple result; |
| uint32 len; |
| |
| Assert(htup->t_len > MINIMAL_TUPLE_OFFSET); |
| len = htup->t_len - MINIMAL_TUPLE_OFFSET; |
| result = (MinimalTuple) palloc(len); |
| memcpy(result, (char *) htup->t_data + MINIMAL_TUPLE_OFFSET, len); |
| result->t_len = len; |
| return result; |
| } |
| |
| /* |
| * This mainly exists so JIT can inline the definition, but it's also |
| * sometimes useful in debugging sessions. |
| */ |
| size_t |
| varsize_any(void *p) |
| { |
| return VARSIZE_ANY(p); |
| } |