From 41f7dbbc560a026e2e311896056284fd60796cf0 Mon Sep 17 00:00:00 2001 From: David Rowley Date: Tue, 31 Dec 2024 09:19:24 +1300 Subject: [PATCH v1 1/3] Precalculate CompactAttribute's attcacheoff This allows code to be removed from the tuple deform routines which shrinks down the code a little, which can make it run more quickly. This also makes a dedicated deformer loop to deform the portion of the tuple which has a known offset, which makes deforming much faster when a leading set of the table's columns are non-NULL values and fixed-width types. --- contrib/dblink/dblink.c | 2 + contrib/pg_buffercache/pg_buffercache_pages.c | 1 + contrib/pg_visibility/pg_visibility.c | 2 + src/backend/access/brin/brin_tuple.c | 1 + src/backend/access/common/heaptuple.c | 317 ++++++---------- src/backend/access/common/indextuple.c | 355 +++++++----------- src/backend/access/common/tupdesc.c | 56 +++ src/backend/access/gin/ginutil.c | 1 + src/backend/access/gist/gistscan.c | 1 + src/backend/access/spgist/spgutils.c | 4 +- src/backend/access/transam/twophase.c | 1 + src/backend/access/transam/xlogfuncs.c | 1 + src/backend/backup/basebackup_copy.c | 3 + src/backend/catalog/index.c | 2 + src/backend/catalog/pg_publication.c | 1 + src/backend/catalog/toasting.c | 6 + src/backend/commands/explain.c | 1 + src/backend/commands/functioncmds.c | 1 + src/backend/commands/sequence.c | 1 + src/backend/commands/tablecmds.c | 4 + src/backend/executor/execSRF.c | 2 + src/backend/executor/execTuples.c | 303 +++++++-------- src/backend/executor/nodeFunctionscan.c | 2 + src/backend/parser/parse_relation.c | 4 +- src/backend/parser/parse_target.c | 2 + .../libpqwalreceiver/libpqwalreceiver.c | 1 + src/backend/replication/walsender.c | 5 + src/backend/utils/adt/acl.c | 1 + src/backend/utils/adt/genfile.c | 1 + src/backend/utils/adt/lockfuncs.c | 1 + src/backend/utils/adt/orderedsetaggs.c | 1 + src/backend/utils/adt/pgstatfuncs.c | 5 + src/backend/utils/adt/tsvector_op.c | 1 + src/backend/utils/cache/relcache.c | 20 +- src/backend/utils/fmgr/funcapi.c | 6 + src/backend/utils/init/postinit.c | 1 + src/backend/utils/misc/guc_funcs.c | 5 + src/include/access/htup_details.h | 19 +- src/include/access/itup.h | 20 +- src/include/access/tupdesc.h | 12 + src/include/access/tupmacs.h | 57 +++ src/include/executor/tuptable.h | 9 +- src/pl/plpgsql/src/pl_comp.c | 2 + .../modules/test_predtest/test_predtest.c | 1 + 44 files changed, 613 insertions(+), 629 deletions(-) diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c index 8bf8fc8ea2f..82dbabc8927 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -1045,6 +1045,7 @@ materializeQueryResult(FunctionCallInfo fcinfo, TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status", TEXTOID, -1, 0); attinmeta = TupleDescGetAttInMetadata(tupdesc); + TupleDescFinalize(tupdesc); oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory); tupstore = tuplestore_begin_heap(true, false, work_mem); @@ -1534,6 +1535,7 @@ dblink_get_pkey(PG_FUNCTION_ARGS) * C strings */ attinmeta = TupleDescGetAttInMetadata(tupdesc); + TupleDescFinalize(tupdesc); funcctx->attinmeta = attinmeta; if ((results != NULL) && (indnkeyatts > 0)) diff --git a/contrib/pg_buffercache/pg_buffercache_pages.c b/contrib/pg_buffercache/pg_buffercache_pages.c index 0c58e4b265c..976c38b9197 100644 --- a/contrib/pg_buffercache/pg_buffercache_pages.c +++ b/contrib/pg_buffercache/pg_buffercache_pages.c @@ -174,6 +174,7 @@ pg_buffercache_pages(PG_FUNCTION_ARGS) TupleDescInitEntry(tupledesc, (AttrNumber) 9, "pinning_backends", INT4OID, -1, 0); + TupleDescFinalize(tupledesc); fctx->tupdesc = BlessTupleDesc(tupledesc); /* Allocate NBuffers worth of BufferCachePagesRec records. */ diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c index 715f5cdd17c..7047895c5e8 100644 --- a/contrib/pg_visibility/pg_visibility.c +++ b/contrib/pg_visibility/pg_visibility.c @@ -469,6 +469,8 @@ pg_visibility_tupdesc(bool include_blkno, bool include_pd) TupleDescInitEntry(tupdesc, ++a, "pd_all_visible", BOOLOID, -1, 0); Assert(a == maxattr); + TupleDescFinalize(tupdesc); + return BlessTupleDesc(tupdesc); } diff --git a/src/backend/access/brin/brin_tuple.c b/src/backend/access/brin/brin_tuple.c index 43850ce8f48..1e0c2a44b7a 100644 --- a/src/backend/access/brin/brin_tuple.c +++ b/src/backend/access/brin/brin_tuple.c @@ -84,6 +84,7 @@ brtuple_disk_tupdesc(BrinDesc *brdesc) MemoryContextSwitchTo(oldcxt); + TupleDescFinalize(tupdesc); brdesc->bd_disktdesc = tupdesc; } diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c index b7820d692e2..c24ba949c11 100644 --- a/src/backend/access/common/heaptuple.c +++ b/src/backend/access/common/heaptuple.c @@ -497,20 +497,8 @@ heap_attisnull(HeapTuple tup, int attnum, TupleDesc tupleDesc) /* ---------------- * nocachegetattr * - * This only gets called from fastgetattr(), 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 + * This only gets called from fastgetattr(), in cases where the + * attcacheoff is not set. * * 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 @@ -522,194 +510,101 @@ nocachegetattr(HeapTuple tup, int attnum, TupleDesc tupleDesc) { + CompactAttribute *cattr; 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 */ + int startAttr; + int firstnullattr; + bool hasnulls = HeapTupleHasNulls(tup); - /* ---------------- - * 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. - * ---------------- + /* + * If there are no NULLs before the required attnum, then we can start at + * the highest attribute with a known offset, or the first attribute if + * none have a cached offset. If the tuple has no variable width types, + * then we can use a slightly cheaper method of offset calculation, as we + * just need to add the attlen to the aligned offset when skipping over + * columns. When the tuple contains variable-width types, we must use + * att_addlength_pointer(), which does a bit more branching and is + * slightly less efficient. */ - 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; + if (hasnulls) + firstnullattr = first_null_attr(bp, attnum); + else + firstnullattr = attnum; - for (i = 0; i < byte; i++) - { - if (bp[i] != 0xFF) - { - slow = true; - break; - } - } - } + if (tupleDesc->firstNonCachedOffAttr >= 0) + { + startAttr = Min(tupleDesc->firstNonCachedOffAttr - 1, firstnullattr); + off = TupleDescCompactAttr(tupleDesc, startAttr)->attcacheoff; + } + else + { + startAttr = 0; + off = 0; } tp = (char *) td + td->t_hoff; - if (!slow) + if (hasnulls) { - CompactAttribute *att; + for (int i = startAttr; i < attnum; i++) + { + CompactAttribute *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 = TupleDescCompactAttr(tupleDesc, attnum); - if (att->attcacheoff >= 0) - return fetchatt(att, tp + att->attcacheoff); + if (att_isnull(i, bp)) + continue; - /* - * 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; + att = TupleDescCompactAttr(tupleDesc, i); - for (j = 0; j <= attnum; j++) - { - if (TupleDescCompactAttr(tupleDesc, j)->attlen <= 0) - { - slow = true; - break; - } - } + off = att_pointer_alignby(off, + att->attalignby, + att->attlen, + tp + off); + off = att_addlength_pointer(off, att->attlen, tp + off); } + cattr = TupleDescCompactAttr(tupleDesc, attnum); + off = att_pointer_alignby(off, + cattr->attalignby, + cattr->attlen, + tp + off); } - - if (!slow) + else if (!HeapTupleHasVarWidth(tup)) { - 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. - */ - TupleDescCompactAttr(tupleDesc, 0)->attcacheoff = 0; - - /* we might have set some offsets in the slow path previously */ - while (j < natts && TupleDescCompactAttr(tupleDesc, j)->attcacheoff > 0) - j++; - - off = TupleDescCompactAttr(tupleDesc, j - 1)->attcacheoff + - TupleDescCompactAttr(tupleDesc, j - 1)->attlen; - - for (; j < natts; j++) + for (int i = startAttr; i < attnum; i++) { - CompactAttribute *att = TupleDescCompactAttr(tupleDesc, j); - - if (att->attlen <= 0) - break; + CompactAttribute *att = TupleDescCompactAttr(tupleDesc, i); off = att_nominal_alignby(off, att->attalignby); - - att->attcacheoff = off; - off += att->attlen; } - - Assert(j > attnum); - - off = TupleDescCompactAttr(tupleDesc, attnum)->attcacheoff; + cattr = TupleDescCompactAttr(tupleDesc, attnum); + off = att_nominal_alignby(off, cattr->attalignby); } 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" */ + for (int i = startAttr; i < attnum; i++) { CompactAttribute *att = TupleDescCompactAttr(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_nominal_alignby(off, att->attalignby)) - att->attcacheoff = off; - else - { - off = att_pointer_alignby(off, att->attalignby, -1, - tp + off); - usecache = false; - } - } - else - { - /* not varlena, so safe to use att_nominal_alignby */ - off = att_nominal_alignby(off, att->attalignby); - - if (usecache) - att->attcacheoff = off; - } - - if (i == attnum) - break; - + off = att_pointer_alignby(off, + att->attalignby, + att->attlen, + tp + off); off = att_addlength_pointer(off, att->attlen, tp + off); - if (usecache && att->attlen <= 0) - usecache = false; } + cattr = TupleDescCompactAttr(tupleDesc, attnum); + off = att_pointer_alignby(off, + cattr->attalignby, + cattr->attlen, + tp + off); } - return fetchatt(TupleDescCompactAttr(tupleDesc, attnum), tp + off); + return fetchatt(cattr, tp + off); } /* ---------------- @@ -1354,7 +1249,8 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc, 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? */ + int cacheoffattrs; + int firstnullattr; natts = HeapTupleHeaderGetNatts(tup); @@ -1364,60 +1260,77 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc, * the caller's arrays. */ natts = Min(natts, tdesc_natts); + cacheoffattrs = Min(tupleDesc->firstNonCachedOffAttr, natts); - tp = (char *) tup + tup->t_hoff; + if (hasnulls) + { + firstnullattr = first_null_attr(bp, natts); + cacheoffattrs = Min(cacheoffattrs, firstnullattr); + } + else + firstnullattr = natts; + tp = (char *) tup + tup->t_hoff; off = 0; - for (attnum = 0; attnum < natts; attnum++) + for (attnum = 0; attnum < cacheoffattrs; attnum++) { - CompactAttribute *thisatt = TupleDescCompactAttr(tupleDesc, attnum); + CompactAttribute *cattr = TupleDescCompactAttr(tupleDesc, attnum); + + Assert(cattr->attcacheoff >= 0); + + values[attnum] = fetch_att(tp + cattr->attcacheoff, cattr->attbyval, + cattr->attlen); + isnull[attnum] = false; + off = cattr->attcacheoff + cattr->attlen; + } - if (hasnulls && att_isnull(attnum, bp)) + for (; attnum < firstnullattr; attnum++) + { + CompactAttribute *cattr = TupleDescCompactAttr(tupleDesc, attnum); + + if (cattr->attlen == -1) + off = att_pointer_alignby(off, cattr->attalignby, -1, + tp + off); + else { - values[attnum] = (Datum) 0; - isnull[attnum] = true; - slow = true; /* can't use attcacheoff anymore */ - continue; + /* not varlena, so safe to use att_nominal_alignby */ + off = att_nominal_alignby(off, cattr->attalignby); } isnull[attnum] = false; + values[attnum] = fetchatt(cattr, tp + off); - if (!slow && thisatt->attcacheoff >= 0) - off = thisatt->attcacheoff; - else if (thisatt->attlen == -1) + off = att_addlength_pointer(off, cattr->attlen, tp + off); + } + + for (; attnum < natts; attnum++) + { + CompactAttribute *cattr; + + Assert(hasnulls); + + if (att_isnull(attnum, bp)) { - /* - * 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_nominal_alignby(off, thisatt->attalignby)) - thisatt->attcacheoff = off; - else - { - off = att_pointer_alignby(off, thisatt->attalignby, -1, - tp + off); - slow = true; - } + values[attnum] = (Datum) 0; + isnull[attnum] = true; + continue; } + + cattr = TupleDescCompactAttr(tupleDesc, attnum); + if (cattr->attlen == -1) + off = att_pointer_alignby(off, cattr->attalignby, -1, + tp + off); else { /* not varlena, so safe to use att_nominal_alignby */ - off = att_nominal_alignby(off, thisatt->attalignby); - - if (!slow) - thisatt->attcacheoff = off; + off = att_nominal_alignby(off, cattr->attalignby); } - values[attnum] = fetchatt(thisatt, tp + off); - - off = att_addlength_pointer(off, thisatt->attlen, tp + off); + isnull[attnum] = false; + values[attnum] = fetchatt(cattr, tp + off); - if (thisatt->attlen <= 0) - slow = true; /* can't use attcacheoff anymore */ + off = att_addlength_pointer(off, cattr->attlen, tp + off); } /* diff --git a/src/backend/access/common/indextuple.c b/src/backend/access/common/indextuple.c index 3efa3889c6f..8d0c273cdf6 100644 --- a/src/backend/access/common/indextuple.c +++ b/src/backend/access/common/indextuple.c @@ -223,18 +223,6 @@ index_form_tuple_context(TupleDesc tupleDescriptor, * * This gets called from index_getattr() macro, and only in cases * where we can't use 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 * ---------------- */ Datum @@ -242,205 +230,126 @@ nocache_index_getattr(IndexTuple tup, int attnum, TupleDesc tupleDesc) { + CompactAttribute *cattr; char *tp; /* ptr to data part of tuple */ bits8 *bp = NULL; /* ptr to null bitmap in tuple */ - bool slow = false; /* do we have to walk attrs? */ int data_off; /* tuple data offset */ int off; /* current offset within data */ + int startAttr; + int firstnullattr; + bool hasnulls = IndexTupleHasNulls(tup); + int i; - /* ---------------- - * 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 there are no NULLs before the required attnum, then we can start at + * the highest attribute with a known offset, or the first attribute if + * none have a cached offset. If the tuple has no variable width types, + * which is common with indexes, then we can use a slightly cheaper method + * of offset calculation, as we just need to add the attlen to the aligned + * offset when skipping over columns. When the tuple contains + * variable-width types, we must use att_addlength_pointer(), which does a + * bit more branching and is slightly less efficient. + */ data_off = IndexInfoFindDataOffset(tup->t_info); + tp = (char *) tup + data_off; - attnum--; - - if (IndexTupleHasNulls(tup)) + /* + * Find the first NULL column, or if there's none set the first NULL to + * attnum so that we can forego NULL checking all the way to attnum. + */ + if (hasnulls) { - /* - * there's a null somewhere in the tuple - * - * check to see if desired att is null - */ - - /* XXX "knows" t_bits are just after fixed tuple header! */ bp = (bits8 *) ((char *) tup + sizeof(IndexTupleData)); - - /* - * Now 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; - } - } - } - } + firstnullattr = first_null_attr(bp, attnum); } + else + firstnullattr = attnum; - tp = (char *) tup + data_off; - - if (!slow) + if (tupleDesc->firstNonCachedOffAttr >= 0) { - CompactAttribute *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 = TupleDescCompactAttr(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 (IndexTupleHasVarwidths(tup)) - { - int j; - - for (j = 0; j <= attnum; j++) - { - if (TupleDescCompactAttr(tupleDesc, j)->attlen <= 0) - { - slow = true; - break; - } - } - } + startAttr = Min(tupleDesc->firstNonCachedOffAttr - 1, firstnullattr); + off = TupleDescCompactAttr(tupleDesc, startAttr)->attcacheoff; } - - if (!slow) + else { - 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. - */ - TupleDescCompactAttr(tupleDesc, 0)->attcacheoff = 0; + startAttr = 0; + off = 0; + } - /* we might have set some offsets in the slow path previously */ - while (j < natts && TupleDescCompactAttr(tupleDesc, j)->attcacheoff > 0) - j++; + /* Handle tuples with var-width attributes */ + if (IndexTupleHasVarwidths(tup)) + { + /* Calculate the offset up until the first NULL */ + for (i = startAttr; i < firstnullattr; i++) + { + cattr = TupleDescCompactAttr(tupleDesc, i); - off = TupleDescCompactAttr(tupleDesc, j - 1)->attcacheoff + - TupleDescCompactAttr(tupleDesc, j - 1)->attlen; + off = att_pointer_alignby(off, + cattr->attalignby, + cattr->attlen, + tp + off); + off = att_addlength_pointer(off, cattr->attlen, tp + off); + } - for (; j < natts; j++) + /* Calculate the offset for any remaining columns. */ + for (; i < attnum; i++) { - CompactAttribute *att = TupleDescCompactAttr(tupleDesc, j); - - if (att->attlen <= 0) - break; + Assert(hasnulls); - off = att_nominal_alignby(off, att->attalignby); + if (att_isnull(i, bp)) + continue; - att->attcacheoff = off; + cattr = TupleDescCompactAttr(tupleDesc, i); - off += att->attlen; + off = att_pointer_alignby(off, + cattr->attalignby, + cattr->attlen, + tp + off); + off = att_addlength_pointer(off, cattr->attlen, tp + off); } - - Assert(j > attnum); - - off = TupleDescCompactAttr(tupleDesc, attnum)->attcacheoff; } else { - bool usecache = true; - int i; + /* Handle tuples with only fixed-width attributes */ - /* - * 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" */ + /* Calculate the offset up until the first NULL */ + for (i = startAttr; i < firstnullattr; i++) { - CompactAttribute *att = TupleDescCompactAttr(tupleDesc, i); - - if (IndexTupleHasNulls(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_nominal_alignby(off, att->attalignby)) - att->attcacheoff = off; - else - { - off = att_pointer_alignby(off, att->attalignby, -1, - tp + off); - usecache = false; - } - } - else - { - /* not varlena, so safe to use att_nominal_alignby */ - off = att_nominal_alignby(off, att->attalignby); + cattr = TupleDescCompactAttr(tupleDesc, i); + + Assert(cattr->attlen > 0); + off = att_pointer_alignby(off, + cattr->attalignby, + cattr->attlen, + tp + off); + off += cattr->attlen; + } - if (usecache) - att->attcacheoff = off; - } + /* Calculate the offset for any remaining columns. */ + for (; i < attnum; i++) + { + Assert(hasnulls); - if (i == attnum) - break; + if (att_isnull(i, bp)) + continue; - off = att_addlength_pointer(off, att->attlen, tp + off); + cattr = TupleDescCompactAttr(tupleDesc, i); - if (usecache && att->attlen <= 0) - usecache = false; + Assert(cattr->attlen > 0); + off = att_pointer_alignby(off, + cattr->attalignby, + cattr->attlen, + tp + off); + off += cattr->attlen; } } - return fetchatt(TupleDescCompactAttr(tupleDesc, attnum), tp + off); + cattr = TupleDescCompactAttr(tupleDesc, attnum); + off = att_pointer_alignby(off, cattr->attalignby, + cattr->attlen, tp + off); + return fetchatt(cattr, tp + off); } /* @@ -481,62 +390,76 @@ index_deform_tuple_internal(TupleDesc tupleDescriptor, char *tp, bits8 *bp, int hasnulls) { int natts = tupleDescriptor->natts; /* number of atts to extract */ - int attnum; + int attnum = 0; int off = 0; /* offset in tuple data */ - bool slow = false; /* can we use/set attcacheoff? */ + int cacheoffattrs; + int firstnullattr; /* Assert to protect callers who allocate fixed-size arrays */ Assert(natts <= INDEX_MAX_KEYS); - for (attnum = 0; attnum < natts; attnum++) + cacheoffattrs = Min(tupleDescriptor->firstNonCachedOffAttr, natts); + + if (hasnulls) { - CompactAttribute *thisatt = TupleDescCompactAttr(tupleDescriptor, attnum); + firstnullattr = first_null_attr(bp, natts); + cacheoffattrs = Min(cacheoffattrs, firstnullattr); + } + else + firstnullattr = natts; + + if (attnum < cacheoffattrs) + { + CompactAttribute *cattr; - if (hasnulls && att_isnull(attnum, bp)) + do { - values[attnum] = (Datum) 0; - isnull[attnum] = true; - slow = true; /* can't use attcacheoff anymore */ - continue; - } + cattr = TupleDescCompactAttr(tupleDescriptor, attnum); + + Assert(cattr->attcacheoff >= 0); + + values[attnum] = fetch_att(tp + cattr->attcacheoff, cattr->attbyval, + cattr->attlen); + isnull[attnum] = false; + } while (++attnum < cacheoffattrs); + + off = cattr->attcacheoff + cattr->attlen; + } + + for (; attnum < firstnullattr; attnum++) + { + CompactAttribute *cattr = TupleDescCompactAttr(tupleDescriptor, attnum); + + off = att_pointer_alignby(off, cattr->attalignby, cattr->attlen, + tp + off); isnull[attnum] = false; + values[attnum] = fetchatt(cattr, tp + off); - 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_nominal_alignby(off, thisatt->attalignby)) - thisatt->attcacheoff = off; - else - { - off = att_pointer_alignby(off, thisatt->attalignby, -1, - tp + off); - slow = true; - } - } - else - { - /* not varlena, so safe to use att_nominal_alignby */ - off = att_nominal_alignby(off, thisatt->attalignby); + off = att_addlength_pointer(off, cattr->attlen, tp + off); + } + + for (; attnum < natts; attnum++) + { + CompactAttribute *cattr; + + Assert(hasnulls); - if (!slow) - thisatt->attcacheoff = off; + if (att_isnull(attnum, bp)) + { + values[attnum] = (Datum) 0; + isnull[attnum] = true; + continue; } - values[attnum] = fetchatt(thisatt, tp + off); + cattr = TupleDescCompactAttr(tupleDescriptor, attnum); + off = att_pointer_alignby(off, cattr->attalignby, cattr->attlen, + tp + off); - off = att_addlength_pointer(off, thisatt->attlen, tp + off); + isnull[attnum] = false; + values[attnum] = fetchatt(cattr, tp + off); - if (thisatt->attlen <= 0) - slow = true; /* can't use attcacheoff anymore */ + off = att_addlength_pointer(off, cattr->attlen, tp + off); } } diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c index bcd1ddcc68b..4aebb0190f8 100644 --- a/src/backend/access/common/tupdesc.c +++ b/src/backend/access/common/tupdesc.c @@ -238,6 +238,9 @@ CreateTupleDesc(int natts, Form_pg_attribute *attrs) memcpy(TupleDescAttr(desc, i), attrs[i], ATTRIBUTE_FIXED_PART_SIZE); populate_compact_attribute(desc, i); } + + TupleDescFinalize(desc); + return desc; } @@ -282,6 +285,8 @@ CreateTupleDescCopy(TupleDesc tupdesc) desc->tdtypeid = tupdesc->tdtypeid; desc->tdtypmod = tupdesc->tdtypmod; + TupleDescFinalize(desc); + return desc; } @@ -328,6 +333,8 @@ CreateTupleDescTruncatedCopy(TupleDesc tupdesc, int natts) desc->tdtypeid = tupdesc->tdtypeid; desc->tdtypmod = tupdesc->tdtypmod; + TupleDescFinalize(desc); + return desc; } @@ -413,6 +420,8 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc) desc->tdtypeid = tupdesc->tdtypeid; desc->tdtypmod = tupdesc->tdtypmod; + TupleDescFinalize(desc); + return desc; } @@ -455,6 +464,8 @@ TupleDescCopy(TupleDesc dst, TupleDesc src) * source's refcount would be wrong in any case.) */ dst->tdrefcount = -1; + + TupleDescFinalize(dst); } /* @@ -463,6 +474,9 @@ TupleDescCopy(TupleDesc dst, TupleDesc src) * descriptor to another. * * !!! Constraints and defaults are not copied !!! + * + * The caller must take care of calling TupleDescFinalize() on once all + * TupleDesc changes have been made. */ void TupleDescCopyEntry(TupleDesc dst, AttrNumber dstAttno, @@ -495,6 +509,46 @@ TupleDescCopyEntry(TupleDesc dst, AttrNumber dstAttno, populate_compact_attribute(dst, dstAttno - 1); } +/* + * TupleDescFinalize + * Finalize the given TupleDesc. This must be called after the + * attributes arrays have been populated or adjusted by any code. + * + * Must be called after populate_compact_attribute() + */ +void +TupleDescFinalize(TupleDesc tupdesc) +{ + int firstNonCachedOffAttr = -1; + int firstByRefAttr = tupdesc->natts; + int offp = 0; + + for (int i = 0; i < tupdesc->natts; i++) + { + CompactAttribute *cattr = TupleDescCompactAttr(tupdesc, i); + + if (!cattr->attbyval) + firstByRefAttr = Min(firstByRefAttr, i); + + /* + * We can't cache the offset for the first varlena attr as the + * alignment for those depends on 1 vs 4 byte headers, however we + * possibily could cache the first attlen == -2 attr. Worthwhile? + */ + if (cattr->attlen <= 0) + break; + + offp = att_nominal_alignby(offp, cattr->attalignby); + cattr->attcacheoff = offp; + + offp += cattr->attlen; + firstNonCachedOffAttr = i + 1; + } + + tupdesc->firstNonCachedOffAttr = firstNonCachedOffAttr; + tupdesc->firstByRefAttr = firstByRefAttr; +} + /* * Free a TupleDesc including all substructure */ @@ -1082,6 +1136,8 @@ BuildDescFromLists(const List *names, const List *types, const List *typmods, co TupleDescInitEntryCollation(desc, attnum, attcollation); } + TupleDescFinalize(desc); + return desc; } diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c index 605f80aad39..a7286615f5b 100644 --- a/src/backend/access/gin/ginutil.c +++ b/src/backend/access/gin/ginutil.c @@ -128,6 +128,7 @@ initGinState(GinState *state, Relation index) attr->attndims); TupleDescInitEntryCollation(state->tupdesc[i], (AttrNumber) 2, attr->attcollation); + TupleDescFinalize(state->tupdesc[i]); } /* diff --git a/src/backend/access/gist/gistscan.c b/src/backend/access/gist/gistscan.c index 01b8ff0b6fa..6f58ba6cf95 100644 --- a/src/backend/access/gist/gistscan.c +++ b/src/backend/access/gist/gistscan.c @@ -201,6 +201,7 @@ gistrescan(IndexScanDesc scan, ScanKey key, int nkeys, attno - 1)->atttypid, -1, 0); } + TupleDescFinalize(so->giststate->fetchTupdesc); scan->xs_hitupdesc = so->giststate->fetchTupdesc; /* Also create a memory context that will hold the returned tuples */ diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c index a60ec85e8be..391e7a4c9a1 100644 --- a/src/backend/access/spgist/spgutils.c +++ b/src/backend/access/spgist/spgutils.c @@ -334,11 +334,9 @@ getSpGistTupleDesc(Relation index, SpGistTypeDesc *keyType) /* We shouldn't need to bother with making these valid: */ att->attcompression = InvalidCompressionMethod; att->attcollation = InvalidOid; - /* In case we changed typlen, we'd better reset following offsets */ - for (int i = spgFirstIncludeColumn; i < outTupDesc->natts; i++) - TupleDescCompactAttr(outTupDesc, i)->attcacheoff = -1; populate_compact_attribute(outTupDesc, spgKeyColumn); + TupleDescFinalize(outTupDesc); } return outTupDesc; } diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c index 3bc85986829..31956d2d0a8 100644 --- a/src/backend/access/transam/twophase.c +++ b/src/backend/access/transam/twophase.c @@ -744,6 +744,7 @@ pg_prepared_xact(PG_FUNCTION_ARGS) TupleDescInitEntry(tupdesc, (AttrNumber) 5, "dbid", OIDOID, -1, 0); + TupleDescFinalize(tupdesc); funcctx->tuple_desc = BlessTupleDesc(tupdesc); /* diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c index 339cb75c3ad..fbc116b747f 100644 --- a/src/backend/access/transam/xlogfuncs.c +++ b/src/backend/access/transam/xlogfuncs.c @@ -401,6 +401,7 @@ pg_walfile_name_offset(PG_FUNCTION_ARGS) INT4OID, -1, 0); resultTupleDesc = BlessTupleDesc(resultTupleDesc); + TupleDescFinalize(resultTupleDesc); /* * xlogfilename diff --git a/src/backend/backup/basebackup_copy.c b/src/backend/backup/basebackup_copy.c index 8bb8d3939fe..d227bfad384 100644 --- a/src/backend/backup/basebackup_copy.c +++ b/src/backend/backup/basebackup_copy.c @@ -357,6 +357,8 @@ SendXlogRecPtrResult(XLogRecPtr ptr, TimeLineID tli) */ TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "tli", INT8OID, -1, 0); + TupleDescFinalize(tupdesc); + /* send RowDescription */ tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual); @@ -388,6 +390,7 @@ SendTablespaceList(List *tablespaces) TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "spcoid", OIDOID, -1, 0); TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "spclocation", TEXTOID, -1, 0); TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 3, "size", INT8OID, -1, 0); + TupleDescFinalize(tupdesc); /* send RowDescription */ tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual); diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 8dea58ad96b..56b46385a0b 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -481,6 +481,8 @@ ConstructTupleDescriptor(Relation heapRelation, populate_compact_attribute(indexTupDesc, i); } + TupleDescFinalize(indexTupDesc); + pfree(amroutine); return indexTupDesc; diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c index 7aa3f179924..219190720a3 100644 --- a/src/backend/catalog/pg_publication.c +++ b/src/backend/catalog/pg_publication.c @@ -1230,6 +1230,7 @@ pg_get_publication_tables(PG_FUNCTION_ARGS) TupleDescInitEntry(tupdesc, (AttrNumber) 4, "qual", PG_NODE_TREEOID, -1, 0); + TupleDescFinalize(tupdesc); funcctx->tuple_desc = BlessTupleDesc(tupdesc); funcctx->user_fctx = table_infos; diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c index 874a8fc89ad..8c1fede1090 100644 --- a/src/backend/catalog/toasting.c +++ b/src/backend/catalog/toasting.c @@ -229,6 +229,12 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, TupleDescAttr(tupdesc, 1)->attcompression = InvalidCompressionMethod; TupleDescAttr(tupdesc, 2)->attcompression = InvalidCompressionMethod; + populate_compact_attribute(tupdesc, 0); + populate_compact_attribute(tupdesc, 1); + populate_compact_attribute(tupdesc, 2); + + TupleDescFinalize(tupdesc); + /* * Toast tables for regular relations go in pg_toast; those for temp * relations go into the per-backend temp-toast-table namespace. diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 5a6390631eb..26eee4ace42 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -281,6 +281,7 @@ ExplainResultDesc(ExplainStmt *stmt) tupdesc = CreateTemplateTupleDesc(1); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "QUERY PLAN", result_type, -1, 0); + TupleDescFinalize(tupdesc); return tupdesc; } diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index 8a435cd93db..bf73ef7d0a3 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -2423,6 +2423,7 @@ CallStmtResultDesc(CallStmt *stmt) -1, 0); } + TupleDescFinalize(tupdesc); } return tupdesc; diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 51567994126..b26cd8e642e 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -1810,6 +1810,7 @@ pg_get_sequence_data(PG_FUNCTION_ARGS) TupleDescInitEntry(resultTupleDesc, (AttrNumber) 3, "page_lsn", LSNOID, -1, 0); resultTupleDesc = BlessTupleDesc(resultTupleDesc); + TupleDescFinalize(resultTupleDesc); init_sequence(relid, &elm, &seqrel); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 1d9565b09fc..89e3dc4a6a9 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -1029,6 +1029,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, } } + TupleDescFinalize(descriptor); + /* * For relations with table AM and partitioned tables, select access * method to use: an explicitly indicated one, or (in the case of a @@ -1447,6 +1449,8 @@ BuildDescForRelation(const List *columns) populate_compact_attribute(desc, attnum - 1); } + TupleDescFinalize(desc); + return desc; } diff --git a/src/backend/executor/execSRF.c b/src/backend/executor/execSRF.c index a03fe780a02..3267f129b60 100644 --- a/src/backend/executor/execSRF.c +++ b/src/backend/executor/execSRF.c @@ -272,6 +272,7 @@ ExecMakeTableFunctionResult(SetExprState *setexpr, funcrettype, -1, 0); + TupleDescFinalize(tupdesc); rsinfo.setDesc = tupdesc; } MemoryContextSwitchTo(oldcontext); @@ -776,6 +777,7 @@ init_sexpr(Oid foid, Oid input_collation, Expr *node, funcrettype, -1, 0); + TupleDescFinalize(tupdesc); sexpr->funcResultDesc = tupdesc; sexpr->funcReturnsTuple = false; } diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c index b0dc2cfa66f..6d33f494a70 100644 --- a/src/backend/executor/execTuples.c +++ b/src/backend/executor/execTuples.c @@ -992,118 +992,6 @@ tts_buffer_heap_store_tuple(TupleTableSlot *slot, HeapTuple tuple, } } -/* - * slot_deform_heap_tuple_internal - * An always inline helper function for use in slot_deform_heap_tuple to - * allow the compiler to emit specialized versions of this function for - * various combinations of "slow" and "hasnulls". For example, if a - * given tuple has no nulls, then we needn't check "hasnulls" for every - * attribute that we're deforming. The caller can just call this - * function with hasnulls set to constant-false and have the compiler - * remove the constant-false branches and emit more optimal code. - * - * Returns the next attnum to deform, which can be equal to natts when the - * function manages to deform all requested attributes. *offp is an input and - * output parameter which is the byte offset within the tuple to start deforming - * from which, on return, gets set to the offset where the next attribute - * should be deformed from. *slowp is set to true when subsequent deforming - * of this tuple must use a version of this function with "slow" passed as - * true. - * - * Callers cannot assume when we return "attnum" (i.e. all requested - * attributes have been deformed) that slow mode isn't required for any - * additional deforming as the final attribute may have caused a switch to - * slow mode. - */ -static pg_attribute_always_inline int -slot_deform_heap_tuple_internal(TupleTableSlot *slot, HeapTuple tuple, - int attnum, int natts, bool slow, - bool hasnulls, uint32 *offp, bool *slowp) -{ - TupleDesc tupleDesc = slot->tts_tupleDescriptor; - Datum *values = slot->tts_values; - bool *isnull = slot->tts_isnull; - HeapTupleHeader tup = tuple->t_data; - char *tp; /* ptr to tuple data */ - bits8 *bp = tup->t_bits; /* ptr to null bitmap in tuple */ - bool slownext = false; - - tp = (char *) tup + tup->t_hoff; - - for (; attnum < natts; attnum++) - { - CompactAttribute *thisatt = TupleDescCompactAttr(tupleDesc, attnum); - - if (hasnulls && att_isnull(attnum, bp)) - { - values[attnum] = (Datum) 0; - isnull[attnum] = true; - if (!slow) - { - *slowp = true; - return attnum + 1; - } - else - continue; - } - - isnull[attnum] = false; - - /* calculate the offset of this attribute */ - if (!slow && thisatt->attcacheoff >= 0) - *offp = 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 && *offp == att_nominal_alignby(*offp, thisatt->attalignby)) - thisatt->attcacheoff = *offp; - else - { - *offp = att_pointer_alignby(*offp, - thisatt->attalignby, - -1, - tp + *offp); - - if (!slow) - slownext = true; - } - } - else - { - /* not varlena, so safe to use att_nominal_alignby */ - *offp = att_nominal_alignby(*offp, thisatt->attalignby); - - if (!slow) - thisatt->attcacheoff = *offp; - } - - values[attnum] = fetchatt(thisatt, tp + *offp); - - *offp = att_addlength_pointer(*offp, thisatt->attlen, tp + *offp); - - /* check if we need to switch to slow mode */ - if (!slow) - { - /* - * We're unable to deform any further if the above code set - * 'slownext', or if this isn't a fixed-width attribute. - */ - if (slownext || thisatt->attlen <= 0) - { - *slowp = true; - return attnum + 1; - } - } - } - - return natts; -} - /* * slot_deform_heap_tuple * Given a TupleTableSlot, extract data from the slot's physical tuple @@ -1122,78 +1010,165 @@ static pg_attribute_always_inline void slot_deform_heap_tuple(TupleTableSlot *slot, HeapTuple tuple, uint32 *offp, int natts) { + CompactAttribute *cattr; + TupleDesc tupleDesc = slot->tts_tupleDescriptor; bool hasnulls = HeapTupleHasNulls(tuple); + HeapTupleHeader tup = tuple->t_data; + bits8 *bp; /* ptr to null bitmap in tuple */ int attnum; + int firstNonCacheOffsetAttr; + +/* #define OPTIMIZE_BYVAL */ +#ifdef OPTIMIZE_BYVAL + int firstByRefAttr; +#endif + int firstNullAttr; + Datum *values; + bool *isnull; + char *tp; /* ptr to tuple data */ uint32 off; /* offset in tuple data */ - bool slow; /* can we use/set attcacheoff? */ /* We can only fetch as many attributes as the tuple has. */ - natts = Min(HeapTupleHeaderGetNatts(tuple->t_data), natts); + natts = Min(HeapTupleHeaderGetNatts(tup), natts); + attnum = slot->tts_nvalid; + firstNonCacheOffsetAttr = Min(tupleDesc->firstNonCachedOffAttr, natts); + + if (hasnulls) + { + bp = tup->t_bits; + firstNullAttr = first_null_attr(bp, natts); + firstNonCacheOffsetAttr = Min(firstNonCacheOffsetAttr, firstNullAttr); + } + else + { + bp = NULL; + firstNullAttr = natts; + } + +#ifdef OPTIMIZE_BYVAL + firstByRefAttr = Min(firstNonCacheOffsetAttr, tupleDesc->firstByRefAttr); +#endif + values = slot->tts_values; + isnull = slot->tts_isnull; + tp = (char *) tup + tup->t_hoff; + +#ifdef OPTIMIZE_BYVAL /* - * Check whether the first call for this tuple, and initialize or restore - * loop state. + * Many tuples have leading byval attributes, try and process as many of + * those as possible with a special loop that can't handle byref types. */ - attnum = slot->tts_nvalid; - if (attnum == 0) + if (attnum < firstByRefAttr) + { + /* Use do/while as we already know we need to loop at least once. */ + do + { + cattr = TupleDescCompactAttr(tupleDesc, attnum); + + Assert(cattr->attcacheoff >= 0); + + /* + * Hard code byval == true to allow the compiler to remove the + * byval check when inlining fetch_att(). + */ + values[attnum] = fetch_att(tp + cattr->attcacheoff, true, cattr->attlen); + isnull[attnum] = false; + } while (++attnum < firstByRefAttr); + + /* + * Point the offset after the end of the last attribute with a cached + * offset. We expect the final cached offset attribute to have a + * fixed width, so just add the attlen to the attcacheoff. + */ + Assert(cattr->attlen > 0); + off = cattr->attcacheoff + cattr->attlen; + } +#endif + + /* + * Handle the portion of the tuple that we have cached the offset for up + * to the first NULL attribute. The offset is effectively fixed for these + * so we can use the CompactAttribute's attcacheoff. + */ + if (attnum < firstNonCacheOffsetAttr) + { + do + { + cattr = TupleDescCompactAttr(tupleDesc, attnum); + + Assert(cattr->attcacheoff >= 0); + + values[attnum] = fetchatt(cattr, tp + cattr->attcacheoff); + isnull[attnum] = false; + } while (++attnum < firstNonCacheOffsetAttr); + + /* + * Point the offset after the end of the last attribute with a cached + * offset. We expect the final cached offset attribute to have a + * fixed width, so just add the attlen to the attcacheoff + */ + Assert(cattr->attlen > 0); + off = cattr->attcacheoff + cattr->attlen; + } + else if (attnum == 0) { /* Start from the first attribute */ off = 0; - slow = false; } else { /* Restore state from previous execution */ off = *offp; - slow = TTS_SLOW(slot); } /* - * If 'slow' isn't set, try deforming using deforming code that does not - * contain any of the extra checks required for non-fixed offset - * deforming. During deforming, if or when we find a NULL or a variable - * length attribute, we'll switch to a deforming method which includes the - * extra code required for non-fixed offset deforming, a.k.a slow mode. - * Because this is performance critical, we inline - * slot_deform_heap_tuple_internal passing the 'slow' and 'hasnull' - * parameters as constants to allow the compiler to emit specialized code - * with the known-const false comparisons and subsequent branches removed. + * Handle any portion of the tuple that doesn't have a fixed offset up + * until the first NULL attribute. This loops only differs from the one + * after it by the NULL checks. */ - if (!slow) + for (; attnum < firstNullAttr; attnum++) { - /* Tuple without any NULLs? We can skip doing any NULL checking */ - if (!hasnulls) - attnum = slot_deform_heap_tuple_internal(slot, - tuple, - attnum, - natts, - false, /* slow */ - false, /* hasnulls */ - &off, - &slow); - else - attnum = slot_deform_heap_tuple_internal(slot, - tuple, - attnum, - natts, - false, /* slow */ - true, /* hasnulls */ - &off, - &slow); + cattr = TupleDescCompactAttr(tupleDesc, attnum); + + /* align the offset for this attribute */ + off = att_pointer_alignby(off, + cattr->attalignby, + cattr->attlen, + tp + off); + + values[attnum] = fetchatt(cattr, tp + off); + isnull[attnum] = false; + + /* move the offset beyond this attribute */ + off = att_addlength_pointer(off, cattr->attlen, tp + off); } - /* If there's still work to do then we must be in slow mode */ - if (attnum < natts) + /* + * Now handle any remaining tuples, this time include NULL checks as we're + * now at the first NULL attribute. + */ + for (; attnum < natts; attnum++) { - /* XXX is it worth adding a separate call when hasnulls is false? */ - attnum = slot_deform_heap_tuple_internal(slot, - tuple, - attnum, - natts, - true, /* slow */ - hasnulls, - &off, - &slow); + if (att_isnull(attnum, bp)) + { + values[attnum] = (Datum) 0; + isnull[attnum] = true; + continue; + } + + cattr = TupleDescCompactAttr(tupleDesc, attnum); + + /* align the offset for this attribute */ + off = att_pointer_alignby(off, + cattr->attalignby, + cattr->attlen, + tp + off); + + values[attnum] = fetchatt(cattr, tp + off); + isnull[attnum] = false; + + /* move the offset beyond this attribute */ + off = att_addlength_pointer(off, cattr->attlen, tp + off); } /* @@ -1201,10 +1176,6 @@ slot_deform_heap_tuple(TupleTableSlot *slot, HeapTuple tuple, uint32 *offp, */ slot->tts_nvalid = attnum; *offp = off; - if (slow) - slot->tts_flags |= TTS_FLAG_SLOW; - else - slot->tts_flags &= ~TTS_FLAG_SLOW; } const TupleTableSlotOps TTSOpsVirtual = { @@ -2173,6 +2144,8 @@ ExecTypeFromTLInternal(List *targetList, bool skipjunk) cur_resno++; } + TupleDescFinalize(typeInfo); + return typeInfo; } @@ -2207,6 +2180,8 @@ ExecTypeFromExprList(List *exprList) cur_resno++; } + TupleDescFinalize(typeInfo); + return typeInfo; } diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c index af75dd8fc5e..ea19684de2e 100644 --- a/src/backend/executor/nodeFunctionscan.c +++ b/src/backend/executor/nodeFunctionscan.c @@ -414,6 +414,7 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) TupleDescInitEntryCollation(tupdesc, (AttrNumber) 1, exprCollation(funcexpr)); + TupleDescFinalize(tupdesc); } else { @@ -485,6 +486,7 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) 0); } + TupleDescFinalize(scan_tupdesc); Assert(attno == natts); } diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index dd64f45478a..23cbb92d859 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -1891,6 +1891,7 @@ addRangeTableEntryForFunction(ParseState *pstate, TupleDescInitEntryCollation(tupdesc, (AttrNumber) 1, exprCollation(funcexpr)); + TupleDescFinalize(tupdesc); } else if (functypclass == TYPEFUNC_RECORD) { @@ -1948,6 +1949,7 @@ addRangeTableEntryForFunction(ParseState *pstate, i++; } + TupleDescFinalize(tupdesc); /* * Ensure that the coldeflist defines a legal set of names (no @@ -2016,7 +2018,7 @@ addRangeTableEntryForFunction(ParseState *pstate, 0); /* no need to set collation */ } - + TupleDescFinalize(tupdesc); Assert(natts == totalatts); } else diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 905c975d83b..f0387166279 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1570,6 +1570,8 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup) } Assert(lname == NULL && lvar == NULL); /* lists same length? */ + TupleDescFinalize(tupleDesc); + return tupleDesc; } diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c index 5ddc9e812e7..75a33ea6ada 100644 --- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c +++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c @@ -1049,6 +1049,7 @@ libpqrcv_processTuples(PGresult *pgres, WalRcvExecResult *walres, TupleDescInitEntry(walres->tupledesc, (AttrNumber) coln + 1, PQfname(pgres, coln), retTypes[coln], -1, 0); attinmeta = TupleDescGetAttInMetadata(walres->tupledesc); + TupleDescFinalize(walres->tupledesc); /* No point in doing more here if there were no tuples returned. */ if (PQntuples(pgres) == 0) diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index 96cede8f45a..364ae7a3ee1 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -452,6 +452,7 @@ IdentifySystem(void) TEXTOID, -1, 0); TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 4, "dbname", TEXTOID, -1, 0); + TupleDescFinalize(tupdesc); /* prepare for projection of tuples */ tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual); @@ -497,6 +498,7 @@ ReadReplicationSlot(ReadReplicationSlotCmd *cmd) /* TimeLineID is unsigned, so int4 is not wide enough. */ TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 3, "restart_tli", INT8OID, -1, 0); + TupleDescFinalize(tupdesc); memset(nulls, true, READ_REPLICATION_SLOT_COLS * sizeof(bool)); @@ -599,6 +601,7 @@ SendTimeLineHistory(TimeLineHistoryCmd *cmd) tupdesc = CreateTemplateTupleDesc(2); TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "filename", TEXTOID, -1, 0); TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "content", TEXTOID, -1, 0); + TupleDescFinalize(tupdesc); TLHistoryFileName(histfname, cmd->timeline); TLHistoryFilePath(path, cmd->timeline); @@ -1016,6 +1019,7 @@ StartReplication(StartReplicationCmd *cmd) INT8OID, -1, 0); TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "next_tli_startpos", TEXTOID, -1, 0); + TupleDescFinalize(tupdesc); /* prepare for projection of tuple */ tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual); @@ -1370,6 +1374,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd) TEXTOID, -1, 0); TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 4, "output_plugin", TEXTOID, -1, 0); + TupleDescFinalize(tupdesc); /* prepare for projection of tuples */ tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual); diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c index 05d48412f82..3d3ca2185e6 100644 --- a/src/backend/utils/adt/acl.c +++ b/src/backend/utils/adt/acl.c @@ -1818,6 +1818,7 @@ aclexplode(PG_FUNCTION_ARGS) TupleDescInitEntry(tupdesc, (AttrNumber) 4, "is_grantable", BOOLOID, -1, 0); + TupleDescFinalize(tupdesc); funcctx->tuple_desc = BlessTupleDesc(tupdesc); /* allocate memory for user context */ diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c index 80bb807fbe9..26348513b18 100644 --- a/src/backend/utils/adt/genfile.c +++ b/src/backend/utils/adt/genfile.c @@ -454,6 +454,7 @@ pg_stat_file(PG_FUNCTION_ARGS) "creation", TIMESTAMPTZOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 6, "isdir", BOOLOID, -1, 0); + TupleDescFinalize(tupdesc); BlessTupleDesc(tupdesc); memset(isnull, false, sizeof(isnull)); diff --git a/src/backend/utils/adt/lockfuncs.c b/src/backend/utils/adt/lockfuncs.c index bf38d68aa03..5c0c6dda7c5 100644 --- a/src/backend/utils/adt/lockfuncs.c +++ b/src/backend/utils/adt/lockfuncs.c @@ -146,6 +146,7 @@ pg_lock_status(PG_FUNCTION_ARGS) TupleDescInitEntry(tupdesc, (AttrNumber) 16, "waitstart", TIMESTAMPTZOID, -1, 0); + TupleDescFinalize(tupdesc); funcctx->tuple_desc = BlessTupleDesc(tupdesc); /* diff --git a/src/backend/utils/adt/orderedsetaggs.c b/src/backend/utils/adt/orderedsetaggs.c index ac3963fc3e0..2ae1e46fbef 100644 --- a/src/backend/utils/adt/orderedsetaggs.c +++ b/src/backend/utils/adt/orderedsetaggs.c @@ -233,6 +233,7 @@ ordered_set_startup(FunctionCallInfo fcinfo, bool use_tuples) -1, 0); + TupleDescFinalize(newdesc); FreeTupleDesc(qstate->tupdesc); qstate->tupdesc = newdesc; } diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index a97aa7c73db..b5aebc0f3e6 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -769,6 +769,7 @@ pg_stat_get_backend_subxact(PG_FUNCTION_ARGS) TupleDescInitEntry(tupdesc, (AttrNumber) 2, "subxact_overflow", BOOLOID, -1, 0); + TupleDescFinalize(tupdesc); BlessTupleDesc(tupdesc); if ((local_beentry = pgstat_get_local_beentry_by_proc_number(procNumber)) != NULL) @@ -1658,6 +1659,7 @@ pg_stat_wal_build_tuple(PgStat_WalCounters wal_counters, TupleDescInitEntry(tupdesc, (AttrNumber) 6, "stats_reset", TIMESTAMPTZOID, -1, 0); + TupleDescFinalize(tupdesc); BlessTupleDesc(tupdesc); /* Fill values and NULLs */ @@ -2085,6 +2087,7 @@ pg_stat_get_archiver(PG_FUNCTION_ARGS) TupleDescInitEntry(tupdesc, (AttrNumber) 7, "stats_reset", TIMESTAMPTZOID, -1, 0); + TupleDescFinalize(tupdesc); BlessTupleDesc(tupdesc); /* Get statistics about the archiver process */ @@ -2166,6 +2169,7 @@ pg_stat_get_replication_slot(PG_FUNCTION_ARGS) TIMESTAMPTZOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 13, "stats_reset", TIMESTAMPTZOID, -1, 0); + TupleDescFinalize(tupdesc); BlessTupleDesc(tupdesc); namestrcpy(&slotname, text_to_cstring(slotname_text)); @@ -2253,6 +2257,7 @@ pg_stat_get_subscription_stats(PG_FUNCTION_ARGS) INT8OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 13, "stats_reset", TIMESTAMPTZOID, -1, 0); + TupleDescFinalize(tupdesc); BlessTupleDesc(tupdesc); if (!subentry) diff --git a/src/backend/utils/adt/tsvector_op.c b/src/backend/utils/adt/tsvector_op.c index b809089ac5d..78592499b0c 100644 --- a/src/backend/utils/adt/tsvector_op.c +++ b/src/backend/utils/adt/tsvector_op.c @@ -651,6 +651,7 @@ tsvector_unnest(PG_FUNCTION_ARGS) TEXTARRAYOID, -1, 0); if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "return type must be a row type"); + TupleDescFinalize(tupdesc); funcctx->tuple_desc = tupdesc; funcctx->user_fctx = PG_GETARG_TSVECTOR_COPY(0); diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 2d0cb7bcfd4..642c4b96297 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -666,14 +666,6 @@ RelationBuildTupleDesc(Relation relation) elog(ERROR, "pg_attribute catalog is missing %d attribute(s) for relation OID %u", need, RelationGetRelid(relation)); - /* - * We can easily set the attcacheoff value for the first attribute: it - * must be zero. This eliminates the need for special cases for attnum=1 - * that used to exist in fastgetattr() and index_getattr(). - */ - if (RelationGetNumberOfAttributes(relation) > 0) - TupleDescCompactAttr(relation->rd_att, 0)->attcacheoff = 0; - /* * Set up constraint/default info */ @@ -729,6 +721,8 @@ RelationBuildTupleDesc(Relation relation) pfree(constr); relation->rd_att->constr = NULL; } + + TupleDescFinalize(relation->rd_att); } /* @@ -1988,8 +1982,7 @@ formrdesc(const char *relationName, Oid relationReltype, populate_compact_attribute(relation->rd_att, i); } - /* initialize first attribute's attcacheoff, cf RelationBuildTupleDesc */ - TupleDescCompactAttr(relation->rd_att, 0)->attcacheoff = 0; + TupleDescFinalize(relation->rd_att); /* mark not-null status */ if (has_not_null) @@ -3693,6 +3686,8 @@ RelationBuildLocalRelation(const char *relname, for (i = 0; i < natts; i++) TupleDescAttr(rel->rd_att, i)->attrelid = relid; + TupleDescFinalize(rel->rd_att); + rel->rd_rel->reltablespace = reltablespace; if (mapped_relation) @@ -4446,8 +4441,7 @@ BuildHardcodedDescriptor(int natts, const FormData_pg_attribute *attrs) populate_compact_attribute(result, i); } - /* initialize first attribute's attcacheoff, cf RelationBuildTupleDesc */ - TupleDescCompactAttr(result, 0)->attcacheoff = 0; + TupleDescFinalize(result); /* Note: we don't bother to set up a TupleConstr entry */ @@ -6273,6 +6267,8 @@ load_relcache_init_file(bool shared) populate_compact_attribute(rel->rd_att, i); } + TupleDescFinalize(rel->rd_att); + /* next read the access method specific field */ if (fread(&len, 1, sizeof(len), fp) != sizeof(len)) goto read_failed; diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c index f40879f0617..a98bc9f9e4f 100644 --- a/src/backend/utils/fmgr/funcapi.c +++ b/src/backend/utils/fmgr/funcapi.c @@ -340,6 +340,8 @@ get_expr_result_type(Node *expr, exprCollation(col)); i++; } + TupleDescFinalize(tupdesc); + if (resultTypeId) *resultTypeId = rexpr->row_typeid; if (resultTupleDesc) @@ -1044,6 +1046,7 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, } } + TupleDescFinalize(tupdesc); return true; } @@ -1853,6 +1856,8 @@ build_function_result_tupdesc_d(char prokind, 0); } + TupleDescFinalize(desc); + return desc; } @@ -1970,6 +1975,7 @@ TypeGetTupleDesc(Oid typeoid, List *colaliases) typeoid, -1, 0); + TupleDescFinalize(tupdesc); } else if (functypclass == TYPEFUNC_RECORD) { diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index b7e94ca45bd..afbcb8193a5 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -718,6 +718,7 @@ InitPostgres(const char *in_dbname, Oid dboid, char dbname[NAMEDATALEN]; int nfree = 0; + /* pg_usleep(10000000); */ elog(DEBUG3, "InitPostgres"); /* diff --git a/src/backend/utils/misc/guc_funcs.c b/src/backend/utils/misc/guc_funcs.c index 9dbc5d3aeb9..554f20f61d1 100644 --- a/src/backend/utils/misc/guc_funcs.c +++ b/src/backend/utils/misc/guc_funcs.c @@ -444,6 +444,7 @@ GetPGVariableResultDesc(const char *name) TupleDescInitEntry(tupdesc, (AttrNumber) 1, varname, TEXTOID, -1, 0); } + TupleDescFinalize(tupdesc); return tupdesc; } @@ -465,6 +466,7 @@ ShowGUCConfigOption(const char *name, DestReceiver *dest) tupdesc = CreateTemplateTupleDesc(1); TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, varname, TEXTOID, -1, 0); + TupleDescFinalize(tupdesc); /* prepare for projection of tuples */ tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual); @@ -499,6 +501,7 @@ ShowAllGUCConfig(DestReceiver *dest) TEXTOID, -1, 0); TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 3, "description", TEXTOID, -1, 0); + TupleDescFinalize(tupdesc); /* prepare for projection of tuples */ tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual); @@ -939,6 +942,8 @@ show_all_settings(PG_FUNCTION_ARGS) * C strings */ attinmeta = TupleDescGetAttInMetadata(tupdesc); + TupleDescFinalize(tupdesc); + funcctx->attinmeta = attinmeta; /* collect the variables, in sorted order */ diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h index f3593acc8c2..0901950b206 100644 --- a/src/include/access/htup_details.h +++ b/src/include/access/htup_details.h @@ -865,20 +865,17 @@ extern MinimalTuple minimal_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleD static inline Datum fastgetattr(HeapTuple tup, int attnum, TupleDesc tupleDesc, bool *isnull) { - Assert(attnum > 0); + CompactAttribute *att = TupleDescCompactAttr(tupleDesc, attnum - 1); + Assert(attnum > 0); *isnull = false; - if (HeapTupleNoNulls(tup)) - { - CompactAttribute *att; - att = TupleDescCompactAttr(tupleDesc, attnum - 1); - if (att->attcacheoff >= 0) - return fetchatt(att, (char *) tup->t_data + tup->t_data->t_hoff + - att->attcacheoff); - else - return nocachegetattr(tup, attnum, tupleDesc); - } + if (att->attcacheoff >= 0 && !HeapTupleHasNulls(tup)) + return fetchatt(att, (char *) tup->t_data + tup->t_data->t_hoff + + att->attcacheoff); + + if (HeapTupleNoNulls(tup)) + return nocachegetattr(tup, attnum, tupleDesc); else { if (att_isnull(attnum - 1, tup->t_data->t_bits)) diff --git a/src/include/access/itup.h b/src/include/access/itup.h index 4ba928c7132..d52e8cd2a83 100644 --- a/src/include/access/itup.h +++ b/src/include/access/itup.h @@ -131,24 +131,20 @@ IndexInfoFindDataOffset(unsigned short t_info) static inline Datum index_getattr(IndexTuple tup, int attnum, TupleDesc tupleDesc, bool *isnull) { + CompactAttribute *attr = TupleDescCompactAttr(tupleDesc, attnum - 1); + Assert(isnull); Assert(attnum > 0); *isnull = false; - if (!IndexTupleHasNulls(tup)) - { - CompactAttribute *attr = TupleDescCompactAttr(tupleDesc, attnum - 1); + if (attr->attcacheoff >= 0 && !IndexTupleHasNulls(tup)) + return fetchatt(attr, + (char *) tup + IndexInfoFindDataOffset(tup->t_info) + + attr->attcacheoff); - if (attr->attcacheoff >= 0) - { - return fetchatt(attr, - (char *) tup + IndexInfoFindDataOffset(tup->t_info) + - attr->attcacheoff); - } - else - return nocache_index_getattr(tup, attnum, tupleDesc); - } + if (!IndexTupleHasNulls(tup)) + return nocache_index_getattr(tup, attnum, tupleDesc); else { if (att_isnull(attnum - 1, (bits8 *) tup + sizeof(IndexTupleData))) diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h index a25b94ba423..dca20301b7f 100644 --- a/src/include/access/tupdesc.h +++ b/src/include/access/tupdesc.h @@ -131,6 +131,12 @@ typedef struct CompactAttribute * Any code making changes manually to and fields in the FormData_pg_attribute * array must subsequently call populate_compact_attribute() to flush the * changes out to the corresponding 'compact_attrs' element. + * + * firstNonCachedOffAttr stores the index into the compact_attrs array for the + * first attribute that we don't have a known attcacheoff for. + * + * Once a TupleDesc has been populated, before it is used for any purpose + * TupleDescFinalize() must be called on it. */ typedef struct TupleDescData { @@ -138,6 +144,10 @@ typedef struct TupleDescData Oid tdtypeid; /* composite type ID for tuple type */ int32 tdtypmod; /* typmod for tuple type */ int tdrefcount; /* reference count, or -1 if not counting */ + int firstNonCachedOffAttr; /* index of last att with an + * attcacheoff */ + int firstByRefAttr; /* index of the first attr with !attbyval, or + * natts if none. */ TupleConstr *constr; /* constraints, or NULL if none */ /* compact_attrs[N] is the compact metadata of Attribute Number N+1 */ CompactAttribute compact_attrs[FLEXIBLE_ARRAY_MEMBER]; @@ -205,6 +215,8 @@ extern void TupleDescCopy(TupleDesc dst, TupleDesc src); extern void TupleDescCopyEntry(TupleDesc dst, AttrNumber dstAttno, TupleDesc src, AttrNumber srcAttno); +extern void TupleDescFinalize(TupleDesc tupdesc); + extern void FreeTupleDesc(TupleDesc tupdesc); extern void IncrTupleDescRefCount(TupleDesc tupdesc); diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h index 84b3e7fd896..d6ab90bbde1 100644 --- a/src/include/access/tupmacs.h +++ b/src/include/access/tupmacs.h @@ -15,6 +15,7 @@ #define TUPMACS_H #include "catalog/pg_type_d.h" /* for TYPALIGN macros */ +#include "port/pg_bitutils.h" /* @@ -69,6 +70,62 @@ fetch_att(const void *T, bool attbyval, int attlen) else return PointerGetDatum(T); } + +/* + * first_null_attr + * Inspect a NULL bitmask from a tuple and return the 0-based attnum of the + * first NULL attribute. Returns natts if no NULLs were found. + */ +static inline int +first_null_attr(const bits8 *bits, int natts) +{ + int lastByte = natts >> 3; + uint8 mask; + int res = natts; + uint8 byte; + +#ifdef USE_ASSERT_CHECKING + int firstnull_check = natts; + + /* Do it the slow way and check we get the same answer. */ + for (int i = 0; i < natts; i++) + { + if (att_isnull(i, bits)) + { + firstnull_check = i; + break; + } + } +#endif + + /* Process all bytes up to just before the byte for the natts index */ + for (int bytenum = 0; bytenum < lastByte; bytenum++) + { + if (bits[bytenum] != 0xFF) + { + byte = ~bits[bytenum]; + res = bytenum * 8; + res += pg_rightmost_one_pos[byte]; + + Assert(res == firstnull_check); + return res; + } + } + + /* Create a mask with all bits beyond natts's bit set to off */ + mask = 0xFF & ((((uint8) 1) << (natts & 7)) - 1); + byte = (~bits[lastByte]) & mask; + + if (byte != 0) + { + res = lastByte * 8; + res += pg_rightmost_one_pos[byte]; + } + + Assert(res == firstnull_check); + + return res; +} #endif /* FRONTEND */ /* diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h index 43f1d999b91..ff3ebbc76b9 100644 --- a/src/include/executor/tuptable.h +++ b/src/include/executor/tuptable.h @@ -84,9 +84,6 @@ * tts_values/tts_isnull are allocated either when the slot is created (when * the descriptor is provided), or when a descriptor is assigned to the slot; * they are of length equal to the descriptor's natts. - * - * The TTS_FLAG_SLOW flag is saved state for - * slot_deform_heap_tuple, and should not be touched by any other code. *---------- */ @@ -98,12 +95,8 @@ #define TTS_FLAG_SHOULDFREE (1 << 2) #define TTS_SHOULDFREE(slot) (((slot)->tts_flags & TTS_FLAG_SHOULDFREE) != 0) -/* saved state for slot_deform_heap_tuple */ -#define TTS_FLAG_SLOW (1 << 3) -#define TTS_SLOW(slot) (((slot)->tts_flags & TTS_FLAG_SLOW) != 0) - /* fixed tuple descriptor */ -#define TTS_FLAG_FIXED (1 << 4) +#define TTS_FLAG_FIXED (1 << 4) /* XXX change to #3? */ #define TTS_FIXED(slot) (((slot)->tts_flags & TTS_FLAG_FIXED) != 0) struct TupleTableSlotOps; diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index 4d90a0c2f06..ace4f5f03c0 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -1912,6 +1912,8 @@ build_row_from_vars(PLpgSQL_variable **vars, int numvars) TupleDescInitEntryCollation(row->rowtupdesc, i + 1, typcoll); } + TupleDescFinalize(row->rowtupdesc); + return row; } diff --git a/src/test/modules/test_predtest/test_predtest.c b/src/test/modules/test_predtest/test_predtest.c index be5b8c40914..9911fbe642b 100644 --- a/src/test/modules/test_predtest/test_predtest.c +++ b/src/test/modules/test_predtest/test_predtest.c @@ -230,6 +230,7 @@ test_predtest(PG_FUNCTION_ARGS) "s_r_holds", BOOLOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 8, "w_r_holds", BOOLOID, -1, 0); + TupleDescFinalize(tupdesc); tupdesc = BlessTupleDesc(tupdesc); values[0] = BoolGetDatum(strong_implied_by); -- 2.43.0