From 8c4d491c2cb02cdb51805f52eaa8c0f5fc37ac4c Mon Sep 17 00:00:00 2001 From: Vaibhave Sekar Date: Sun, 25 Jan 2026 10:34:46 +0000 Subject: [PATCH 1/2] Stable tupDesc_identifier. --- src/backend/utils/cache/typcache.c | 111 +++++++++++++++++++++++++++-- 1 file changed, 104 insertions(+), 7 deletions(-) diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c index aa4720cb598..6795ac294c1 100644 --- a/src/backend/utils/cache/typcache.c +++ b/src/backend/utils/cache/typcache.c @@ -57,6 +57,7 @@ #include "catalog/pg_range.h" #include "catalog/pg_type.h" #include "commands/defrem.h" +#include "common/hashfn.h" #include "common/int.h" #include "executor/executor.h" #include "lib/dshash.h" @@ -287,12 +288,15 @@ static int32 RecordCacheArrayLen = 0; /* allocated length of above array */ static int32 NextRecordTypmod = 0; /* number of entries used */ /* - * Process-wide counter for generating unique tupledesc identifiers. + * Process-wide counter for generating unique tupledesc identifiers for + * record (RECORDOID) tupdescs. Named composite types now derive their + * identifiers from a deterministic signature of the tupledesc contents. * Zero and one (INVALID_TUPLEDESC_IDENTIFIER) aren't allowed to be chosen * as identifiers, so we start the counter at INVALID_TUPLEDESC_IDENTIFIER. */ static uint64 tupledesc_id_counter = INVALID_TUPLEDESC_IDENTIFIER; +static uint64 compute_tupdesc_identifier(TupleDesc tupdesc); static void load_typcache_tupdesc(TypeCacheEntry *typentry); static void load_rangetype_info(TypeCacheEntry *typentry); static void load_multirangetype_info(TypeCacheEntry *typentry); @@ -331,6 +335,75 @@ static dsa_pointer share_tupledesc(dsa_area *area, TupleDesc tupdesc, uint32 typmod); +/* + * compute_tupdesc_identifier + * + * Build a stable, deterministic 64-bit signature for a tuple descriptor so + * that unchanged composite type definitions keep the same identifier across + * cache reloads and backends. We hash only structural fields that affect the + * layout or logical contract of the rowtype, avoiding volatile fields such as + * attcacheoff. + */ +static uint64 +compute_tupdesc_identifier(TupleDesc tupdesc) +{ + uint64 hash; + int natts = tupdesc->natts; + + hash = hash_bytes_extended((const unsigned char *) &tupdesc->tdtypeid, + sizeof(Oid), 0); + hash = hash_bytes_extended((const unsigned char *) &tupdesc->tdtypmod, + sizeof(int32), hash); + hash = hash_bytes_extended((const unsigned char *) &natts, + sizeof(int), hash); + + for (int i = 0; i < natts; i++) + { + Form_pg_attribute att = TupleDescAttr(tupdesc, i); + + hash = hash_bytes_extended((const unsigned char *) att->attname.data, + NAMEDATALEN, hash); + hash = hash_bytes_extended((const unsigned char *) &att->attisdropped, + sizeof(bool), hash); + hash = hash_bytes_extended((const unsigned char *) &att->attnotnull, + sizeof(bool), hash); + hash = hash_bytes_extended((const unsigned char *) &att->attlen, + sizeof(int16), hash); + hash = hash_bytes_extended((const unsigned char *) &att->attbyval, + sizeof(bool), hash); + hash = hash_bytes_extended((const unsigned char *) &att->attalign, + sizeof(char), hash); + hash = hash_bytes_extended((const unsigned char *) &att->attstorage, + sizeof(char), hash); + hash = hash_bytes_extended((const unsigned char *) &att->attcompression, + sizeof(char), hash); + hash = hash_bytes_extended((const unsigned char *) &att->atttypid, + sizeof(Oid), hash); + hash = hash_bytes_extended((const unsigned char *) &att->atttypmod, + sizeof(int32), hash); + hash = hash_bytes_extended((const unsigned char *) &att->attcollation, + sizeof(Oid), hash); + hash = hash_bytes_extended((const unsigned char *) &att->attndims, + sizeof(int32), hash); + hash = hash_bytes_extended((const unsigned char *) &att->attidentity, + sizeof(char), hash); + hash = hash_bytes_extended((const unsigned char *) &att->attgenerated, + sizeof(char), hash); + hash = hash_bytes_extended((const unsigned char *) &att->atthasmissing, + sizeof(bool), hash); + } + + /* Avoid reserved identifiers (0 and 1). */ + if (hash <= INVALID_TUPLEDESC_IDENTIFIER) + hash = hash_bytes_extended((const unsigned char *) &natts, + sizeof(int), UINT64CONST(0xA5A5A5A5A5A5A5A5)); + if (hash <= INVALID_TUPLEDESC_IDENTIFIER) + hash = INVALID_TUPLEDESC_IDENTIFIER + 1; + + return hash; +} + + /* * lookup_type_cache * @@ -898,11 +971,14 @@ load_typcache_tupdesc(TypeCacheEntry *typentry) Assert(typentry->tupDesc->tdrefcount > 0); typentry->tupDesc->tdrefcount++; + typentry->tupDesc_identifier = compute_tupdesc_identifier(typentry->tupDesc); + /* - * In future, we could take some pains to not change tupDesc_identifier if - * the tupdesc didn't really change; but for now it's not worth it. + * Stash a version marker in tdtypmod so composite Datums can detect stale + * definitions later. Keep it non-negative so callers can reliably check + * for it. */ - typentry->tupDesc_identifier = ++tupledesc_id_counter; + typentry->tupDesc->tdtypmod = (int32) (typentry->tupDesc_identifier & 0x7FFFFFFF); relation_close(rel, AccessShareLock); } @@ -1748,9 +1824,30 @@ lookup_rowtype_tupdesc_internal(Oid type_id, int32 typmod, bool noError) typentry = lookup_type_cache(type_id, TYPECACHE_TUPDESC); if (typentry->tupDesc == NULL && !noError) ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("type %s is not composite", - format_type_be(type_id)))); + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("type %s is not composite", + format_type_be(type_id)))); + else if (typentry->tupDesc == NULL) + return NULL; + + /* + * If the caller supplied a non-negative typmod, treat it as the version + * of the composite type that was current when the Datum was formed. If + * it doesn't match the current definition, fail fast instead of + * interpreting the tuple using the wrong layout. + */ + if (typmod >= 0) + { + int32 expected_typmod = typentry->tupDesc->tdtypmod; + + if (expected_typmod >= 0 && typmod != expected_typmod) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("type %s has changed", + format_type_be(type_id)), + errdetail("The composite value was created using a previous definition of type %s.", + format_type_be(type_id)))); + } return typentry->tupDesc; } else -- 2.43.0