From a69bb1663487a7e1aa8fa105c2800938ea40d023 Mon Sep 17 00:00:00 2001 From: Greg Burd Date: Fri, 14 Nov 2025 08:02:31 -0500 Subject: [PATCH v4 1/3] Refactor how we form HeapTuples for CatalogTuple(Insert|Update) This commit provides a set of macros that should be used when inserting or updating tuples before calling CatalogTupleUpdate() or CatalogTupleInsert(). Historically there have been two methods for accomplishing this task; Form/GETSTRUCT, and values/nulls/replaces. This new method provides a more intuitive and less error-prone approach without changing the fundamentals of the process. In addition, it is now possible to retain knowledge of the set of mutated attributes when working with catalog tuples. This isn't used in practice (yet), but could be a way to avoid the overhead of HeapDetermineColumnsInfo() in heap_update() where (while holding a lock) we re-discover that set by comparing old/new HeapTuple datums when trying to find what indexed attributes have new values and prevent HOT updates. The "Form/GETSTRUCT" model allows for direct access to the tuple data that is then modified, copied, and then updated via CatalogTupleUpdate(). Old: Form_pg_index relform = (Form_pg_index) GETSTRUCT(tuple); relform->inisclustered = false; CatalogTupleUpdate(pg_index, &tuple->t_self, tuple); New: Bitmapset *updated = NULL; Form_pg_index relform = (Form_pg_index) GETSTRUCT(tuple); HeapTupleUpdateField(pg_index, indisclustered, false, indexForm, updated); CatalogTupleUpdate(pg_index, &indexTuple->t_self, indexTuple, updated, NULL); bms_free(updated); The "values/nulls/replaces" model collects the necessary information to either update or create a heap tuple using heap_modify_tuple() or heap_form_tuple() or sometimes heap_modify_tuple_by_cols(). While all those functions remain unchanged now the prefered function for catalog tuple modification is heap_update_tuple(). Old: bool nulls[Natts_pg_type]; bool replaces[Natts_pg_type]; Datum values[Natts_pg_type]; values[Anum_pg_type_typtype - 1] = CharGetDatum(typeType); nulls[Anum_pg_type_typdefaultbin - 1] = true; replaces[Anum_pg_type_oid - 1] = false; tup = heap_modify_tuple(tuple, desc, values, nulls, replaces); CatalogTupleUpdate(pg_type_desc, &tuple->t_self, tuple); New: Datum values[Natts_pg_type] = {0}; bool nulls[Natts_pg_type] = {false}; Bitmapset *updated = NULL; HeapTupleUpdateValue(pg_type, typtype, CharGetDatum(typeType), values, nulls, updated); HeapTupleUpdateValueNull(pg_type, typdefaultbin, values, nulls, updated); HeapTupleSetColumnNotUpdated(pg_type, oid, updated); tup = heap_update_tuple(tuple, desc, values, nulls, updated); CatalogTupleUpdate(pg_type_desc, &tuple->t_self, tuple, updated, NULL); In addition CatalogTupleUpdate and CatalogTupleInsert now have an optional argument for the CatalogIndexState, when not provided it is created and then released for the caller. The heap_update_tuple() function is functionally equivalent to heap_modify_tuple(), but takes a Bitmapset called "updated" rather than an array of bool generally called "replaces" as a method for indicating what was modified. Additionally, this new function tries to balance the tradeoffs of calling heap_getattr() versus heap_deform_tuple() based on the ratio of attributes updated and their known runtime complexities. Both paths are functionally equivalent. The changes also include initialization of the values/nulls arrays rather than loops or memset(). Some effort was also given to unify naming of these variables and their order so as to lower cognitive overhead. There is no impact to non-catalog related paths. --- src/backend/access/common/heaptuple.c | 119 +++++++++++++++++++ src/backend/catalog/indexing.c | 137 +++++++++++----------- src/backend/catalog/pg_aggregate.c | 74 ++++++------ src/backend/catalog/pg_constraint.c | 138 ++++++++++++----------- src/backend/commands/alter.c | 60 ++++++---- src/backend/commands/analyze.c | 75 +++++------- src/backend/statistics/attribute_stats.c | 99 ++++++++-------- src/backend/statistics/stat_utils.c | 99 ++++++---------- src/include/access/htup.h | 61 ++++++++++ src/include/access/htup_details.h | 8 ++ src/include/catalog/indexing.h | 17 +-- src/include/statistics/stat_utils.h | 15 ++- 12 files changed, 529 insertions(+), 373 deletions(-) diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c index 11bec20e82e..89342af7012 100644 --- a/src/backend/access/common/heaptuple.c +++ b/src/backend/access/common/heaptuple.c @@ -1325,6 +1325,125 @@ heap_modify_tuple_by_cols(HeapTuple tuple, return newTuple; } +/* + * heap_update_tuple + * Form a new tuple from an old tuple and a set of replacement values. + * + * Creates a new HeapTuple by selectively replacing attributes from the original + * tuple with new values. The 'updated' Bitmapset specifies which attributes + * (by attribute number, 1-based adjusted by FirstLowInvalidHeapAttributeNumber) + * should be replaced with corresponding entries from new_values and new_isnull + * arrays (0-based indices). + * + * Performance strategy: + * - If updating many attributes (> 2*natts/3), use heap_getattr() to extract + * only the few non-updated attributes. This is O(k*n) where k is the number + * of non-updated attributes, which is small when updating many. + * - If updating few attributes (<= 2*natts/3), use heap_deform_tuple() to + * extract all attributes at once (O(n)), then replace the updated ones. + * This avoids the O(n^2) cost of many heap_getattr() calls. + * + * The threshold of 2*natts/3 balances the fixed O(n) cost of heap_deform_tuple + * against the variable O(k*n) cost of heap_getattr, where k = natts - num_updated. + */ +HeapTuple +heap_update_tuple(HeapTuple tuple, + TupleDesc desc, + const Datum *new_values, + const bool *new_nulls, + const Bitmapset *updated) +{ + int natts = desc->natts; + int num_updated; + Datum *values; + bool *nulls; + HeapTuple new_tuple; + + Assert(!bms_is_empty(updated)); + + num_updated = bms_num_members(updated); + Assert(num_updated <= natts); + + values = (Datum *) palloc0(natts * sizeof(Datum)); + nulls = (bool *) palloc0(natts * sizeof(bool)); + + /* + * Choose strategy based on update density. When updating most attributes, + * it's cheaper to extract the few unchanged ones individually. + */ + if (num_updated > (2 * natts) / 3) + { + /* Updating many: use heap_getattr for the few non-updated attributes */ + for (int i = 0; i < natts; i++) + { + /* + * Convert array index to attribute number, then to bitmapset + * member. Array index i (0-based) -> attnum (1-based) -> bms + * member. + */ + AttrNumber attnum = i + 1; + int member = attnum - FirstLowInvalidHeapAttributeNumber; + + if (bms_is_member(member, updated)) + { + /* Use replacement value */ + if (unlikely(!new_values || !new_nulls)) + values[i] = heap_getattr(tuple, attnum, desc, &nulls[i]); + + if (likely(new_values)) + values[i] = new_values[i]; + + if (likely(new_nulls)) + nulls[i] = new_nulls[i]; + } + else + { + /* Extract original value using heap_getattr (1-based) */ + values[i] = heap_getattr(tuple, attnum, desc, &nulls[i]); + } + } + } + else + { + int member = -1; + + /* Updating few: deform entire tuple, then replace updated attributes */ + heap_deform_tuple(tuple, desc, values, nulls); + + while ((member = bms_next_member(updated, member)) >= 0) + { + /* + * Convert bitmapset member to attribute number, then to array + * index. bms_member -> attnum (1-based) -> array index i + * (0-based). + */ + AttrNumber attnum = member + FirstLowInvalidHeapAttributeNumber; + int i = attnum - 1; + + Assert(i >= 0 && i < natts); + + if (likely(new_values)) + values[i] = new_values[i]; + + if (likely(new_nulls)) + nulls[i] = new_nulls[i]; + } + } + + /* Create the new tuple */ + new_tuple = heap_form_tuple(desc, values, nulls); + + pfree(values); + pfree(nulls); + + /* Preserve tuple identity and location information from the original */ + new_tuple->t_data->t_ctid = tuple->t_data->t_ctid; + new_tuple->t_self = tuple->t_self; + new_tuple->t_tableOid = tuple->t_tableOid; + + return new_tuple; +} + /* * heap_deform_tuple * Given a tuple, extract data into values/isnull arrays; this is diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c index 0a1a68e0644..a8ef28a0704 100644 --- a/src/backend/catalog/indexing.c +++ b/src/backend/catalog/indexing.c @@ -225,58 +225,67 @@ CatalogTupleCheckConstraints(Relation heapRel, HeapTuple tup) * * This is a convenience routine for the common case of inserting a single * tuple in a system catalog; it inserts a new heap tuple, keeping indexes - * current. Avoid using it for multiple tuples, since opening the indexes - * and building the index info structures is moderately expensive. - * (Use CatalogTupleInsertWithInfo in such cases.) + * current. + * + * If 'indstate' is NULL, the function opens and closes the indexes internally. + * This is convenient for single-tuple updates but has overhead from opening the + * indexes and building index info structures. + * + * If 'indstate' is provided (non-NULL), it is used directly without opening or + * closing indexes. This allows callers to amortize index management costs across + * multiple tuple updates. Callers must use CatalogOpenIndexes() before the first + * update and CatalogCloseIndexes() after the last update. + * + * XXX: At some point we might cache the CatalogIndexState data somewhere (perhaps + * in the relcache) so that callers needn't trouble over this. */ void -CatalogTupleInsert(Relation heapRel, HeapTuple tup) +CatalogTupleInsert(Relation heapRel, HeapTuple tup, + CatalogIndexState indstate) { - CatalogIndexState indstate; - - CatalogTupleCheckConstraints(heapRel, tup); - - indstate = CatalogOpenIndexes(heapRel); + bool close_indexes = false; - simple_heap_insert(heapRel, tup); - - CatalogIndexInsert(indstate, tup, TU_All); - CatalogCloseIndexes(indstate); -} + /* Open indexes if not provided by caller */ + if (indstate == NULL) + { + indstate = CatalogOpenIndexes(heapRel); + close_indexes = true; + } -/* - * CatalogTupleInsertWithInfo - as above, but with caller-supplied index info - * - * This should be used when it's important to amortize CatalogOpenIndexes/ - * CatalogCloseIndexes work across multiple insertions. At some point we - * might cache the CatalogIndexState data somewhere (perhaps in the relcache) - * so that callers needn't trouble over this ... but we don't do so today. - */ -void -CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup, - CatalogIndexState indstate) -{ CatalogTupleCheckConstraints(heapRel, tup); simple_heap_insert(heapRel, tup); CatalogIndexInsert(indstate, tup, TU_All); + + /* Close indexes only if we opened them ourselves */ + if (close_indexes) + CatalogCloseIndexes(indstate); } /* - * CatalogTuplesMultiInsertWithInfo - as above, but for multiple tuples + * CatalogTuplesMultiInsert - as above, but for multiple tuples * * Insert multiple tuples into the given catalog relation at once, with an * amortized cost of CatalogOpenIndexes. */ void -CatalogTuplesMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot, - int ntuples, CatalogIndexState indstate) +CatalogTuplesMultiInsert(Relation heapRel, TupleTableSlot **slot, + int ntuples, CatalogIndexState indstate) { + bool close_indexes = false; + /* Nothing to do */ if (ntuples <= 0) return; + /* Open indexes if not provided by caller */ + if (indstate == NULL) + { + indstate = CatalogOpenIndexes(heapRel); + close_indexes = true; + } + heap_multi_insert(heapRel, slot, ntuples, GetCurrentCommandId(true), 0, NULL); @@ -296,6 +305,10 @@ CatalogTuplesMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot, if (should_free) heap_freetuple(tuple); } + + /* Close indexes only if we opened them ourselves */ + if (close_indexes) + CatalogCloseIndexes(indstate); } /* @@ -303,47 +316,44 @@ CatalogTuplesMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot, * * Update the tuple identified by "otid", replacing it with the data in "tup". * - * This is a convenience routine for the common case of updating a single - * tuple in a system catalog; it updates one heap tuple, keeping indexes - * current. Avoid using it for multiple tuples, since opening the indexes - * and building the index info structures is moderately expensive. - * (Use CatalogTupleUpdateWithInfo in such cases.) + * This function updates a heap tuple in a system catalog and keeps its indexes + * current. The 'updated' bitmapset specifies which columns were modified. + * + * If 'indstate' is NULL, the function opens and closes the indexes internally. + * This is convenient for single-tuple updates but has overhead from opening the + * indexes and building index info structures. + * + * If 'indstate' is provided (non-NULL), it is used directly without opening or + * closing indexes. This allows callers to amortize index management costs across + * multiple tuple updates. Callers must use CatalogOpenIndexes() before the first + * update and CatalogCloseIndexes() after the last update. + * + * XXX: At some point we might cache the CatalogIndexState data somewhere (perhaps + * in the relcache) so that callers needn't trouble over this. */ void -CatalogTupleUpdate(Relation heapRel, const ItemPointerData *otid, HeapTuple tup) +CatalogTupleUpdate(Relation heapRel, const ItemPointerData *otid, HeapTuple tuple, + const Bitmapset *updated, CatalogIndexState indstate) { - CatalogIndexState indstate; TU_UpdateIndexes updateIndexes = TU_All; + bool close_indexes = false; - CatalogTupleCheckConstraints(heapRel, tup); - - indstate = CatalogOpenIndexes(heapRel); - - simple_heap_update(heapRel, otid, tup, &updateIndexes); - - CatalogIndexInsert(indstate, tup, updateIndexes); - CatalogCloseIndexes(indstate); -} + CatalogTupleCheckConstraints(heapRel, tuple); -/* - * CatalogTupleUpdateWithInfo - as above, but with caller-supplied index info - * - * This should be used when it's important to amortize CatalogOpenIndexes/ - * CatalogCloseIndexes work across multiple updates. At some point we - * might cache the CatalogIndexState data somewhere (perhaps in the relcache) - * so that callers needn't trouble over this ... but we don't do so today. - */ -void -CatalogTupleUpdateWithInfo(Relation heapRel, const ItemPointerData *otid, HeapTuple tup, - CatalogIndexState indstate) -{ - TU_UpdateIndexes updateIndexes = TU_All; + /* Open indexes if not provided by caller */ + if (indstate == NULL) + { + indstate = CatalogOpenIndexes(heapRel); + close_indexes = true; + } - CatalogTupleCheckConstraints(heapRel, tup); + simple_heap_update(heapRel, otid, tuple, &updateIndexes); - simple_heap_update(heapRel, otid, tup, &updateIndexes); + CatalogIndexInsert(indstate, tuple, updateIndexes); - CatalogIndexInsert(indstate, tup, updateIndexes); + /* Close indexes only if we opened them ourselves */ + if (close_indexes) + CatalogCloseIndexes(indstate); } /* @@ -355,11 +365,6 @@ CatalogTupleUpdateWithInfo(Relation heapRel, const ItemPointerData *otid, HeapTu * cleanup will be done later by VACUUM. However, callers of this function * shouldn't have to know that; we'd like a uniform abstraction for all * catalog tuple changes. Hence, provide this currently-trivial wrapper. - * - * The abstraction is a bit leaky in that we don't provide an optimized - * CatalogTupleDeleteWithInfo version, because there is currently nothing to - * optimize. If we ever need that, rather than touching a lot of call sites, - * it might be better to do something about caching CatalogIndexState. */ void CatalogTupleDelete(Relation heapRel, const ItemPointerData *tid) diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c index 243b952b9cc..17f14cf0e17 100644 --- a/src/backend/catalog/pg_aggregate.c +++ b/src/backend/catalog/pg_aggregate.c @@ -79,9 +79,9 @@ AggregateCreate(const char *aggName, Relation aggdesc; HeapTuple tup; HeapTuple oldtup; - bool nulls[Natts_pg_aggregate]; - Datum values[Natts_pg_aggregate]; - bool replaces[Natts_pg_aggregate]; + bool nulls[Natts_pg_aggregate] = {false}; + Datum values[Natts_pg_aggregate] = {0}; + Bitmapset *updated = NULL; Form_pg_proc proc; Oid transfn; Oid finalfn = InvalidOid; /* can be omitted */ @@ -102,7 +102,6 @@ AggregateCreate(const char *aggName, Oid procOid; TupleDesc tupDesc; char *detailmsg; - int i; ObjectAddress myself, referenced; ObjectAddresses *addrs; @@ -584,7 +583,7 @@ AggregateCreate(const char *aggName, /* * permission checks on used types */ - for (i = 0; i < numArgs; i++) + for (int i = 0; i < numArgs; i++) { aclresult = object_aclcheck(TypeRelationId, aggArgTypes[i], GetUserId(), ACL_USAGE); if (aclresult != ACLCHECK_OK) @@ -651,40 +650,34 @@ AggregateCreate(const char *aggName, tupDesc = aggdesc->rd_att; /* initialize nulls and values */ - for (i = 0; i < Natts_pg_aggregate; i++) - { - nulls[i] = false; - values[i] = (Datum) 0; - replaces[i] = true; - } - values[Anum_pg_aggregate_aggfnoid - 1] = ObjectIdGetDatum(procOid); - values[Anum_pg_aggregate_aggkind - 1] = CharGetDatum(aggKind); - values[Anum_pg_aggregate_aggnumdirectargs - 1] = Int16GetDatum(numDirectArgs); - values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn); - values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn); - values[Anum_pg_aggregate_aggcombinefn - 1] = ObjectIdGetDatum(combinefn); - values[Anum_pg_aggregate_aggserialfn - 1] = ObjectIdGetDatum(serialfn); - values[Anum_pg_aggregate_aggdeserialfn - 1] = ObjectIdGetDatum(deserialfn); - values[Anum_pg_aggregate_aggmtransfn - 1] = ObjectIdGetDatum(mtransfn); - values[Anum_pg_aggregate_aggminvtransfn - 1] = ObjectIdGetDatum(minvtransfn); - values[Anum_pg_aggregate_aggmfinalfn - 1] = ObjectIdGetDatum(mfinalfn); - values[Anum_pg_aggregate_aggfinalextra - 1] = BoolGetDatum(finalfnExtraArgs); - values[Anum_pg_aggregate_aggmfinalextra - 1] = BoolGetDatum(mfinalfnExtraArgs); - values[Anum_pg_aggregate_aggfinalmodify - 1] = CharGetDatum(finalfnModify); - values[Anum_pg_aggregate_aggmfinalmodify - 1] = CharGetDatum(mfinalfnModify); - values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop); - values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType); - values[Anum_pg_aggregate_aggtransspace - 1] = Int32GetDatum(aggTransSpace); - values[Anum_pg_aggregate_aggmtranstype - 1] = ObjectIdGetDatum(aggmTransType); - values[Anum_pg_aggregate_aggmtransspace - 1] = Int32GetDatum(aggmTransSpace); + HeapTupleUpdateValue(pg_aggregate, aggfnoid, ObjectIdGetDatum(procOid), values, nulls, updated); + HeapTupleUpdateValue(pg_aggregate, aggkind, CharGetDatum(aggKind), values, nulls, updated); + HeapTupleUpdateValue(pg_aggregate, aggnumdirectargs, Int16GetDatum(numDirectArgs), values, nulls, updated); + HeapTupleUpdateValue(pg_aggregate, aggtransfn, ObjectIdGetDatum(transfn), values, nulls, updated); + HeapTupleUpdateValue(pg_aggregate, aggfinalfn, ObjectIdGetDatum(finalfn), values, nulls, updated); + HeapTupleUpdateValue(pg_aggregate, aggcombinefn, ObjectIdGetDatum(combinefn), values, nulls, updated); + HeapTupleUpdateValue(pg_aggregate, aggserialfn, ObjectIdGetDatum(serialfn), values, nulls, updated); + HeapTupleUpdateValue(pg_aggregate, aggdeserialfn, ObjectIdGetDatum(deserialfn), values, nulls, updated); + HeapTupleUpdateValue(pg_aggregate, aggmtransfn, ObjectIdGetDatum(mtransfn), values, nulls, updated); + HeapTupleUpdateValue(pg_aggregate, aggminvtransfn, ObjectIdGetDatum(minvtransfn), values, nulls, updated); + HeapTupleUpdateValue(pg_aggregate, aggmfinalfn, ObjectIdGetDatum(mfinalfn), values, nulls, updated); + HeapTupleUpdateValue(pg_aggregate, aggfinalextra, BoolGetDatum(finalfnExtraArgs), values, nulls, updated); + HeapTupleUpdateValue(pg_aggregate, aggmfinalextra, BoolGetDatum(mfinalfnExtraArgs), values, nulls, updated); + HeapTupleUpdateValue(pg_aggregate, aggfinalmodify, CharGetDatum(finalfnModify), values, nulls, updated); + HeapTupleUpdateValue(pg_aggregate, aggmfinalmodify, CharGetDatum(mfinalfnModify), values, nulls, updated); + HeapTupleUpdateValue(pg_aggregate, aggsortop, ObjectIdGetDatum(sortop), values, nulls, updated); + HeapTupleUpdateValue(pg_aggregate, aggtranstype, ObjectIdGetDatum(aggTransType), values, nulls, updated); + HeapTupleUpdateValue(pg_aggregate, aggtransspace, Int32GetDatum(aggTransSpace), values, nulls, updated); + HeapTupleUpdateValue(pg_aggregate, aggmtranstype, ObjectIdGetDatum(aggmTransType), values, nulls, updated); + HeapTupleUpdateValue(pg_aggregate, aggmtransspace, Int32GetDatum(aggmTransSpace), values, nulls, updated); if (agginitval) - values[Anum_pg_aggregate_agginitval - 1] = CStringGetTextDatum(agginitval); + HeapTupleUpdateValue(pg_aggregate, agginitval, CStringGetTextDatum(agginitval), values, nulls, updated); else - nulls[Anum_pg_aggregate_agginitval - 1] = true; + HeapTupleUpdateValueNull(pg_aggregate, agginitval, values, nulls, updated); if (aggminitval) - values[Anum_pg_aggregate_aggminitval - 1] = CStringGetTextDatum(aggminitval); + HeapTupleUpdateValue(pg_aggregate, aggminitval, CStringGetTextDatum(aggminitval), values, nulls, updated); else - nulls[Anum_pg_aggregate_aggminitval - 1] = true; + HeapTupleUpdateValueNull(pg_aggregate, aggminitval, values, nulls, updated); if (replace) oldtup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(procOid)); @@ -717,20 +710,17 @@ AggregateCreate(const char *aggName, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("cannot change number of direct arguments of an aggregate function"))); - replaces[Anum_pg_aggregate_aggfnoid - 1] = false; - replaces[Anum_pg_aggregate_aggkind - 1] = false; - replaces[Anum_pg_aggregate_aggnumdirectargs - 1] = false; - - tup = heap_modify_tuple(oldtup, tupDesc, values, nulls, replaces); - CatalogTupleUpdate(aggdesc, &tup->t_self, tup); + tup = heap_update_tuple(oldtup, tupDesc, values, nulls, updated); + CatalogTupleUpdate(aggdesc, &tup->t_self, tup, updated, NULL); ReleaseSysCache(oldtup); } else { tup = heap_form_tuple(tupDesc, values, nulls); - CatalogTupleInsert(aggdesc, tup); + CatalogTupleInsert(aggdesc, tup, NULL); } + bms_free(updated); table_close(aggdesc, RowExclusiveLock); /* diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index cbbcf166e45..5b44317ed7c 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -85,8 +85,8 @@ CreateConstraintEntry(const char *constraintName, Relation conDesc; Oid conOid; HeapTuple tup; - bool nulls[Natts_pg_constraint]; - Datum values[Natts_pg_constraint]; + Datum values[Natts_pg_constraint] = {0}; + bool nulls[Natts_pg_constraint] = {false}; ArrayType *conkeyArray; ArrayType *confkeyArray; ArrayType *conpfeqopArray; @@ -175,79 +175,72 @@ CreateConstraintEntry(const char *constraintName, else conexclopArray = NULL; - /* initialize nulls and values */ - for (i = 0; i < Natts_pg_constraint; i++) - { - nulls[i] = false; - values[i] = (Datum) 0; - } - conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId, Anum_pg_constraint_oid); - values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid); - values[Anum_pg_constraint_conname - 1] = NameGetDatum(&cname); - values[Anum_pg_constraint_connamespace - 1] = ObjectIdGetDatum(constraintNamespace); - values[Anum_pg_constraint_contype - 1] = CharGetDatum(constraintType); - values[Anum_pg_constraint_condeferrable - 1] = BoolGetDatum(isDeferrable); - values[Anum_pg_constraint_condeferred - 1] = BoolGetDatum(isDeferred); - values[Anum_pg_constraint_conenforced - 1] = BoolGetDatum(isEnforced); - values[Anum_pg_constraint_convalidated - 1] = BoolGetDatum(isValidated); - values[Anum_pg_constraint_conrelid - 1] = ObjectIdGetDatum(relId); - values[Anum_pg_constraint_contypid - 1] = ObjectIdGetDatum(domainId); - values[Anum_pg_constraint_conindid - 1] = ObjectIdGetDatum(indexRelId); - values[Anum_pg_constraint_conparentid - 1] = ObjectIdGetDatum(parentConstrId); - values[Anum_pg_constraint_confrelid - 1] = ObjectIdGetDatum(foreignRelId); - values[Anum_pg_constraint_confupdtype - 1] = CharGetDatum(foreignUpdateType); - values[Anum_pg_constraint_confdeltype - 1] = CharGetDatum(foreignDeleteType); - values[Anum_pg_constraint_confmatchtype - 1] = CharGetDatum(foreignMatchType); - values[Anum_pg_constraint_conislocal - 1] = BoolGetDatum(conIsLocal); - values[Anum_pg_constraint_coninhcount - 1] = Int16GetDatum(conInhCount); - values[Anum_pg_constraint_connoinherit - 1] = BoolGetDatum(conNoInherit); - values[Anum_pg_constraint_conperiod - 1] = BoolGetDatum(conPeriod); + HeapTupleSetValue(pg_constraint, oid, ObjectIdGetDatum(conOid), values); + HeapTupleSetValue(pg_constraint, conname, NameGetDatum(&cname), values); + HeapTupleSetValue(pg_constraint, connamespace, ObjectIdGetDatum(constraintNamespace), values); + HeapTupleSetValue(pg_constraint, contype, CharGetDatum(constraintType), values); + HeapTupleSetValue(pg_constraint, condeferrable, BoolGetDatum(isDeferrable), values); + HeapTupleSetValue(pg_constraint, condeferred, BoolGetDatum(isDeferred), values); + HeapTupleSetValue(pg_constraint, conenforced, BoolGetDatum(isEnforced), values); + HeapTupleSetValue(pg_constraint, convalidated, BoolGetDatum(isValidated), values); + HeapTupleSetValue(pg_constraint, conrelid, ObjectIdGetDatum(relId), values); + HeapTupleSetValue(pg_constraint, contypid, ObjectIdGetDatum(domainId), values); + HeapTupleSetValue(pg_constraint, conindid, ObjectIdGetDatum(indexRelId), values); + HeapTupleSetValue(pg_constraint, conparentid, ObjectIdGetDatum(parentConstrId), values); + HeapTupleSetValue(pg_constraint, confrelid, ObjectIdGetDatum(foreignRelId), values); + HeapTupleSetValue(pg_constraint, confupdtype, CharGetDatum(foreignUpdateType), values); + HeapTupleSetValue(pg_constraint, confdeltype, CharGetDatum(foreignDeleteType), values); + HeapTupleSetValue(pg_constraint, confmatchtype, CharGetDatum(foreignMatchType), values); + HeapTupleSetValue(pg_constraint, conislocal, BoolGetDatum(conIsLocal), values); + HeapTupleSetValue(pg_constraint, coninhcount, Int16GetDatum(conInhCount), values); + HeapTupleSetValue(pg_constraint, connoinherit, BoolGetDatum(conNoInherit), values); + HeapTupleSetValue(pg_constraint, conperiod, BoolGetDatum(conPeriod), values); if (conkeyArray) - values[Anum_pg_constraint_conkey - 1] = PointerGetDatum(conkeyArray); + HeapTupleSetValue(pg_constraint, conkey, PointerGetDatum(conkeyArray), values); else - nulls[Anum_pg_constraint_conkey - 1] = true; + HeapTupleSetValueNull(pg_constraint, conkey, values, nulls); if (confkeyArray) - values[Anum_pg_constraint_confkey - 1] = PointerGetDatum(confkeyArray); + HeapTupleSetValue(pg_constraint, confkey, PointerGetDatum(confkeyArray), values); else - nulls[Anum_pg_constraint_confkey - 1] = true; + HeapTupleSetValueNull(pg_constraint, confkey, values, nulls); if (conpfeqopArray) - values[Anum_pg_constraint_conpfeqop - 1] = PointerGetDatum(conpfeqopArray); + HeapTupleSetValue(pg_constraint, conpfeqop, PointerGetDatum(conpfeqopArray), values); else - nulls[Anum_pg_constraint_conpfeqop - 1] = true; + HeapTupleSetValueNull(pg_constraint, conpfeqop, values, nulls); if (conppeqopArray) - values[Anum_pg_constraint_conppeqop - 1] = PointerGetDatum(conppeqopArray); + HeapTupleSetValue(pg_constraint, conppeqop, PointerGetDatum(conppeqopArray), values); else - nulls[Anum_pg_constraint_conppeqop - 1] = true; + HeapTupleSetValueNull(pg_constraint, conppeqop, values, nulls); if (conffeqopArray) - values[Anum_pg_constraint_conffeqop - 1] = PointerGetDatum(conffeqopArray); + HeapTupleSetValue(pg_constraint, conffeqop, PointerGetDatum(conffeqopArray), values); else - nulls[Anum_pg_constraint_conffeqop - 1] = true; + HeapTupleSetValueNull(pg_constraint, conffeqop, values, nulls); if (confdelsetcolsArray) - values[Anum_pg_constraint_confdelsetcols - 1] = PointerGetDatum(confdelsetcolsArray); + HeapTupleSetValue(pg_constraint, confdelsetcols, PointerGetDatum(confdelsetcolsArray), values); else - nulls[Anum_pg_constraint_confdelsetcols - 1] = true; + HeapTupleSetValueNull(pg_constraint, confdelsetcols, values, nulls); if (conexclopArray) - values[Anum_pg_constraint_conexclop - 1] = PointerGetDatum(conexclopArray); + HeapTupleSetValue(pg_constraint, conexclop, PointerGetDatum(conexclopArray), values); else - nulls[Anum_pg_constraint_conexclop - 1] = true; + HeapTupleSetValueNull(pg_constraint, conexclop, values, nulls); if (conBin) - values[Anum_pg_constraint_conbin - 1] = CStringGetTextDatum(conBin); + HeapTupleSetValue(pg_constraint, conbin, CStringGetTextDatum(conBin), values); else - nulls[Anum_pg_constraint_conbin - 1] = true; + HeapTupleSetValueNull(pg_constraint, conbin, values, nulls); tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls); - CatalogTupleInsert(conDesc, tup); + CatalogTupleInsert(conDesc, tup, NULL); ObjectAddressSet(conobject, ConstraintRelationId, conOid); @@ -748,7 +741,7 @@ AdjustNotNullInheritance(Oid relid, AttrNumber attnum, { Relation pg_constraint; Form_pg_constraint conform; - bool changed = false; + Bitmapset *updated = NULL; pg_constraint = table_open(ConstraintRelationId, RowExclusiveLock); conform = (Form_pg_constraint) GETSTRUCT(tup); @@ -784,19 +777,21 @@ AdjustNotNullInheritance(Oid relid, AttrNumber attnum, ereport(ERROR, errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("too many inheritance parents")); - changed = true; + HeapTupleMarkColumnUpdated(pg_constraint, coninhcount, updated); } else if (!conform->conislocal) { - conform->conislocal = true; - changed = true; + HeapTupleUpdateField(pg_constraint, conislocal, true, conform, updated); } - if (changed) - CatalogTupleUpdate(pg_constraint, &tup->t_self, tup); + if (!bms_is_empty(updated)) + { + CatalogTupleUpdate(pg_constraint, &tup->t_self, tup, updated, NULL); + } table_close(pg_constraint, RowExclusiveLock); + bms_free(updated); return true; } @@ -928,6 +923,7 @@ RemoveConstraintById(Oid conId) Relation pgrel; HeapTuple relTup; Form_pg_class classForm; + Bitmapset *updated = NULL; pgrel = table_open(RelationRelationId, RowExclusiveLock); relTup = SearchSysCacheCopy1(RELOID, @@ -938,14 +934,14 @@ RemoveConstraintById(Oid conId) classForm = (Form_pg_class) GETSTRUCT(relTup); if (classForm->relchecks > 0) - classForm->relchecks--; + HeapTupleUpdateField(pg_class, relchecks, classForm->relchecks - 1, classForm, updated); else /* should not happen */ elog(WARNING, "relation \"%s\" has relchecks = %d", RelationGetRelationName(rel), classForm->relchecks); - CatalogTupleUpdate(pgrel, &relTup->t_self, relTup); - + CatalogTupleUpdate(pgrel, &relTup->t_self, relTup, updated, NULL); + bms_free(updated); heap_freetuple(relTup); table_close(pgrel, RowExclusiveLock); @@ -990,6 +986,7 @@ RenameConstraintById(Oid conId, const char *newname) Relation conDesc; HeapTuple tuple; Form_pg_constraint con; + Bitmapset *updated = NULL; conDesc = table_open(ConstraintRelationId, RowExclusiveLock); @@ -1020,11 +1017,13 @@ RenameConstraintById(Oid conId, const char *newname) /* OK, do the rename --- tuple is a copy, so OK to scribble on it */ namestrcpy(&(con->conname), newname); + HeapTupleMarkColumnUpdated(pg_constraint, conname, updated); - CatalogTupleUpdate(conDesc, &tuple->t_self, tuple); + CatalogTupleUpdate(conDesc, &tuple->t_self, tuple, updated, NULL); InvokeObjectPostAlterHook(ConstraintRelationId, conId, 0); + bms_free(updated); heap_freetuple(tuple); table_close(conDesc, RowExclusiveLock); } @@ -1072,15 +1071,17 @@ AlterConstraintNamespaces(Oid ownerId, Oid oldNspId, /* Don't update if the object is already part of the namespace */ if (conform->connamespace == oldNspId && oldNspId != newNspId) { + Bitmapset *updated = NULL; + tup = heap_copytuple(tup); conform = (Form_pg_constraint) GETSTRUCT(tup); - conform->connamespace = newNspId; - - CatalogTupleUpdate(conRel, &tup->t_self, tup); + HeapTupleUpdateField(pg_constraint, connamespace, newNspId, conform, updated); + CatalogTupleUpdate(conRel, &tup->t_self, tup, updated, NULL); + bms_free(updated); /* - * Note: currently, the constraint will not have its own + * NOTE: currently, the constraint will not have its own * dependency on the namespace, so we don't need to do * changeDependencyFor(). */ @@ -1116,6 +1117,7 @@ ConstraintSetParentConstraint(Oid childConstrId, newtup; ObjectAddress depender; ObjectAddress referenced; + Bitmapset *updated = NULL; constrRel = table_open(ConstraintRelationId, RowExclusiveLock); tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(childConstrId)); @@ -1131,16 +1133,17 @@ ConstraintSetParentConstraint(Oid childConstrId, elog(ERROR, "constraint %u already has a parent constraint", childConstrId); - constrForm->conislocal = false; + HeapTupleUpdateField(pg_constraint, conislocal, false, constrForm, updated); if (pg_add_s16_overflow(constrForm->coninhcount, 1, &constrForm->coninhcount)) ereport(ERROR, errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("too many inheritance parents")); - constrForm->conparentid = parentConstrId; + HeapTupleMarkColumnUpdated(pg_constraint, coninhcount, updated); + HeapTupleUpdateField(pg_constraint, conparentid, parentConstrId, constrForm, updated); - CatalogTupleUpdate(constrRel, &tuple->t_self, newtup); + CatalogTupleUpdate(constrRel, &tuple->t_self, newtup, updated, NULL); ObjectAddressSet(depender, ConstraintRelationId, childConstrId); @@ -1152,14 +1155,14 @@ ConstraintSetParentConstraint(Oid childConstrId, } else { - constrForm->coninhcount--; - constrForm->conislocal = true; - constrForm->conparentid = InvalidOid; + HeapTupleUpdateField(pg_constraint, coninhcount, constrForm->coninhcount - 1, constrForm, updated); + HeapTupleUpdateField(pg_constraint, conislocal, true, constrForm, updated); + HeapTupleUpdateField(pg_constraint, conparentid, InvalidOid, constrForm, updated); /* Make sure there's no further inheritance. */ Assert(constrForm->coninhcount == 0); - CatalogTupleUpdate(constrRel, &tuple->t_self, newtup); + CatalogTupleUpdate(constrRel, &tuple->t_self, newtup, updated, NULL); deleteDependencyRecordsForClass(ConstraintRelationId, childConstrId, ConstraintRelationId, @@ -1169,6 +1172,7 @@ ConstraintSetParentConstraint(Oid childConstrId, DEPENDENCY_PARTITION_SEC); } + bms_free(updated); ReleaseSysCache(tuple); table_close(constrRel, RowExclusiveLock); } diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c index f7b2389b019..396637f757d 100644 --- a/src/backend/commands/alter.c +++ b/src/backend/commands/alter.c @@ -180,7 +180,7 @@ AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name) AclResult aclresult; Datum *values; bool *nulls; - bool *replaces; + Bitmapset *updated = NULL; NameData nameattrdata; oldtup = SearchSysCache1(oidCacheId, ObjectIdGetDatum(objectId)); @@ -326,15 +326,21 @@ AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name) /* Build modified tuple */ values = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(Datum)); nulls = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(bool)); - replaces = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(bool)); + + /* + * NOTE: We can't use the HeapTupleMarkColumnUpdated() macro here because + * 'Anum_name' isn't a table/column name, it's a index for the relation + * passed into the function as an argument. + */ namestrcpy(&nameattrdata, new_name); values[Anum_name - 1] = NameGetDatum(&nameattrdata); - replaces[Anum_name - 1] = true; - newtup = heap_modify_tuple(oldtup, RelationGetDescr(rel), - values, nulls, replaces); + updated = bms_add_member(updated, Anum_name - FirstLowInvalidHeapAttributeNumber); + + newtup = heap_update_tuple(oldtup, RelationGetDescr(rel), + values, nulls, updated); /* Perform actual update */ - CatalogTupleUpdate(rel, &oldtup->t_self, newtup); + CatalogTupleUpdate(rel, &oldtup->t_self, newtup, updated, NULL); InvokeObjectPostAlterHook(classId, objectId, 0); @@ -357,7 +363,7 @@ AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name) /* Release memory */ pfree(values); pfree(nulls); - pfree(replaces); + bms_free(updated); heap_freetuple(newtup); ReleaseSysCache(oldtup); @@ -705,7 +711,7 @@ AlterObjectNamespace_internal(Relation rel, Oid objid, Oid nspOid) newtup; Datum *values; bool *nulls; - bool *replaces; + Bitmapset *updated = NULL; tup = SearchSysCacheCopy1(oidCacheId, ObjectIdGetDatum(objid)); if (!HeapTupleIsValid(tup)) /* should not happen */ @@ -804,19 +810,21 @@ AlterObjectNamespace_internal(Relation rel, Oid objid, Oid nspOid) /* Build modified tuple */ values = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(Datum)); nulls = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(bool)); - replaces = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(bool)); + + /* NOTE: Don't use the HeapTupleMarkColumnUpdated() macro here either. */ values[Anum_namespace - 1] = ObjectIdGetDatum(nspOid); - replaces[Anum_namespace - 1] = true; - newtup = heap_modify_tuple(tup, RelationGetDescr(rel), - values, nulls, replaces); + updated = bms_add_member(updated, Anum_namespace - FirstLowInvalidHeapAttributeNumber); + + newtup = heap_update_tuple(tup, RelationGetDescr(rel), + values, nulls, updated); /* Perform actual update */ - CatalogTupleUpdate(rel, &tup->t_self, newtup); + CatalogTupleUpdate(rel, &tup->t_self, newtup, updated, NULL); /* Release memory */ pfree(values); pfree(nulls); - pfree(replaces); + bms_free(updated); /* update dependency to point to the new schema */ if (changeDependencyFor(classId, objid, @@ -967,7 +975,7 @@ AlterObjectOwner_internal(Oid classId, Oid objectId, Oid new_ownerId) HeapTuple newtup; Datum *values; bool *nulls; - bool *replaces; + Bitmapset *updated = NULL; /* Superusers can bypass permission checks */ if (!superuser()) @@ -1014,9 +1022,12 @@ AlterObjectOwner_internal(Oid classId, Oid objectId, Oid new_ownerId) nattrs = RelationGetNumberOfAttributes(rel); values = palloc0(nattrs * sizeof(Datum)); nulls = palloc0(nattrs * sizeof(bool)); - replaces = palloc0(nattrs * sizeof(bool)); + + /* + * NOTE: Don't use the HeapTupleMarkColumnUpdated() macro here either. + */ values[Anum_owner - 1] = ObjectIdGetDatum(new_ownerId); - replaces[Anum_owner - 1] = true; + updated = bms_add_member(updated, Anum_owner - FirstLowInvalidHeapAttributeNumber); /* * Determine the modified ACL for the new owner. This is only @@ -1032,16 +1043,21 @@ AlterObjectOwner_internal(Oid classId, Oid objectId, Oid new_ownerId) newAcl = aclnewowner(DatumGetAclP(datum), old_ownerId, new_ownerId); + + /* + * NOTE: Don't use the HeapTupleMarkColumnUpdated() macro here + * either. + */ values[Anum_acl - 1] = PointerGetDatum(newAcl); - replaces[Anum_acl - 1] = true; + updated = bms_add_member(updated, Anum_acl - FirstLowInvalidHeapAttributeNumber); } } - newtup = heap_modify_tuple(oldtup, RelationGetDescr(rel), - values, nulls, replaces); + newtup = heap_update_tuple(oldtup, RelationGetDescr(rel), + values, nulls, updated); /* Perform actual update */ - CatalogTupleUpdate(rel, &newtup->t_self, newtup); + CatalogTupleUpdate(rel, &newtup->t_self, newtup, updated, NULL); UnlockTuple(rel, &oldtup->t_self, InplaceUpdateTupleLock); @@ -1051,7 +1067,7 @@ AlterObjectOwner_internal(Oid classId, Oid objectId, Oid new_ownerId) /* Release memory */ pfree(values); pfree(nulls); - pfree(replaces); + bms_free(updated); } else UnlockTuple(rel, &oldtup->t_self, InplaceUpdateTupleLock); diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index a483424152c..efdae1327ba 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -29,6 +29,7 @@ #include "catalog/index.h" #include "catalog/indexing.h" #include "catalog/pg_inherits.h" +#include "catalog/pg_statistic.h" #include "commands/progress.h" #include "commands/tablecmds.h" #include "commands/vacuum.h" @@ -1674,48 +1675,34 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats) VacAttrStats *stats = vacattrstats[attno]; HeapTuple stup, oldtup; - int i, - k, + int k, n; - Datum values[Natts_pg_statistic]; - bool nulls[Natts_pg_statistic]; - bool replaces[Natts_pg_statistic]; + Datum values[Natts_pg_statistic] = {0}; + bool nulls[Natts_pg_statistic] = {false}; + Bitmapset *updated = NULL; /* Ignore attr if we weren't able to collect stats */ if (!stats->stats_valid) continue; - /* - * Construct a new pg_statistic tuple - */ - for (i = 0; i < Natts_pg_statistic; ++i) - { - nulls[i] = false; - replaces[i] = true; - } + /* Construct a new pg_statistic tuple */ + HeapTupleUpdateSetAllColumnsUpdated(pg_statistic, updated); + HeapTupleSetValue(pg_statistic, starelid, ObjectIdGetDatum(relid), values); + HeapTupleSetValue(pg_statistic, staattnum, Int16GetDatum(stats->tupattnum), values); + HeapTupleSetValue(pg_statistic, stainherit, BoolGetDatum(inh), values); + HeapTupleSetValue(pg_statistic, stanullfrac, Float4GetDatum(stats->stanullfrac), values); + HeapTupleSetValue(pg_statistic, stawidth, Int32GetDatum(stats->stawidth), values); + HeapTupleSetValue(pg_statistic, stadistinct, Float4GetDatum(stats->stadistinct), values); - values[Anum_pg_statistic_starelid - 1] = ObjectIdGetDatum(relid); - values[Anum_pg_statistic_staattnum - 1] = Int16GetDatum(stats->tupattnum); - values[Anum_pg_statistic_stainherit - 1] = BoolGetDatum(inh); - values[Anum_pg_statistic_stanullfrac - 1] = Float4GetDatum(stats->stanullfrac); - values[Anum_pg_statistic_stawidth - 1] = Int32GetDatum(stats->stawidth); - values[Anum_pg_statistic_stadistinct - 1] = Float4GetDatum(stats->stadistinct); - i = Anum_pg_statistic_stakind1 - 1; for (k = 0; k < STATISTIC_NUM_SLOTS; k++) - { - values[i++] = Int16GetDatum(stats->stakind[k]); /* stakindN */ - } - i = Anum_pg_statistic_staop1 - 1; + HeapTupleSetValue(pg_statistic, stakind1 + k, Int16GetDatum(stats->stakind[k]), values); + for (k = 0; k < STATISTIC_NUM_SLOTS; k++) - { - values[i++] = ObjectIdGetDatum(stats->staop[k]); /* staopN */ - } - i = Anum_pg_statistic_stacoll1 - 1; + HeapTupleSetValue(pg_statistic, staop1 + k, ObjectIdGetDatum(stats->staop[k]), values); + for (k = 0; k < STATISTIC_NUM_SLOTS; k++) - { - values[i++] = ObjectIdGetDatum(stats->stacoll[k]); /* stacollN */ - } - i = Anum_pg_statistic_stanumbers1 - 1; + HeapTupleSetValue(pg_statistic, stacoll1 + k, ObjectIdGetDatum(stats->stacoll[k]), values); + for (k = 0; k < STATISTIC_NUM_SLOTS; k++) { if (stats->stanumbers[k] != NULL) @@ -1727,15 +1714,12 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats) for (n = 0; n < nnum; n++) numdatums[n] = Float4GetDatum(stats->stanumbers[k][n]); arry = construct_array_builtin(numdatums, nnum, FLOAT4OID); - values[i++] = PointerGetDatum(arry); /* stanumbersN */ + HeapTupleSetValue(pg_statistic, stanumbers1 + k, PointerGetDatum(arry), values); } else - { - nulls[i] = true; - values[i++] = (Datum) 0; - } + HeapTupleSetFieldNull(pg_statistic, stanumbers1 + k, nulls); } - i = Anum_pg_statistic_stavalues1 - 1; + for (k = 0; k < STATISTIC_NUM_SLOTS; k++) { if (stats->stavalues[k] != NULL) @@ -1748,12 +1732,11 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats) stats->statyplen[k], stats->statypbyval[k], stats->statypalign[k]); - values[i++] = PointerGetDatum(arry); /* stavaluesN */ + HeapTupleSetValue(pg_statistic, stavalues1 + k, PointerGetDatum(arry), values); } else { - nulls[i] = true; - values[i++] = (Datum) 0; + HeapTupleSetFieldNull(pg_statistic, stavalues1 + k, nulls); } } @@ -1770,26 +1753,28 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats) if (HeapTupleIsValid(oldtup)) { /* Yes, replace it */ - stup = heap_modify_tuple(oldtup, + stup = heap_update_tuple(oldtup, RelationGetDescr(sd), values, nulls, - replaces); + updated); ReleaseSysCache(oldtup); - CatalogTupleUpdateWithInfo(sd, &stup->t_self, stup, indstate); + CatalogTupleUpdate(sd, &stup->t_self, stup, updated, indstate); } else { /* No, insert new tuple */ stup = heap_form_tuple(RelationGetDescr(sd), values, nulls); - CatalogTupleInsertWithInfo(sd, stup, indstate); + CatalogTupleInsert(sd, stup, NULL); } heap_freetuple(stup); + bms_free(updated); } if (indstate != NULL) CatalogCloseIndexes(indstate); + table_close(sd, RowExclusiveLock); } diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c index a6b118a8d72..440e6db0e6f 100644 --- a/src/backend/statistics/attribute_stats.c +++ b/src/backend/statistics/attribute_stats.c @@ -18,6 +18,7 @@ #include "postgres.h" #include "access/heapam.h" +#include "access/htup.h" #include "catalog/indexing.h" #include "catalog/namespace.h" #include "catalog/pg_operator.h" @@ -106,7 +107,7 @@ static struct StatsArgInfo cleararginfo[] = static bool attribute_statistics_update(FunctionCallInfo fcinfo); static void upsert_pg_statistic(Relation starel, HeapTuple oldtup, - const Datum *values, const bool *nulls, const bool *replaces); + const Datum *values, const bool *nulls, const Bitmapset *updated); static bool delete_pg_statistic(Oid reloid, AttrNumber attnum, bool stainherit); /* @@ -163,9 +164,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo) !PG_ARGISNULL(RANGE_EMPTY_FRAC_ARG); Datum values[Natts_pg_statistic] = {0}; - bool nulls[Natts_pg_statistic] = {0}; - bool replaces[Natts_pg_statistic] = {0}; - + bool nulls[Natts_pg_statistic] = {false}; + Bitmapset *updated = NULL; bool result = true; stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG); @@ -340,25 +340,18 @@ attribute_statistics_update(FunctionCallInfo fcinfo) if (HeapTupleIsValid(statup)) heap_deform_tuple(statup, RelationGetDescr(starel), values, nulls); else - statatt_init_empty_tuple(reloid, attnum, inherited, values, nulls, - replaces); + updated = statatt_init_empty_tuple(reloid, attnum, inherited, values, nulls, + updated); /* if specified, set to argument values */ if (!PG_ARGISNULL(NULL_FRAC_ARG)) - { - values[Anum_pg_statistic_stanullfrac - 1] = PG_GETARG_DATUM(NULL_FRAC_ARG); - replaces[Anum_pg_statistic_stanullfrac - 1] = true; - } + HeapTupleUpdateValue(pg_statistic, stanullfrac, PG_GETARG_DATUM(NULL_FRAC_ARG), values, nulls, updated); + if (!PG_ARGISNULL(AVG_WIDTH_ARG)) - { - values[Anum_pg_statistic_stawidth - 1] = PG_GETARG_DATUM(AVG_WIDTH_ARG); - replaces[Anum_pg_statistic_stawidth - 1] = true; - } + HeapTupleUpdateValue(pg_statistic, stawidth, PG_GETARG_DATUM(AVG_WIDTH_ARG), values, nulls, updated); + if (!PG_ARGISNULL(N_DISTINCT_ARG)) - { - values[Anum_pg_statistic_stadistinct - 1] = PG_GETARG_DATUM(N_DISTINCT_ARG); - replaces[Anum_pg_statistic_stadistinct - 1] = true; - } + HeapTupleUpdateValue(pg_statistic, stadistinct, PG_GETARG_DATUM(N_DISTINCT_ARG), values, nulls, updated); /* STATISTIC_KIND_MCV */ if (do_mcv) @@ -373,10 +366,10 @@ attribute_statistics_update(FunctionCallInfo fcinfo) if (converted) { - statatt_set_slot(values, nulls, replaces, - STATISTIC_KIND_MCV, - eq_opr, atttypcoll, - stanumbers, false, stavalues, false); + updated = statatt_set_slot(values, nulls, updated, + STATISTIC_KIND_MCV, + eq_opr, atttypcoll, + stanumbers, false, stavalues, false); } else result = false; @@ -396,10 +389,10 @@ attribute_statistics_update(FunctionCallInfo fcinfo) if (converted) { - statatt_set_slot(values, nulls, replaces, - STATISTIC_KIND_HISTOGRAM, - lt_opr, atttypcoll, - 0, true, stavalues, false); + updated = statatt_set_slot(values, nulls, updated, + STATISTIC_KIND_HISTOGRAM, + lt_opr, atttypcoll, + 0, true, stavalues, false); } else result = false; @@ -412,10 +405,10 @@ attribute_statistics_update(FunctionCallInfo fcinfo) ArrayType *arry = construct_array_builtin(elems, 1, FLOAT4OID); Datum stanumbers = PointerGetDatum(arry); - statatt_set_slot(values, nulls, replaces, - STATISTIC_KIND_CORRELATION, - lt_opr, atttypcoll, - stanumbers, false, 0, true); + updated = statatt_set_slot(values, nulls, updated, + STATISTIC_KIND_CORRELATION, + lt_opr, atttypcoll, + stanumbers, false, 0, true); } /* STATISTIC_KIND_MCELEM */ @@ -433,10 +426,10 @@ attribute_statistics_update(FunctionCallInfo fcinfo) if (converted) { - statatt_set_slot(values, nulls, replaces, - STATISTIC_KIND_MCELEM, - elem_eq_opr, atttypcoll, - stanumbers, false, stavalues, false); + updated = statatt_set_slot(values, nulls, updated, + STATISTIC_KIND_MCELEM, + elem_eq_opr, atttypcoll, + stanumbers, false, stavalues, false); } else result = false; @@ -447,10 +440,10 @@ attribute_statistics_update(FunctionCallInfo fcinfo) { Datum stanumbers = PG_GETARG_DATUM(ELEM_COUNT_HISTOGRAM_ARG); - statatt_set_slot(values, nulls, replaces, - STATISTIC_KIND_DECHIST, - elem_eq_opr, atttypcoll, - stanumbers, false, 0, true); + updated = statatt_set_slot(values, nulls, updated, + STATISTIC_KIND_DECHIST, + elem_eq_opr, atttypcoll, + stanumbers, false, 0, true); } /* @@ -473,10 +466,10 @@ attribute_statistics_update(FunctionCallInfo fcinfo) if (converted) { - statatt_set_slot(values, nulls, replaces, - STATISTIC_KIND_BOUNDS_HISTOGRAM, - InvalidOid, InvalidOid, - 0, true, stavalues, false); + updated = statatt_set_slot(values, nulls, updated, + STATISTIC_KIND_BOUNDS_HISTOGRAM, + InvalidOid, InvalidOid, + 0, true, stavalues, false); } else result = false; @@ -500,20 +493,21 @@ attribute_statistics_update(FunctionCallInfo fcinfo) if (converted) { - statatt_set_slot(values, nulls, replaces, - STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM, - Float8LessOperator, InvalidOid, - stanumbers, false, stavalues, false); + updated = statatt_set_slot(values, nulls, updated, + STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM, + Float8LessOperator, InvalidOid, + stanumbers, false, stavalues, false); } else result = false; } - upsert_pg_statistic(starel, statup, values, nulls, replaces); + upsert_pg_statistic(starel, statup, values, nulls, updated); if (HeapTupleIsValid(statup)) ReleaseSysCache(statup); table_close(starel, RowExclusiveLock); + bms_free(updated); return result; } @@ -523,20 +517,21 @@ attribute_statistics_update(FunctionCallInfo fcinfo) */ static void upsert_pg_statistic(Relation starel, HeapTuple oldtup, - const Datum *values, const bool *nulls, const bool *replaces) + const Datum *values, const bool *nulls, const Bitmapset *updated) { HeapTuple newtup; if (HeapTupleIsValid(oldtup)) { - newtup = heap_modify_tuple(oldtup, RelationGetDescr(starel), - values, nulls, replaces); - CatalogTupleUpdate(starel, &newtup->t_self, newtup); + TupleDesc tupdesc = RelationGetDescr(starel); + + newtup = heap_update_tuple(oldtup, tupdesc, values, nulls, updated); + CatalogTupleUpdate(starel, &newtup->t_self, newtup, updated, NULL); } else { newtup = heap_form_tuple(RelationGetDescr(starel), values, nulls); - CatalogTupleInsert(starel, newtup); + CatalogTupleInsert(starel, newtup, NULL); } heap_freetuple(newtup); diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c index 9c680f1cb37..a343c513e98 100644 --- a/src/backend/statistics/stat_utils.c +++ b/src/backend/statistics/stat_utils.c @@ -633,27 +633,24 @@ statatt_build_stavalues(const char *staname, FmgrInfo *array_in, Datum d, Oid ty * The value/null pairs for stanumbers and stavalues should be calculated * based on the stakind, using statatt_build_stavalues() or constructed arrays. */ -void -statatt_set_slot(Datum *values, bool *nulls, bool *replaces, +Bitmapset * +statatt_set_slot(Datum *values, bool *nulls, Bitmapset *updated, int16 stakind, Oid staop, Oid stacoll, Datum stanumbers, bool stanumbers_isnull, Datum stavalues, bool stavalues_isnull) { int slotidx; int first_empty = -1; - AttrNumber stakind_attnum; - AttrNumber staop_attnum; - AttrNumber stacoll_attnum; /* find existing slot with given stakind */ for (slotidx = 0; slotidx < STATISTIC_NUM_SLOTS; slotidx++) { - stakind_attnum = Anum_pg_statistic_stakind1 - 1 + slotidx; + Datum d = HeapTupleValue(pg_statistic, stakind1 + slotidx, values); + int16 v = DatumGetInt16(d); - if (first_empty < 0 && - DatumGetInt16(values[stakind_attnum]) == 0) + if (first_empty < 0 && v == 0) first_empty = slotidx; - if (DatumGetInt16(values[stakind_attnum]) == stakind) + if (v == stakind) break; } @@ -665,37 +662,22 @@ statatt_set_slot(Datum *values, bool *nulls, bool *replaces, (errmsg("maximum number of statistics slots exceeded: %d", slotidx + 1))); - stakind_attnum = Anum_pg_statistic_stakind1 - 1 + slotidx; - staop_attnum = Anum_pg_statistic_staop1 - 1 + slotidx; - stacoll_attnum = Anum_pg_statistic_stacoll1 - 1 + slotidx; + if (DatumGetInt16(HeapTupleValue(pg_statistic, stakind1 + slotidx, values)) != stakind) + HeapTupleUpdateValue(pg_statistic, stakind1 + slotidx, Int16GetDatum(stakind), values, nulls, updated); + + if (DatumGetInt16(HeapTupleValue(pg_statistic, staop1 + slotidx, values)) != staop) + HeapTupleUpdateValue(pg_statistic, staop1 + slotidx, ObjectIdGetDatum(staop), values, nulls, updated); + + if (DatumGetInt16(HeapTupleValue(pg_statistic, stacoll1 + slotidx, values)) != stacoll) + HeapTupleUpdateValue(pg_statistic, stacoll1 + slotidx, ObjectIdGetDatum(stacoll), values, nulls, updated); - if (DatumGetInt16(values[stakind_attnum]) != stakind) - { - values[stakind_attnum] = Int16GetDatum(stakind); - replaces[stakind_attnum] = true; - } - if (DatumGetObjectId(values[staop_attnum]) != staop) - { - values[staop_attnum] = ObjectIdGetDatum(staop); - replaces[staop_attnum] = true; - } - if (DatumGetObjectId(values[stacoll_attnum]) != stacoll) - { - values[stacoll_attnum] = ObjectIdGetDatum(stacoll); - replaces[stacoll_attnum] = true; - } if (!stanumbers_isnull) - { - values[Anum_pg_statistic_stanumbers1 - 1 + slotidx] = stanumbers; - nulls[Anum_pg_statistic_stanumbers1 - 1 + slotidx] = false; - replaces[Anum_pg_statistic_stanumbers1 - 1 + slotidx] = true; - } + HeapTupleUpdateValue(pg_statistic, stanumbers1 + slotidx, stanumbers, values, nulls, updated); + if (!stavalues_isnull) - { - values[Anum_pg_statistic_stavalues1 - 1 + slotidx] = stavalues; - nulls[Anum_pg_statistic_stavalues1 - 1 + slotidx] = false; - replaces[Anum_pg_statistic_stavalues1 - 1 + slotidx] = true; - } + HeapTupleUpdateValue(pg_statistic, stavalues1 + slotidx, stavalues, values, nulls, updated); + + return updated; } /* @@ -711,36 +693,29 @@ statatt_set_slot(Datum *values, bool *nulls, bool *replaces, * array inserted into pg_statistic_ext_data, reloid, attnum and inherited * should be respectively set to InvalidOid, InvalidAttrNumber and false. */ -void +Bitmapset * statatt_init_empty_tuple(Oid reloid, int16 attnum, bool inherited, - Datum *values, bool *nulls, bool *replaces) + Datum *values, bool *nulls, Bitmapset *updated) { memset(nulls, true, sizeof(bool) * Natts_pg_statistic); - memset(replaces, true, sizeof(bool) * Natts_pg_statistic); - - /* This must initialize non-NULL attributes */ - values[Anum_pg_statistic_starelid - 1] = ObjectIdGetDatum(reloid); - nulls[Anum_pg_statistic_starelid - 1] = false; - values[Anum_pg_statistic_staattnum - 1] = Int16GetDatum(attnum); - nulls[Anum_pg_statistic_staattnum - 1] = false; - values[Anum_pg_statistic_stainherit - 1] = BoolGetDatum(inherited); - nulls[Anum_pg_statistic_stainherit - 1] = false; - - values[Anum_pg_statistic_stanullfrac - 1] = DEFAULT_STATATT_NULL_FRAC; - nulls[Anum_pg_statistic_stanullfrac - 1] = false; - values[Anum_pg_statistic_stawidth - 1] = DEFAULT_STATATT_AVG_WIDTH; - nulls[Anum_pg_statistic_stawidth - 1] = false; - values[Anum_pg_statistic_stadistinct - 1] = DEFAULT_STATATT_N_DISTINCT; - nulls[Anum_pg_statistic_stadistinct - 1] = false; + HeapTupleUpdateSetAllColumnsUpdated(pg_statistic, updated); + + /* must initialize non-NULL attributes */ + HeapTupleUpdateValue(pg_statistic, starelid, ObjectIdGetDatum(reloid), values, nulls, updated); + HeapTupleUpdateValue(pg_statistic, staattnum, Int16GetDatum(attnum), values, nulls, updated); + HeapTupleUpdateValue(pg_statistic, stainherit, BoolGetDatum(inherited), values, nulls, updated); + + HeapTupleUpdateValue(pg_statistic, stanullfrac, DEFAULT_STATATT_NULL_FRAC, values, nulls, updated); + HeapTupleUpdateValue(pg_statistic, stawidth, DEFAULT_STATATT_AVG_WIDTH, values, nulls, updated); + HeapTupleUpdateValue(pg_statistic, stadistinct, DEFAULT_STATATT_N_DISTINCT, values, nulls, updated); /* initialize stakind, staop, and stacoll slots */ - for (int slotnum = 0; slotnum < STATISTIC_NUM_SLOTS; slotnum++) + for (int i = 0; i < STATISTIC_NUM_SLOTS; i++) { - values[Anum_pg_statistic_stakind1 + slotnum - 1] = (Datum) 0; - nulls[Anum_pg_statistic_stakind1 + slotnum - 1] = false; - values[Anum_pg_statistic_staop1 + slotnum - 1] = ObjectIdGetDatum(InvalidOid); - nulls[Anum_pg_statistic_staop1 + slotnum - 1] = false; - values[Anum_pg_statistic_stacoll1 + slotnum - 1] = ObjectIdGetDatum(InvalidOid); - nulls[Anum_pg_statistic_stacoll1 + slotnum - 1] = false; + HeapTupleUpdateValue(pg_statistic, stakind1 + i, (Datum) 0, values, nulls, updated); + HeapTupleUpdateValue(pg_statistic, staop1 + i, ObjectIdGetDatum(InvalidOid), values, nulls, updated); + HeapTupleUpdateValue(pg_statistic, stacoll1 + i, ObjectIdGetDatum(InvalidOid), values, nulls, updated); } + + return updated; } diff --git a/src/include/access/htup.h b/src/include/access/htup.h index cfc4e1cbc58..4e116eae1af 100644 --- a/src/include/access/htup.h +++ b/src/include/access/htup.h @@ -14,6 +14,7 @@ #ifndef HTUP_H #define HTUP_H +#include "catalog/pg_aggregate_d.h" #include "storage/itemptr.h" /* typedefs and forward declarations for structs defined in htup_details.h */ @@ -77,6 +78,66 @@ typedef HeapTupleData *HeapTuple; */ #define HeapTupleIsValid(tuple) ((tuple) != NULL) +#define HeapTupleValue(table_name, field, values) \ + (values)[Anum_##table_name##_##field - 1] + +/* + * These are useful when forming tuples for CatalogTupleInsert() + */ + +#define HeapTupleSetField(table_name, field, value, form_ptr) \ + (form_ptr)->field = (value) + +#define HeapTupleSetFieldNull(table_name, field, nulls) \ + (nulls)[Anum_##table_name##_##field - 1] = true + +#define HeapTupleSetValue(table_name, field, value, values) \ + (values)[Anum_##table_name##_##field - 1] = (value) + +#define HeapTupleSetValueNull(table_name, field, values, nulls) \ + do { \ + (values)[Anum_##table_name##_##field - 1] = (Datum) 0; \ + (nulls)[Anum_##table_name##_##field - 1] = true; \ + } while(0) + +/* + * These are useful when forming tuples for CatalogTupleUpdate() + * + * Updated catalog tuples need to track which fields were changed when + * calling heap_update_tuple(), so we use a bitmap to keep track of that. + */ +#define HeapTupleMarkColumnUpdated(table_name, field, updated) \ + (updated) = bms_add_member((updated), \ + Anum_##table_name##_##field - FirstLowInvalidHeapAttributeNumber) + +#define HeapTupleUpdateSetAllColumnsUpdated(table_name, updated) \ + (updated) = bms_add_range((updated), 1 - FirstLowInvalidHeapAttributeNumber, \ + Natts_##table_name - FirstLowInvalidHeapAttributeNumber) + +#define HeapTupleSetColumnNotUpdated(table_name, field, updated) \ + (updated) = bms_del_member((updated), \ + Anum_##table_name##_##field - FirstLowInvalidHeapAttributeNumber) + +#define HeapTupleUpdateField(table_name, field, value, form_ptr, updated) \ + do { \ + (form_ptr)->field = (value); \ + HeapTupleMarkColumnUpdated(table_name, field, updated); \ + } while(0) + +#define HeapTupleUpdateValue(table_name, field, value, values, nulls, updated) \ + do { \ + (values)[Anum_##table_name##_##field - 1] = (Datum) (value); \ + (nulls)[Anum_##table_name##_##field - 1] = false; \ + HeapTupleMarkColumnUpdated(table_name, field, updated); \ + } while(0) + +#define HeapTupleUpdateValueNull(table_name, field, values, nulls, updated) \ + do { \ + (values)[Anum_##table_name##_##field - 1] = (Datum) 0; \ + (nulls)[Anum_##table_name##_##field - 1] = true; \ + HeapTupleMarkColumnUpdated(table_name, field, updated); \ + } while(0) + /* HeapTupleHeader functions implemented in utils/time/combocid.c */ extern CommandId HeapTupleHeaderGetCmin(const HeapTupleHeaderData *tup); extern CommandId HeapTupleHeaderGetCmax(const HeapTupleHeaderData *tup); diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h index d406825ff22..5bd413242b8 100644 --- a/src/include/access/htup_details.h +++ b/src/include/access/htup_details.h @@ -21,6 +21,9 @@ #include "storage/bufpage.h" #include "varatt.h" +/* Forward declarations */ +typedef struct Bitmapset Bitmapset; + /* * MaxTupleAttributeNumber limits the number of (user) columns in a tuple. * The key limit on this value is that the size of the fixed overhead for @@ -835,6 +838,11 @@ extern HeapTuple heap_modify_tuple_by_cols(HeapTuple tuple, const int *replCols, const Datum *replValues, const bool *replIsnull); +extern HeapTuple heap_update_tuple(HeapTuple tuple, + TupleDesc tupleDesc, + const Datum *replValues, + const bool *replIsnull, + const Bitmapset *replaces); extern void heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc, Datum *values, bool *isnull); extern void heap_freetuple(HeapTuple htup); diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index 58fc185d0d8..b9ae00facd0 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -37,18 +37,13 @@ typedef struct ResultRelInfo *CatalogIndexState; */ extern CatalogIndexState CatalogOpenIndexes(Relation heapRel); extern void CatalogCloseIndexes(CatalogIndexState indstate); -extern void CatalogTupleInsert(Relation heapRel, HeapTuple tup); -extern void CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup, - CatalogIndexState indstate); -extern void CatalogTuplesMultiInsertWithInfo(Relation heapRel, - TupleTableSlot **slot, - int ntuples, - CatalogIndexState indstate); +extern void CatalogTupleInsert(Relation heapRel, HeapTuple tup, + CatalogIndexState indstate); +extern void CatalogTuplesMultiInsert(Relation heapRel, TupleTableSlot **slot, + int ntuples, CatalogIndexState indstate); extern void CatalogTupleUpdate(Relation heapRel, const ItemPointerData *otid, - HeapTuple tup); -extern void CatalogTupleUpdateWithInfo(Relation heapRel, - const ItemPointerData *otid, HeapTuple tup, - CatalogIndexState indstate); + HeapTuple tuple, const struct Bitmapset *updated, + CatalogIndexState indstate); extern void CatalogTupleDelete(Relation heapRel, const ItemPointerData *tid); #endif /* INDEXING_H */ diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h index 74da7790579..77e477bcbd4 100644 --- a/src/include/statistics/stat_utils.h +++ b/src/include/statistics/stat_utils.h @@ -19,6 +19,9 @@ /* avoid including primnodes.h here */ typedef struct RangeVar RangeVar; +/* avoid including bitmapset.h */ +typedef struct Bitmapset Bitmapset; + struct StatsArgInfo { const char *argname; @@ -45,13 +48,13 @@ extern void statatt_get_type(Oid reloid, AttrNumber attnum, Oid *atttypid, int32 *atttypmod, char *atttyptype, Oid *atttypcoll, Oid *eq_opr, Oid *lt_opr); -extern void statatt_init_empty_tuple(Oid reloid, int16 attnum, bool inherited, - Datum *values, bool *nulls, bool *replaces); +extern Bitmapset *statatt_init_empty_tuple(Oid reloid, int16 attnum, bool inherited, + Datum *values, bool *nulls, Bitmapset *updated); -extern void statatt_set_slot(Datum *values, bool *nulls, bool *replaces, - int16 stakind, Oid staop, Oid stacoll, - Datum stanumbers, bool stanumbers_isnull, - Datum stavalues, bool stavalues_isnull); +extern Bitmapset *statatt_set_slot(Datum *values, bool *nulls, Bitmapset *updated, + int16 stakind, Oid staop, Oid stacoll, + Datum stanumbers, bool stanumbers_isnull, + Datum stavalues, bool stavalues_isnull); extern Datum statatt_build_stavalues(const char *staname, FmgrInfo *array_in, Datum d, Oid typid, int32 typmod, bool *ok); -- 2.51.2