mirror of
https://github.com/postgres/postgres.git
synced 2025-07-09 22:41:56 +03:00
Make plpgsql use its DTYPE_REC code paths for composite-type variables.
Formerly, DTYPE_REC was used only for variables declared as "record"; variables of named composite types used DTYPE_ROW, which is faster for some purposes but much less flexible. In particular, the ROW code paths are entirely incapable of dealing with DDL-caused changes to the number or data types of the columns of a row variable, once a particular plpgsql function has been parsed for the first time in a session. And, since the stored representation of a ROW isn't a tuple, there wasn't any easy way to deal with variables of domain-over-composite types, since the domain constraint checking code would expect the value to be checked to be a tuple. A lesser, but still real, annoyance is that ROW format cannot represent a true NULL composite value, only a row of per-field NULL values, which is not exactly the same thing. Hence, switch to using DTYPE_REC for all composite-typed variables, whether "record", named composite type, or domain over named composite type. DTYPE_ROW remains but is used only for its native purpose, to represent a fixed-at-compile-time list of variables, for instance the targets of an INTO clause. To accomplish this without taking significant performance losses, introduce infrastructure that allows storing composite-type variables as "expanded objects", similar to the "expanded array" infrastructure introduced in commit1dc5ebc90
. A composite variable's value is thereby kept (most of the time) in the form of separate Datums, so that field accesses and updates are not much more expensive than they were in the ROW format. This holds the line, more or less, on performance of variables of named composite types in field-access-intensive microbenchmarks, and makes variables declared "record" perform much better than before in similar tests. In addition, the logic involved with enforcing composite-domain constraints against updates of individual fields is in the expanded record infrastructure not plpgsql proper, so that it might be reusable for other purposes. In further support of this, introduce a typcache feature for assigning a unique-within-process identifier to each distinct tuple descriptor of interest; in particular, DDL alterations on composite types result in a new identifier for that type. This allows very cheap detection of the need to refresh tupdesc-dependent data. This improves on the "tupDescSeqNo" idea I had in commit687f096ea
: that assigned identifying sequence numbers to successive versions of individual composite types, but the numbers were not unique across different types, nor was there support for assigning numbers to registered record types. In passing, allow plpgsql functions to accept as well as return type "record". There was no good reason for the old restriction, and it was out of step with most of the other PLs. Tom Lane, reviewed by Pavel Stehule Discussion: https://postgr.es/m/8962.1514399547@sss.pgh.pa.us
This commit is contained in:
84
src/backend/utils/cache/typcache.c
vendored
84
src/backend/utils/cache/typcache.c
vendored
@ -259,12 +259,22 @@ static const dshash_parameters srtr_typmod_table_params = {
|
||||
LWTRANCHE_SESSION_TYPMOD_TABLE
|
||||
};
|
||||
|
||||
/* hashtable for recognizing registered record types */
|
||||
static HTAB *RecordCacheHash = NULL;
|
||||
|
||||
/* arrays of info about registered record types, indexed by assigned typmod */
|
||||
static TupleDesc *RecordCacheArray = NULL;
|
||||
static int32 RecordCacheArrayLen = 0; /* allocated length of array */
|
||||
static uint64 *RecordIdentifierArray = NULL;
|
||||
static int32 RecordCacheArrayLen = 0; /* allocated length of above arrays */
|
||||
static int32 NextRecordTypmod = 0; /* number of entries used */
|
||||
|
||||
/*
|
||||
* Process-wide counter for generating unique tupledesc identifiers.
|
||||
* 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 void load_typcache_tupdesc(TypeCacheEntry *typentry);
|
||||
static void load_rangetype_info(TypeCacheEntry *typentry);
|
||||
static void load_domaintype_info(TypeCacheEntry *typentry);
|
||||
@ -793,10 +803,10 @@ load_typcache_tupdesc(TypeCacheEntry *typentry)
|
||||
typentry->tupDesc->tdrefcount++;
|
||||
|
||||
/*
|
||||
* In future, we could take some pains to not increment the seqno if the
|
||||
* tupdesc didn't really change; but for now it's not worth it.
|
||||
* 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.
|
||||
*/
|
||||
typentry->tupDescSeqNo++;
|
||||
typentry->tupDesc_identifier = ++tupledesc_id_counter;
|
||||
|
||||
relation_close(rel, AccessShareLock);
|
||||
}
|
||||
@ -1496,7 +1506,8 @@ cache_range_element_properties(TypeCacheEntry *typentry)
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure that RecordCacheArray is large enough to store 'typmod'.
|
||||
* Make sure that RecordCacheArray and RecordIdentifierArray are large enough
|
||||
* to store 'typmod'.
|
||||
*/
|
||||
static void
|
||||
ensure_record_cache_typmod_slot_exists(int32 typmod)
|
||||
@ -1505,6 +1516,8 @@ ensure_record_cache_typmod_slot_exists(int32 typmod)
|
||||
{
|
||||
RecordCacheArray = (TupleDesc *)
|
||||
MemoryContextAllocZero(CacheMemoryContext, 64 * sizeof(TupleDesc));
|
||||
RecordIdentifierArray = (uint64 *)
|
||||
MemoryContextAllocZero(CacheMemoryContext, 64 * sizeof(uint64));
|
||||
RecordCacheArrayLen = 64;
|
||||
}
|
||||
|
||||
@ -1519,6 +1532,10 @@ ensure_record_cache_typmod_slot_exists(int32 typmod)
|
||||
newlen * sizeof(TupleDesc));
|
||||
memset(RecordCacheArray + RecordCacheArrayLen, 0,
|
||||
(newlen - RecordCacheArrayLen) * sizeof(TupleDesc));
|
||||
RecordIdentifierArray = (uint64 *) repalloc(RecordIdentifierArray,
|
||||
newlen * sizeof(uint64));
|
||||
memset(RecordIdentifierArray + RecordCacheArrayLen, 0,
|
||||
(newlen - RecordCacheArrayLen) * sizeof(uint64));
|
||||
RecordCacheArrayLen = newlen;
|
||||
}
|
||||
}
|
||||
@ -1581,11 +1598,17 @@ lookup_rowtype_tupdesc_internal(Oid type_id, int32 typmod, bool noError)
|
||||
|
||||
/*
|
||||
* Our local array can now point directly to the TupleDesc
|
||||
* in shared memory.
|
||||
* in shared memory, which is non-reference-counted.
|
||||
*/
|
||||
RecordCacheArray[typmod] = tupdesc;
|
||||
Assert(tupdesc->tdrefcount == -1);
|
||||
|
||||
/*
|
||||
* We don't share tupdesc identifiers across processes, so
|
||||
* assign one locally.
|
||||
*/
|
||||
RecordIdentifierArray[typmod] = ++tupledesc_id_counter;
|
||||
|
||||
dshash_release_lock(CurrentSession->shared_typmod_table,
|
||||
entry);
|
||||
|
||||
@ -1790,12 +1813,61 @@ assign_record_type_typmod(TupleDesc tupDesc)
|
||||
RecordCacheArray[entDesc->tdtypmod] = entDesc;
|
||||
recentry->tupdesc = entDesc;
|
||||
|
||||
/* Assign a unique tupdesc identifier, too. */
|
||||
RecordIdentifierArray[entDesc->tdtypmod] = ++tupledesc_id_counter;
|
||||
|
||||
/* Update the caller's tuple descriptor. */
|
||||
tupDesc->tdtypmod = entDesc->tdtypmod;
|
||||
|
||||
MemoryContextSwitchTo(oldcxt);
|
||||
}
|
||||
|
||||
/*
|
||||
* assign_record_type_identifier
|
||||
*
|
||||
* Get an identifier, which will be unique over the lifespan of this backend
|
||||
* process, for the current tuple descriptor of the specified composite type.
|
||||
* For named composite types, the value is guaranteed to change if the type's
|
||||
* definition does. For registered RECORD types, the value will not change
|
||||
* once assigned, since the registered type won't either. If an anonymous
|
||||
* RECORD type is specified, we return a new identifier on each call.
|
||||
*/
|
||||
uint64
|
||||
assign_record_type_identifier(Oid type_id, int32 typmod)
|
||||
{
|
||||
if (type_id != RECORDOID)
|
||||
{
|
||||
/*
|
||||
* It's a named composite type, so use the regular typcache.
|
||||
*/
|
||||
TypeCacheEntry *typentry;
|
||||
|
||||
typentry = lookup_type_cache(type_id, TYPECACHE_TUPDESC);
|
||||
if (typentry->tupDesc == NULL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("type %s is not composite",
|
||||
format_type_be(type_id))));
|
||||
Assert(typentry->tupDesc_identifier != 0);
|
||||
return typentry->tupDesc_identifier;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* It's a transient record type, so look in our record-type table.
|
||||
*/
|
||||
if (typmod >= 0 && typmod < RecordCacheArrayLen &&
|
||||
RecordCacheArray[typmod] != NULL)
|
||||
{
|
||||
Assert(RecordIdentifierArray[typmod] != 0);
|
||||
return RecordIdentifierArray[typmod];
|
||||
}
|
||||
|
||||
/* For anonymous or unrecognized record type, generate a new ID */
|
||||
return ++tupledesc_id_counter;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the amout of shmem required to hold a SharedRecordTypmodRegistry.
|
||||
* This exists only to avoid exposing private innards of
|
||||
|
Reference in New Issue
Block a user