PostgreSQL Source Code git master
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
funccache.c
Go to the documentation of this file.
1/*-------------------------------------------------------------------------
2 *
3 * funccache.c
4 * Function cache management.
5 *
6 * funccache.c manages a cache of function execution data. The cache
7 * is used by SQL-language and PL/pgSQL functions, and could be used by
8 * other function languages. Each cache entry is specific to the execution
9 * of a particular function (identified by OID) with specific input data
10 * types; so a polymorphic function could have many associated cache entries.
11 * Trigger functions similarly have a cache entry per trigger. These rules
12 * allow the cached data to be specific to the particular data types the
13 * function call will be dealing with.
14 *
15 *
16 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
17 * Portions Copyright (c) 1994, Regents of the University of California
18 *
19 * IDENTIFICATION
20 * src/backend/utils/cache/funccache.c
21 *
22 *-------------------------------------------------------------------------
23 */
24#include "postgres.h"
25
26#include "catalog/pg_proc.h"
28#include "commands/trigger.h"
29#include "common/hashfn.h"
30#include "funcapi.h"
31#include "utils/funccache.h"
32#include "utils/hsearch.h"
33#include "utils/syscache.h"
34
35
36/*
37 * Hash table for cached functions
38 */
39static HTAB *cfunc_hashtable = NULL;
40
42{
43 CachedFunctionHashKey key; /* hash key, must be first */
44 CachedFunction *function; /* points to data of language-specific size */
46
47#define FUNCS_PER_USER 128 /* initial table size */
48
49static uint32 cfunc_hash(const void *key, Size keysize);
50static int cfunc_match(const void *key1, const void *key2, Size keysize);
51
52
53/*
54 * Initialize the hash table on first use.
55 *
56 * The hash table will be in TopMemoryContext regardless of caller's context.
57 */
58static void
60{
62
63 /* don't allow double-initialization */
64 Assert(cfunc_hashtable == NULL);
65
66 ctl.keysize = sizeof(CachedFunctionHashKey);
67 ctl.entrysize = sizeof(CachedFunctionHashEntry);
68 ctl.hash = cfunc_hash;
69 ctl.match = cfunc_match;
70 cfunc_hashtable = hash_create("Cached function hash",
72 &ctl,
74}
75
76/*
77 * cfunc_hash: hash function for cfunc hash table
78 *
79 * We need special hash and match functions to deal with the optional
80 * presence of a TupleDesc in the hash keys. As long as we have to do
81 * that, we might as well also be smart about not comparing unused
82 * elements of the argtypes arrays.
83 */
84static uint32
85cfunc_hash(const void *key, Size keysize)
86{
88 uint32 h;
89
90 Assert(keysize == sizeof(CachedFunctionHashKey));
91 /* Hash all the fixed fields except callResultType */
92 h = DatumGetUInt32(hash_any((const unsigned char *) k,
93 offsetof(CachedFunctionHashKey, callResultType)));
94 /* Incorporate input argument types */
95 if (k->nargs > 0)
96 h = hash_combine(h,
97 DatumGetUInt32(hash_any((const unsigned char *) k->argtypes,
98 k->nargs * sizeof(Oid))));
99 /* Incorporate callResultType if present */
100 if (k->callResultType)
102 return h;
103}
104
105/*
106 * cfunc_match: match function to use with cfunc_hash
107 */
108static int
109cfunc_match(const void *key1, const void *key2, Size keysize)
110{
111 const CachedFunctionHashKey *k1 = (const CachedFunctionHashKey *) key1;
112 const CachedFunctionHashKey *k2 = (const CachedFunctionHashKey *) key2;
113
114 Assert(keysize == sizeof(CachedFunctionHashKey));
115 /* Compare all the fixed fields except callResultType */
116 if (memcmp(k1, k2, offsetof(CachedFunctionHashKey, callResultType)) != 0)
117 return 1; /* not equal */
118 /* Compare input argument types (we just verified that nargs matches) */
119 if (k1->nargs > 0 &&
120 memcmp(k1->argtypes, k2->argtypes, k1->nargs * sizeof(Oid)) != 0)
121 return 1; /* not equal */
122 /* Compare callResultType */
123 if (k1->callResultType)
124 {
125 if (k2->callResultType)
126 {
128 return 1; /* not equal */
129 }
130 else
131 return 1; /* not equal */
132 }
133 else
134 {
135 if (k2->callResultType)
136 return 1; /* not equal */
137 }
138 return 0; /* equal */
139}
140
141/*
142 * Look up the CachedFunction for the given hash key.
143 * Returns NULL if not present.
144 */
145static CachedFunction *
147{
149
150 if (cfunc_hashtable == NULL)
151 return NULL;
152
154 func_key,
155 HASH_FIND,
156 NULL);
157 if (hentry)
158 return hentry->function;
159 else
160 return NULL;
161}
162
163/*
164 * Insert a hash table entry.
165 */
166static void
168 CachedFunctionHashKey *func_key)
169{
171 bool found;
172
173 if (cfunc_hashtable == NULL)
175
177 func_key,
179 &found);
180 if (found)
181 elog(WARNING, "trying to insert a function that already exists");
182
183 /*
184 * If there's a callResultType, copy it into TopMemoryContext. If we're
185 * unlucky enough for that to fail, leave the entry with null
186 * callResultType, which will probably never match anything.
187 */
188 if (func_key->callResultType)
189 {
191
192 hentry->key.callResultType = NULL;
194 MemoryContextSwitchTo(oldcontext);
195 }
196
197 hentry->function = function;
198
199 /* Set back-link from function to hashtable key */
200 function->fn_hashkey = &hentry->key;
201}
202
203/*
204 * Delete a hash table entry.
205 */
206static void
208{
210 TupleDesc tupdesc;
211
212 /* do nothing if not in table */
213 if (function->fn_hashkey == NULL)
214 return;
215
216 /*
217 * We need to free the callResultType if present, which is slightly tricky
218 * because it has to be valid during the hashtable search. Fortunately,
219 * because we have the hashkey back-link, we can grab that pointer before
220 * deleting the hashtable entry.
221 */
222 tupdesc = function->fn_hashkey->callResultType;
223
225 function->fn_hashkey,
227 NULL);
228 if (hentry == NULL)
229 elog(WARNING, "trying to delete function that does not exist");
230
231 /* Remove back link, which no longer points to allocated storage */
232 function->fn_hashkey = NULL;
233
234 /* Release the callResultType if present */
235 if (tupdesc)
236 FreeTupleDesc(tupdesc);
237}
238
239/*
240 * Compute the hashkey for a given function invocation
241 *
242 * The hashkey is returned into the caller-provided storage at *hashkey.
243 * Note however that if a callResultType is incorporated, we've not done
244 * anything about copying that.
245 */
246static void
248 Form_pg_proc procStruct,
249 CachedFunctionHashKey *hashkey,
250 Size cacheEntrySize,
251 bool includeResultType,
252 bool forValidator)
253{
254 /* Make sure pad bytes within fixed part of the struct are zero */
255 memset(hashkey, 0, offsetof(CachedFunctionHashKey, argtypes));
256
257 /* get function OID */
258 hashkey->funcOid = fcinfo->flinfo->fn_oid;
259
260 /* get call context */
261 hashkey->isTrigger = CALLED_AS_TRIGGER(fcinfo);
262 hashkey->isEventTrigger = CALLED_AS_EVENT_TRIGGER(fcinfo);
263
264 /* record cacheEntrySize so multiple languages can share hash table */
265 hashkey->cacheEntrySize = cacheEntrySize;
266
267 /*
268 * If DML trigger, include trigger's OID in the hash, so that each trigger
269 * usage gets a different hash entry, allowing for e.g. different relation
270 * rowtypes or transition table names. In validation mode we do not know
271 * what relation or transition table names are intended to be used, so we
272 * leave trigOid zero; the hash entry built in this case will never be
273 * used for any actual calls.
274 *
275 * We don't currently need to distinguish different event trigger usages
276 * in the same way, since the special parameter variables don't vary in
277 * type in that case.
278 */
279 if (hashkey->isTrigger && !forValidator)
280 {
281 TriggerData *trigdata = (TriggerData *) fcinfo->context;
282
283 hashkey->trigOid = trigdata->tg_trigger->tgoid;
284 }
285
286 /* get input collation, if known */
287 hashkey->inputCollation = fcinfo->fncollation;
288
289 /*
290 * We include only input arguments in the hash key, since output argument
291 * types can be deduced from those, and it would require extra cycles to
292 * include the output arguments. But we have to resolve any polymorphic
293 * argument types to the real types for the call.
294 */
295 if (procStruct->pronargs > 0)
296 {
297 hashkey->nargs = procStruct->pronargs;
298 memcpy(hashkey->argtypes, procStruct->proargtypes.values,
299 procStruct->pronargs * sizeof(Oid));
300 cfunc_resolve_polymorphic_argtypes(procStruct->pronargs,
301 hashkey->argtypes,
302 NULL, /* all args are inputs */
303 fcinfo->flinfo->fn_expr,
304 forValidator,
305 NameStr(procStruct->proname));
306 }
307
308 /*
309 * While regular OUT arguments are sufficiently represented by the
310 * resolved input arguments, a function returning composite has additional
311 * variability: ALTER TABLE/ALTER TYPE could affect what it returns. Also,
312 * a function returning RECORD may depend on a column definition list to
313 * determine its output rowtype. If the caller needs the exact result
314 * type to be part of the hash lookup key, we must run
315 * get_call_result_type() to find that out.
316 */
317 if (includeResultType)
318 {
319 Oid resultTypeId;
320 TupleDesc tupdesc;
321
322 switch (get_call_result_type(fcinfo, &resultTypeId, &tupdesc))
323 {
326 hashkey->callResultType = tupdesc;
327 break;
328 default:
329 /* scalar result, or indeterminate rowtype */
330 break;
331 }
332 }
333}
334
335/*
336 * This is the same as the standard resolve_polymorphic_argtypes() function,
337 * except that:
338 * 1. We go ahead and report the error if we can't resolve the types.
339 * 2. We treat RECORD-type input arguments (not output arguments) as if
340 * they were polymorphic, replacing their types with the actual input
341 * types if we can determine those. This allows us to create a separate
342 * function cache entry for each named composite type passed to such an
343 * argument.
344 * 3. In validation mode, we have no inputs to look at, so assume that
345 * polymorphic arguments are integer, integer-array or integer-range.
346 */
347void
349 Oid *argtypes, char *argmodes,
350 Node *call_expr, bool forValidator,
351 const char *proname)
352{
353 int i;
354
355 if (!forValidator)
356 {
357 int inargno;
358
359 /* normal case, pass to standard routine */
360 if (!resolve_polymorphic_argtypes(numargs, argtypes, argmodes,
361 call_expr))
363 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
364 errmsg("could not determine actual argument "
365 "type for polymorphic function \"%s\"",
366 proname)));
367 /* also, treat RECORD inputs (but not outputs) as polymorphic */
368 inargno = 0;
369 for (i = 0; i < numargs; i++)
370 {
371 char argmode = argmodes ? argmodes[i] : PROARGMODE_IN;
372
373 if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
374 continue;
375 if (argtypes[i] == RECORDOID || argtypes[i] == RECORDARRAYOID)
376 {
377 Oid resolvedtype = get_call_expr_argtype(call_expr,
378 inargno);
379
380 if (OidIsValid(resolvedtype))
381 argtypes[i] = resolvedtype;
382 }
383 inargno++;
384 }
385 }
386 else
387 {
388 /* special validation case (no need to do anything for RECORD) */
389 for (i = 0; i < numargs; i++)
390 {
391 switch (argtypes[i])
392 {
393 case ANYELEMENTOID:
394 case ANYNONARRAYOID:
395 case ANYENUMOID: /* XXX dubious */
396 case ANYCOMPATIBLEOID:
397 case ANYCOMPATIBLENONARRAYOID:
398 argtypes[i] = INT4OID;
399 break;
400 case ANYARRAYOID:
401 case ANYCOMPATIBLEARRAYOID:
402 argtypes[i] = INT4ARRAYOID;
403 break;
404 case ANYRANGEOID:
405 case ANYCOMPATIBLERANGEOID:
406 argtypes[i] = INT4RANGEOID;
407 break;
408 case ANYMULTIRANGEOID:
409 argtypes[i] = INT4MULTIRANGEOID;
410 break;
411 default:
412 break;
413 }
414 }
415 }
416}
417
418/*
419 * delete_function - clean up as much as possible of a stale function cache
420 *
421 * We can't release the CachedFunction struct itself, because of the
422 * possibility that there are fn_extra pointers to it. We can release
423 * the subsidiary storage, but only if there are no active evaluations
424 * in progress. Otherwise we'll just leak that storage. Since the
425 * case would only occur if a pg_proc update is detected during a nested
426 * recursive call on the function, a leak seems acceptable.
427 *
428 * Note that this can be called more than once if there are multiple fn_extra
429 * pointers to the same function cache. Hence be careful not to do things
430 * twice.
431 */
432static void
434{
435 /* remove function from hash table (might be done already) */
437
438 /* release the function's storage if safe and not done already */
439 if (func->use_count == 0 &&
440 func->dcallback != NULL)
441 {
442 func->dcallback(func);
443 func->dcallback = NULL;
444 }
445}
446
447/*
448 * Compile a cached function, if no existing cache entry is suitable.
449 *
450 * fcinfo is the current call information.
451 *
452 * function should be NULL or the result of a previous call of
453 * cached_function_compile() for the same fcinfo. The caller will
454 * typically save the result in fcinfo->flinfo->fn_extra, or in a
455 * field of a struct pointed to by fn_extra, to re-use in later
456 * calls within the same query.
457 *
458 * ccallback and dcallback are function-language-specific callbacks to
459 * compile and delete a cached function entry. dcallback can be NULL
460 * if there's nothing for it to do.
461 *
462 * cacheEntrySize is the function-language-specific size of the cache entry
463 * (which embeds a CachedFunction struct and typically has many more fields
464 * after that).
465 *
466 * If includeResultType is true and the function returns composite,
467 * include the actual result descriptor in the cache lookup key.
468 *
469 * If forValidator is true, we're only compiling for validation purposes,
470 * and so some checks are skipped.
471 *
472 * Note: it's important for this to fall through quickly if the function
473 * has already been compiled.
474 *
475 * Note: this function leaves the "use_count" field as zero. The caller
476 * is expected to increment the use_count and decrement it when done with
477 * the cache entry.
478 */
484 Size cacheEntrySize,
485 bool includeResultType,
486 bool forValidator)
487{
488 Oid funcOid = fcinfo->flinfo->fn_oid;
489 HeapTuple procTup;
490 Form_pg_proc procStruct;
491 CachedFunctionHashKey hashkey;
492 bool function_valid = false;
493 bool hashkey_valid = false;
494
495 /*
496 * Lookup the pg_proc tuple by Oid; we'll need it in any case
497 */
498 procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid));
499 if (!HeapTupleIsValid(procTup))
500 elog(ERROR, "cache lookup failed for function %u", funcOid);
501 procStruct = (Form_pg_proc) GETSTRUCT(procTup);
502
503 /*
504 * Do we already have a cache entry for the current FmgrInfo? If not, try
505 * to find one in the hash table.
506 */
507recheck:
508 if (!function)
509 {
510 /* Compute hashkey using function signature and actual arg types */
511 compute_function_hashkey(fcinfo, procStruct, &hashkey,
512 cacheEntrySize, includeResultType,
513 forValidator);
514 hashkey_valid = true;
515
516 /* And do the lookup */
518 }
519
520 if (function)
521 {
522 /* We have a compiled function, but is it still valid? */
523 if (function->fn_xmin == HeapTupleHeaderGetRawXmin(procTup->t_data) &&
524 ItemPointerEquals(&function->fn_tid, &procTup->t_self))
525 function_valid = true;
526 else
527 {
528 /*
529 * Nope, so remove it from hashtable and try to drop associated
530 * storage (if not done already).
531 */
533
534 /*
535 * If the function isn't in active use then we can overwrite the
536 * func struct with new data, allowing any other existing fn_extra
537 * pointers to make use of the new definition on their next use.
538 * If it is in use then just leave it alone and make a new one.
539 * (The active invocations will run to completion using the
540 * previous definition, and then the cache entry will just be
541 * leaked; doesn't seem worth adding code to clean it up, given
542 * what a corner case this is.)
543 *
544 * If we found the function struct via fn_extra then it's possible
545 * a replacement has already been made, so go back and recheck the
546 * hashtable.
547 */
548 if (function->use_count != 0)
549 {
550 function = NULL;
551 if (!hashkey_valid)
552 goto recheck;
553 }
554 }
555 }
556
557 /*
558 * If the function wasn't found or was out-of-date, we have to compile it.
559 */
560 if (!function_valid)
561 {
562 /*
563 * Calculate hashkey if we didn't already; we'll need it to store the
564 * completed function.
565 */
566 if (!hashkey_valid)
567 compute_function_hashkey(fcinfo, procStruct, &hashkey,
568 cacheEntrySize, includeResultType,
569 forValidator);
570
571 /*
572 * Create the new function struct, if not done already. The function
573 * structs are never thrown away, so keep them in TopMemoryContext.
574 */
575 Assert(cacheEntrySize >= sizeof(CachedFunction));
576 if (function == NULL)
577 {
580 }
581 else
582 {
583 /* re-using a previously existing struct, so clear it out */
584 memset(function, 0, cacheEntrySize);
585 }
586
587 /*
588 * Fill in the CachedFunction part. fn_hashkey and use_count remain
589 * zeroes for now.
590 */
591 function->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data);
592 function->fn_tid = procTup->t_self;
593 function->dcallback = dcallback;
594
595 /*
596 * Do the hard, language-specific part.
597 */
598 ccallback(fcinfo, procTup, &hashkey, function, forValidator);
599
600 /*
601 * Add the completed struct to the hash table.
602 */
604 }
605
606 ReleaseSysCache(procTup);
607
608 /*
609 * Finally return the compiled function
610 */
611 return function;
612}
#define NameStr(name)
Definition: c.h:717
uint32_t uint32
Definition: c.h:502
#define OidIsValid(objectId)
Definition: c.h:746
size_t Size
Definition: c.h:576
void * hash_search(HTAB *hashp, const void *keyPtr, HASHACTION action, bool *foundPtr)
Definition: dynahash.c:955
HTAB * hash_create(const char *tabname, long nelem, const HASHCTL *info, int flags)
Definition: dynahash.c:352
int errcode(int sqlerrcode)
Definition: elog.c:854
int errmsg(const char *fmt,...)
Definition: elog.c:1071
#define WARNING
Definition: elog.h:36
#define ERROR
Definition: elog.h:39
#define elog(elevel,...)
Definition: elog.h:226
#define ereport(elevel,...)
Definition: elog.h:149
#define CALLED_AS_EVENT_TRIGGER(fcinfo)
Definition: event_trigger.h:49
Oid get_call_expr_argtype(Node *expr, int argnum)
Definition: fmgr.c:1929
bool resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, Node *call_expr)
Definition: funcapi.c:1064
TypeFuncClass get_call_result_type(FunctionCallInfo fcinfo, Oid *resultTypeId, TupleDesc *resultTupleDesc)
Definition: funcapi.c:276
@ TYPEFUNC_COMPOSITE
Definition: funcapi.h:149
@ TYPEFUNC_COMPOSITE_DOMAIN
Definition: funcapi.h:150
static void compute_function_hashkey(FunctionCallInfo fcinfo, Form_pg_proc procStruct, CachedFunctionHashKey *hashkey, Size cacheEntrySize, bool includeResultType, bool forValidator)
Definition: funccache.c:247
static void cfunc_hashtable_init(void)
Definition: funccache.c:59
#define FUNCS_PER_USER
Definition: funccache.c:47
static uint32 cfunc_hash(const void *key, Size keysize)
Definition: funccache.c:85
static HTAB * cfunc_hashtable
Definition: funccache.c:39
static void cfunc_hashtable_insert(CachedFunction *function, CachedFunctionHashKey *func_key)
Definition: funccache.c:167
static int cfunc_match(const void *key1, const void *key2, Size keysize)
Definition: funccache.c:109
struct CachedFunctionHashEntry CachedFunctionHashEntry
void cfunc_resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, Node *call_expr, bool forValidator, const char *proname)
Definition: funccache.c:348
CachedFunction * cached_function_compile(FunctionCallInfo fcinfo, CachedFunction *function, CachedFunctionCompileCallback ccallback, CachedFunctionDeleteCallback dcallback, Size cacheEntrySize, bool includeResultType, bool forValidator)
Definition: funccache.c:480
static CachedFunction * cfunc_hashtable_lookup(CachedFunctionHashKey *func_key)
Definition: funccache.c:146
static void cfunc_hashtable_delete(CachedFunction *function)
Definition: funccache.c:207
static void delete_function(CachedFunction *func)
Definition: funccache.c:433
void(* CachedFunctionCompileCallback)(FunctionCallInfo fcinfo, HeapTuple procTup, const struct CachedFunctionHashKey *hashkey, struct CachedFunction *function, bool forValidator)
Definition: funccache.h:35
void(* CachedFunctionDeleteCallback)(struct CachedFunction *cfunc)
Definition: funccache.h:45
struct CachedFunctionHashKey CachedFunctionHashKey
static uint32 hash_combine(uint32 a, uint32 b)
Definition: hashfn.h:68
static Datum hash_any(const unsigned char *k, int keylen)
Definition: hashfn.h:31
Assert(PointerIsAligned(start, uint64))
@ HASH_FIND
Definition: hsearch.h:113
@ HASH_REMOVE
Definition: hsearch.h:115
@ HASH_ENTER
Definition: hsearch.h:114
#define HASH_ELEM
Definition: hsearch.h:95
#define HASH_COMPARE
Definition: hsearch.h:99
#define HASH_FUNCTION
Definition: hsearch.h:98
#define HeapTupleIsValid(tuple)
Definition: htup.h:78
static TransactionId HeapTupleHeaderGetRawXmin(const HeapTupleHeaderData *tup)
Definition: htup_details.h:318
static void * GETSTRUCT(const HeapTupleData *tuple)
Definition: htup_details.h:728
int i
Definition: isn.c:77
bool ItemPointerEquals(ItemPointer pointer1, ItemPointer pointer2)
Definition: itemptr.c:35
void * MemoryContextAllocZero(MemoryContext context, Size size)
Definition: mcxt.c:1290
MemoryContext TopMemoryContext
Definition: mcxt.c:165
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
Definition: palloc.h:124
on_exit_nicely_callback function
FormData_pg_proc * Form_pg_proc
Definition: pg_proc.h:136
NameData proname
Definition: pg_proc.h:35
static uint32 DatumGetUInt32(Datum X)
Definition: postgres.h:227
static Datum ObjectIdGetDatum(Oid X)
Definition: postgres.h:257
unsigned int Oid
Definition: postgres_ext.h:30
tree ctl
Definition: radixtree.h:1838
CachedFunctionHashKey key
Definition: funccache.c:43
CachedFunction * function
Definition: funccache.c:44
TupleDesc callResultType
Definition: funccache.h:92
Oid argtypes[FUNC_MAX_ARGS]
Definition: funccache.h:98
CachedFunctionDeleteCallback dcallback
Definition: funccache.h:114
uint64 use_count
Definition: funccache.h:117
fmNodePtr fn_expr
Definition: fmgr.h:66
Oid fn_oid
Definition: fmgr.h:59
FmgrInfo * flinfo
Definition: fmgr.h:87
fmNodePtr context
Definition: fmgr.h:88
Definition: dynahash.c:220
ItemPointerData t_self
Definition: htup.h:65
HeapTupleHeader t_data
Definition: htup.h:68
Definition: nodes.h:135
Trigger * tg_trigger
Definition: trigger.h:38
Oid tgoid
Definition: reltrigger.h:25
void ReleaseSysCache(HeapTuple tuple)
Definition: syscache.c:269
HeapTuple SearchSysCache1(int cacheId, Datum key1)
Definition: syscache.c:221
#define CALLED_AS_TRIGGER(fcinfo)
Definition: trigger.h:26
void FreeTupleDesc(TupleDesc tupdesc)
Definition: tupdesc.c:495
uint32 hashRowType(TupleDesc desc)
Definition: tupdesc.c:806
TupleDesc CreateTupleDescCopy(TupleDesc tupdesc)
Definition: tupdesc.c:245
bool equalRowTypes(TupleDesc tupdesc1, TupleDesc tupdesc2)
Definition: tupdesc.c:770