diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2004-04-01 21:28:47 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2004-04-01 21:28:47 +0000 |
commit | 375369acd1c621bdc683c58bc9c31d4e79d14849 (patch) | |
tree | f29974842cea4105c92da6031bac736ddf5f833a /src/backend/executor | |
parent | 8590a62b75d3dba24609eb46b34fac13ed881d9e (diff) | |
download | postgresql-375369acd1c621bdc683c58bc9c31d4e79d14849.tar.gz postgresql-375369acd1c621bdc683c58bc9c31d4e79d14849.zip |
Replace TupleTableSlot convention for whole-row variables and function
results with tuples as ordinary varlena Datums. This commit does not
in itself do much for us, except eliminate the horrid memory leak
associated with evaluation of whole-row variables. However, it lays the
groundwork for allowing composite types as table columns, and perhaps
some other useful features as well. Per my proposal of a few days ago.
Diffstat (limited to 'src/backend/executor')
-rw-r--r-- | src/backend/executor/execQual.c | 276 | ||||
-rw-r--r-- | src/backend/executor/execTuples.c | 61 | ||||
-rw-r--r-- | src/backend/executor/functions.c | 124 | ||||
-rw-r--r-- | src/backend/executor/nodeFunctionscan.c | 39 | ||||
-rw-r--r-- | src/backend/executor/spi.c | 55 |
5 files changed, 270 insertions, 285 deletions
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 4c6f95a9a6f..b27e86122bc 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.157 2004/03/24 22:40:28 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.158 2004/04/01 21:28:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -49,6 +49,7 @@ #include "utils/array.h" #include "utils/builtins.h" #include "utils/lsyscache.h" +#include "utils/typcache.h" /* static function decls */ @@ -110,7 +111,7 @@ static Datum ExecEvalCoerceToDomain(CoerceToDomainState *cstate, static Datum ExecEvalCoerceToDomainValue(ExprState *exprstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); -static Datum ExecEvalFieldSelect(GenericExprState *fstate, +static Datum ExecEvalFieldSelect(FieldSelectState *fstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalRelabelType(GenericExprState *exprstate, @@ -420,16 +421,25 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext, *isDone = ExprSingleResult; /* - * get the slot we want + * Get the slot and attribute number we want + * + * The asserts check that references to system attributes only appear + * at the level of a relation scan; at higher levels, system attributes + * must be treated as ordinary variables (since we no longer have access + * to the original tuple). */ + attnum = variable->varattno; + switch (variable->varno) { case INNER: /* get the tuple from the inner node */ slot = econtext->ecxt_innertuple; + Assert(attnum > 0); break; case OUTER: /* get the tuple from the outer node */ slot = econtext->ecxt_outertuple; + Assert(attnum > 0); break; default: /* get the tuple from the relation being @@ -444,8 +454,6 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext, heapTuple = slot->val; tuple_type = slot->ttc_tupleDescriptor; - attnum = variable->varattno; - /* * Some checks that are only applied for user attribute numbers * (bogus system attnums will be caught inside heap_getattr). @@ -481,38 +489,6 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext, Assert(variable->vartype == tuple_type->attrs[attnum - 1]->atttypid); } - /* - * If the attribute number is invalid, then we are supposed to return - * the entire tuple; we give back a whole slot so that callers know - * what the tuple looks like. - * - * XXX this is a horrid crock: since the pointer to the slot might live - * longer than the current evaluation context, we are forced to copy - * the tuple and slot into a long-lived context --- we use the - * econtext's per-query memory which should be safe enough. This - * represents a serious memory leak if many such tuples are processed - * in one command, however. We ought to redesign the representation - * of whole-tuple datums so that this is not necessary. - * - * We assume it's OK to point to the existing tupleDescriptor, rather - * than copy that too. - */ - if (attnum == InvalidAttrNumber) - { - MemoryContext oldContext; - TupleTableSlot *tempSlot; - HeapTuple tup; - - oldContext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); - tempSlot = MakeTupleTableSlot(); - tup = heap_copytuple(heapTuple); - ExecStoreTuple(tup, tempSlot, InvalidBuffer, true); - ExecSetSlotDescriptor(tempSlot, tuple_type, false); - MemoryContextSwitchTo(oldContext); - *isNull = false; - return PointerGetDatum(tempSlot); - } - result = heap_getattr(heapTuple, /* tuple containing attribute */ attnum, /* attribute number of desired * attribute */ @@ -656,17 +632,23 @@ ExecEvalParam(ExprState *exprstate, ExprContext *econtext, * GetAttributeByName * GetAttributeByNum * - * These are functions which return the value of the - * named attribute out of the tuple from the arg slot. User defined + * These functions return the value of the requested attribute + * out of the given tuple Datum. * C functions which take a tuple as an argument are expected - * to use this. Ex: overpaid(EMP) might call GetAttributeByNum(). + * to use these. Ex: overpaid(EMP) might call GetAttributeByNum(). + * Note: these are actually rather slow because they do a typcache + * lookup on each call. */ Datum -GetAttributeByNum(TupleTableSlot *slot, +GetAttributeByNum(HeapTupleHeader tuple, AttrNumber attrno, bool *isNull) { - Datum retval; + Datum result; + Oid tupType; + int32 tupTypmod; + TupleDesc tupDesc; + HeapTupleData tmptup; if (!AttributeNumberIsValid(attrno)) elog(ERROR, "invalid attribute number %d", attrno); @@ -674,29 +656,43 @@ GetAttributeByNum(TupleTableSlot *slot, if (isNull == NULL) elog(ERROR, "a NULL isNull pointer was passed"); - if (TupIsNull(slot)) + if (tuple == NULL) { + /* Kinda bogus but compatible with old behavior... */ *isNull = true; return (Datum) 0; } - retval = heap_getattr(slot->val, + tupType = HeapTupleHeaderGetTypeId(tuple); + tupTypmod = HeapTupleHeaderGetTypMod(tuple); + tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + + /* + * heap_getattr needs a HeapTuple not a bare HeapTupleHeader. We set + * all the fields in the struct just in case user tries to inspect + * system columns. + */ + tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); + ItemPointerSetInvalid(&(tmptup.t_self)); + tmptup.t_tableOid = InvalidOid; + tmptup.t_data = tuple; + + result = heap_getattr(&tmptup, attrno, - slot->ttc_tupleDescriptor, + tupDesc, isNull); - if (*isNull) - return (Datum) 0; - - return retval; + return result; } Datum -GetAttributeByName(TupleTableSlot *slot, char *attname, bool *isNull) +GetAttributeByName(HeapTupleHeader tuple, const char *attname, bool *isNull) { AttrNumber attrno; - TupleDesc tupdesc; - Datum retval; - int natts; + Datum result; + Oid tupType; + int32 tupTypmod; + TupleDesc tupDesc; + HeapTupleData tmptup; int i; if (attname == NULL) @@ -705,21 +701,23 @@ GetAttributeByName(TupleTableSlot *slot, char *attname, bool *isNull) if (isNull == NULL) elog(ERROR, "a NULL isNull pointer was passed"); - if (TupIsNull(slot)) + if (tuple == NULL) { + /* Kinda bogus but compatible with old behavior... */ *isNull = true; return (Datum) 0; } - tupdesc = slot->ttc_tupleDescriptor; - natts = slot->val->t_data->t_natts; + tupType = HeapTupleHeaderGetTypeId(tuple); + tupTypmod = HeapTupleHeaderGetTypMod(tuple); + tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod); attrno = InvalidAttrNumber; - for (i = 0; i < tupdesc->natts; i++) + for (i = 0; i < tupDesc->natts; i++) { - if (namestrcmp(&(tupdesc->attrs[i]->attname), attname) == 0) + if (namestrcmp(&(tupDesc->attrs[i]->attname), attname) == 0) { - attrno = tupdesc->attrs[i]->attnum; + attrno = tupDesc->attrs[i]->attnum; break; } } @@ -727,14 +725,21 @@ GetAttributeByName(TupleTableSlot *slot, char *attname, bool *isNull) if (attrno == InvalidAttrNumber) elog(ERROR, "attribute \"%s\" does not exist", attname); - retval = heap_getattr(slot->val, + /* + * heap_getattr needs a HeapTuple not a bare HeapTupleHeader. We set + * all the fields in the struct just in case user tries to inspect + * system columns. + */ + tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); + ItemPointerSetInvalid(&(tmptup.t_self)); + tmptup.t_tableOid = InvalidOid; + tmptup.t_data = tuple; + + result = heap_getattr(&tmptup, attrno, - tupdesc, + tupDesc, isNull); - if (*isNull) - return (Datum) 0; - - return retval; + return result; } /* @@ -1133,14 +1138,14 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, Tuplestorestate *tupstore = NULL; TupleDesc tupdesc = NULL; Oid funcrettype; + bool returnsTuple; FunctionCallInfoData fcinfo; ReturnSetInfo rsinfo; + HeapTupleData tmptup; MemoryContext callerContext; MemoryContext oldcontext; - TupleTableSlot *slot; bool direct_function_call; bool first_time = true; - bool returnsTuple = false; /* * Normally the passed expression tree will be a FuncExprState, since @@ -1216,6 +1221,9 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, funcrettype = exprType((Node *) funcexpr->expr); + returnsTuple = (funcrettype == RECORDOID || + get_typtype(funcrettype) == 'c'); + /* * Prepare a resultinfo node for communication. We always do this * even if not expecting a set result, so that we can pass @@ -1282,31 +1290,34 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, break; /* + * Can't do anything useful with NULL rowtype values. Currently + * we raise an error, but another alternative is to just ignore + * the result and "continue" to get another row. + */ + if (returnsTuple && fcinfo.isnull) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("function returning row cannot return null value"))); + + /* * If first time through, build tupdesc and tuplestore for * result */ if (first_time) { oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); - if (funcrettype == RECORDOID || - get_typtype(funcrettype) == 'c') + if (returnsTuple) { /* - * Composite type, so function should have returned a - * TupleTableSlot; use its descriptor + * Use the type info embedded in the rowtype Datum to + * look up the needed tupdesc. Make a copy for the query. */ - slot = (TupleTableSlot *) DatumGetPointer(result); - if (fcinfo.isnull || !slot) - ereport(ERROR, - (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), - errmsg("function returning row cannot return null value"))); - if (!IsA(slot, TupleTableSlot) || - !slot->ttc_tupleDescriptor) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("function returning row did not return a valid tuple slot"))); - tupdesc = CreateTupleDescCopy(slot->ttc_tupleDescriptor); - returnsTuple = true; + HeapTupleHeader td; + + td = DatumGetHeapTupleHeader(result); + tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(td), + HeapTupleHeaderGetTypMod(td)); + tupdesc = CreateTupleDescCopy(tupdesc); } else { @@ -1319,8 +1330,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, "column", funcrettype, -1, - 0, - false); + 0); } tupstore = tuplestore_begin_heap(true, false, work_mem); MemoryContextSwitchTo(oldcontext); @@ -1333,15 +1343,17 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, */ if (returnsTuple) { - slot = (TupleTableSlot *) DatumGetPointer(result); - if (fcinfo.isnull || - !slot || - !IsA(slot, TupleTableSlot) || - TupIsNull(slot)) - ereport(ERROR, - (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), - errmsg("function returning row cannot return null value"))); - tuple = slot->val; + HeapTupleHeader td; + + td = DatumGetHeapTupleHeader(result); + + /* + * tuplestore_puttuple needs a HeapTuple not a bare + * HeapTupleHeader, but it doesn't need all the fields. + */ + tmptup.t_len = HeapTupleHeaderGetDatumLength(td); + tmptup.t_data = td; + tuple = &tmptup; } else { @@ -2415,26 +2427,62 @@ ExecEvalCoerceToDomainValue(ExprState *exprstate, * ---------------------------------------------------------------- */ static Datum -ExecEvalFieldSelect(GenericExprState *fstate, +ExecEvalFieldSelect(FieldSelectState *fstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone) { FieldSelect *fselect = (FieldSelect *) fstate->xprstate.expr; Datum result; - TupleTableSlot *resSlot; + Datum tupDatum; + HeapTupleHeader tuple; + Oid tupType; + int32 tupTypmod; + TupleDesc tupDesc; + HeapTupleData tmptup; - result = ExecEvalExpr(fstate->arg, econtext, isNull, isDone); + tupDatum = ExecEvalExpr(fstate->arg, econtext, isNull, isDone); /* this test covers the isDone exception too: */ if (*isNull) - return result; + return tupDatum; + + tuple = DatumGetHeapTupleHeader(tupDatum); + + tupType = HeapTupleHeaderGetTypeId(tuple); + tupTypmod = HeapTupleHeaderGetTypMod(tuple); + + /* Lookup tupdesc if first time through or if type changes */ + tupDesc = fstate->argdesc; + if (tupDesc == NULL || + tupType != tupDesc->tdtypeid || + tupTypmod != tupDesc->tdtypmod) + { + MemoryContext oldcontext; + + tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + /* Copy the tupdesc into query storage for safety */ + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + tupDesc = CreateTupleDescCopy(tupDesc); + if (fstate->argdesc) + FreeTupleDesc(fstate->argdesc); + fstate->argdesc = tupDesc; + MemoryContextSwitchTo(oldcontext); + } - resSlot = (TupleTableSlot *) DatumGetPointer(result); - Assert(resSlot != NULL && IsA(resSlot, TupleTableSlot)); - result = heap_getattr(resSlot->val, + /* + * heap_getattr needs a HeapTuple not a bare HeapTupleHeader. We set + * all the fields in the struct just in case user tries to inspect + * system columns. + */ + tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); + ItemPointerSetInvalid(&(tmptup.t_self)); + tmptup.t_tableOid = InvalidOid; + tmptup.t_data = tuple; + + result = heap_getattr(&tmptup, fselect->fieldnum, - resSlot->ttc_tupleDescriptor, + tupDesc, isNull); return result; } @@ -2703,11 +2751,12 @@ ExecInitExpr(Expr *node, PlanState *parent) case T_FieldSelect: { FieldSelect *fselect = (FieldSelect *) node; - GenericExprState *gstate = makeNode(GenericExprState); + FieldSelectState *fstate = makeNode(FieldSelectState); - gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFieldSelect; - gstate->arg = ExecInitExpr(fselect->arg, parent); - state = (ExprState *) gstate; + fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFieldSelect; + fstate->arg = ExecInitExpr(fselect->arg, parent); + fstate->argdesc = NULL; + state = (ExprState *) fstate; } break; case T_RelabelType: @@ -3088,8 +3137,6 @@ ExecTargetList(List *targetlist, List *tl; bool isNull; bool haveDoneSets; - static struct tupleDesc NullTupleDesc; /* we assume this inits to - * zeroes */ /* * debugging stuff @@ -3106,13 +3153,8 @@ ExecTargetList(List *targetlist, /* * There used to be some klugy and demonstrably broken code here that * special-cased the situation where targetlist == NIL. Now we just - * fall through and return an empty-but-valid tuple. We do, however, - * have to cope with the possibility that targettype is NULL --- - * heap_formtuple won't like that, so pass a dummy descriptor with - * natts = 0 to deal with it. + * fall through and return an empty-but-valid tuple. */ - if (targettype == NULL) - targettype = &NullTupleDesc; /* * evaluate all the expressions in the target list @@ -3285,8 +3327,8 @@ ExecProject(ProjectionInfo *projInfo, ExprDoneCond *isDone) /* * store the tuple in the projection slot and return the slot. */ - return ExecStoreTuple(newTuple, /* tuple to store */ - slot, /* slot to store in */ - InvalidBuffer, /* tuple has no buffer */ + return ExecStoreTuple(newTuple, /* tuple to store */ + slot, /* slot to store in */ + InvalidBuffer, /* tuple has no buffer */ true); } diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c index 0bb59d26b11..faf910b736f 100644 --- a/src/backend/executor/execTuples.c +++ b/src/backend/executor/execTuples.c @@ -15,7 +15,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execTuples.c,v 1.75 2004/01/07 18:56:26 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execTuples.c,v 1.76 2004/04/01 21:28:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -109,8 +109,11 @@ #include "funcapi.h" #include "access/heapam.h" +#include "catalog/pg_type.h" #include "executor/executor.h" #include "utils/lsyscache.h" +#include "utils/typcache.h" + static TupleDesc ExecTypeFromTLInternal(List *targetList, bool hasoid, bool skipjunk); @@ -144,16 +147,11 @@ ExecCreateTupleTable(int initialSize) /* initial number of slots in /* * Now allocate our new table along with space for the pointers to the - * tuples. + * tuples. Zero out the slots. */ newtable = (TupleTable) palloc(sizeof(TupleTableData)); - array = (TupleTableSlot *) palloc(initialSize * sizeof(TupleTableSlot)); - - /* - * clean out the slots we just allocated - */ - MemSet(array, 0, initialSize * sizeof(TupleTableSlot)); + array = (TupleTableSlot *) palloc0(initialSize * sizeof(TupleTableSlot)); /* * initialize the new table and return it to the caller. @@ -514,6 +512,10 @@ TupleTableSlot * ExecInitNullTupleSlot(EState *estate, TupleDesc tupType) { TupleTableSlot *slot = ExecInitExtraTupleSlot(estate); + struct tupleDesc nullTupleDesc; + HeapTuple nullTuple; + Datum values[1]; + char nulls[1]; /* * Since heap_getattr() will treat attributes beyond a tuple's t_natts @@ -521,15 +523,12 @@ ExecInitNullTupleSlot(EState *estate, TupleDesc tupType) * of zero length. However, the slot descriptor must match the real * tupType. */ - HeapTuple nullTuple; - Datum values[1]; - char nulls[1]; - static struct tupleDesc NullTupleDesc; /* we assume this inits to - * zeroes */ + nullTupleDesc = *tupType; + nullTupleDesc.natts = 0; - ExecSetSlotDescriptor(slot, tupType, false); + nullTuple = heap_formtuple(&nullTupleDesc, values, nulls); - nullTuple = heap_formtuple(&NullTupleDesc, values, nulls); + ExecSetSlotDescriptor(slot, tupType, false); return ExecStoreTuple(nullTuple, slot, InvalidBuffer, true); } @@ -590,21 +589,45 @@ ExecTypeFromTLInternal(List *targetList, bool hasoid, bool skipjunk) resdom->resname, resdom->restype, resdom->restypmod, - 0, - false); + 0); } return typeInfo; } /* + * BlessTupleDesc - make a completed tuple descriptor useful for SRFs + * + * Rowtype Datums returned by a function must contain valid type information. + * This happens "for free" if the tupdesc came from a relcache entry, but + * not if we have manufactured a tupdesc for a transient RECORD datatype. + * In that case we have to notify typcache.c of the existence of the type. + */ +TupleDesc +BlessTupleDesc(TupleDesc tupdesc) +{ + if (tupdesc->tdtypeid == RECORDOID && + tupdesc->tdtypmod < 0) + assign_record_type_typmod(tupdesc); + + return tupdesc; /* just for notational convenience */ +} + +/* * TupleDescGetSlot - Initialize a slot based on the supplied tupledesc + * + * Note: this is obsolete; it is sufficient to call BlessTupleDesc on + * the tupdesc. We keep it around just for backwards compatibility with + * existing user-written SRFs. */ TupleTableSlot * TupleDescGetSlot(TupleDesc tupdesc) { TupleTableSlot *slot; + /* The useful work is here */ + BlessTupleDesc(tupdesc); + /* Make a standalone slot */ slot = MakeTupleTableSlot(); @@ -634,6 +657,9 @@ TupleDescGetAttInMetadata(TupleDesc tupdesc) attinmeta = (AttInMetadata *) palloc(sizeof(AttInMetadata)); + /* "Bless" the tupledesc so that we can make rowtype datums with it */ + attinmeta->tupdesc = BlessTupleDesc(tupdesc); + /* * Gather info needed later to call the "in" function for each * attribute @@ -653,7 +679,6 @@ TupleDescGetAttInMetadata(TupleDesc tupdesc) atttypmods[i] = tupdesc->attrs[i]->atttypmod; } } - attinmeta->tupdesc = tupdesc; attinmeta->attinfuncs = attinfuncinfo; attinmeta->attelems = attelems; attinmeta->atttypmods = atttypmods; diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 8dec6131fb4..aa7652e07ef 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.78 2004/03/21 22:29:11 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.79 2004/04/01 21:28:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -24,8 +24,10 @@ #include "tcop/tcopprot.h" #include "tcop/utility.h" #include "utils/builtins.h" +#include "utils/datum.h" #include "utils/lsyscache.h" #include "utils/syscache.h" +#include "utils/typcache.h" /* @@ -61,10 +63,6 @@ typedef struct bool returnsTuple; /* true if return type is a tuple */ bool shutdown_reg; /* true if registered shutdown callback */ - TupleTableSlot *funcSlot; /* if one result we need to copy it before - * we end execution of the function and - * free stuff */ - ParamListInfo paramLI; /* Param list representing current args */ /* head of linked list of execution_state records */ @@ -196,34 +194,9 @@ init_sql_fcache(FmgrInfo *finfo) * get the type length and by-value flag from the type tuple */ fcache->typlen = typeStruct->typlen; - - if (typeStruct->typtype != 'c' && rettype != RECORDOID) - { - /* The return type is not a composite type, so just use byval */ - fcache->typbyval = typeStruct->typbyval; - fcache->returnsTuple = false; - } - else - { - /* - * This is a hack. We assume here that any function returning a - * tuple returns it by reference. This needs to be fixed, since - * actually the mechanism isn't quite like return-by-reference. - */ - fcache->typbyval = false; - fcache->returnsTuple = true; - } - - /* - * If we are returning exactly one result then we have to copy tuples - * and by reference results because we have to end the execution - * before we return the results. When you do this everything - * allocated by the executor (i.e. slots and tuples) is freed. - */ - if (!finfo->fn_retset && !fcache->typbyval) - fcache->funcSlot = MakeTupleTableSlot(); - else - fcache->funcSlot = NULL; + fcache->typbyval = typeStruct->typbyval; + fcache->returnsTuple = (typeStruct->typtype == 'c' || + rettype == RECORDOID); /* * Parse and plan the queries. We need the argument type info to pass @@ -366,39 +339,6 @@ postquel_sub_params(SQLFunctionCachePtr fcache, fcache->paramLI = paramLI; } -static TupleTableSlot * -copy_function_result(SQLFunctionCachePtr fcache, - TupleTableSlot *resultSlot) -{ - TupleTableSlot *funcSlot; - TupleDesc resultTd; - HeapTuple resultTuple; - HeapTuple newTuple; - - Assert(!TupIsNull(resultSlot)); - resultTuple = resultSlot->val; - - funcSlot = fcache->funcSlot; - - if (funcSlot == NULL) - return resultSlot; /* no need to copy result */ - - /* - * If first time through, we have to initialize the funcSlot's tuple - * descriptor. - */ - if (funcSlot->ttc_tupleDescriptor == NULL) - { - resultTd = CreateTupleDescCopy(resultSlot->ttc_tupleDescriptor); - ExecSetSlotDescriptor(funcSlot, resultTd, true); - ExecSetSlotDescriptorIsNew(funcSlot, true); - } - - newTuple = heap_copytuple(resultTuple); - - return ExecStoreTuple(newTuple, funcSlot, InvalidBuffer, true); -} - static Datum postquel_execute(execution_state *es, FunctionCallInfo fcinfo, @@ -429,43 +369,51 @@ postquel_execute(execution_state *es, if (LAST_POSTQUEL_COMMAND(es)) { - TupleTableSlot *resSlot; - /* - * Copy the result. copy_function_result is smart enough to do - * nothing when no action is called for. This helps reduce the - * logic and code redundancy here. + * Set up to return the function value. */ - resSlot = copy_function_result(fcache, slot); + HeapTuple tup = slot->val; + TupleDesc tupDesc = slot->ttc_tupleDescriptor; - /* - * If we are supposed to return a tuple, we return the tuple slot - * pointer converted to Datum. If we are supposed to return a - * simple value, then project out the first attribute of the - * result tuple (ie, take the first result column of the final - * SELECT). - */ if (fcache->returnsTuple) { /* + * We are returning the whole tuple, so copy it into current + * execution context and make sure it is a valid Datum. + * * XXX do we need to remove junk attrs from the result tuple? * Probably OK to leave them, as long as they are at the end. */ - value = PointerGetDatum(resSlot); + HeapTupleHeader dtup; + + dtup = (HeapTupleHeader) palloc(tup->t_len); + memcpy((char *) dtup, (char *) tup->t_data, tup->t_len); + + /* + * For RECORD results, make sure a typmod has been assigned. + */ + if (tupDesc->tdtypeid == RECORDOID && + tupDesc->tdtypmod < 0) + assign_record_type_typmod(tupDesc); + + HeapTupleHeaderSetDatumLength(dtup, tup->t_len); + HeapTupleHeaderSetTypeId(dtup, tupDesc->tdtypeid); + HeapTupleHeaderSetTypMod(dtup, tupDesc->tdtypmod); + + value = PointerGetDatum(dtup); fcinfo->isnull = false; } else { - value = heap_getattr(resSlot->val, - 1, - resSlot->ttc_tupleDescriptor, - &(fcinfo->isnull)); - /* - * Note: if result type is pass-by-reference then we are - * returning a pointer into the tuple copied by - * copy_function_result. This is OK. + * Returning a scalar, which we have to extract from the + * first column of the SELECT result, and then copy into current + * execution context if needed. */ + value = heap_getattr(tup, 1, tupDesc, &(fcinfo->isnull)); + + if (!fcinfo->isnull) + value = datumCopy(value, fcache->typbyval, fcache->typlen); } /* diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c index f3fa17c8881..7847b24ffe2 100644 --- a/src/backend/executor/nodeFunctionscan.c +++ b/src/backend/executor/nodeFunctionscan.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/nodeFunctionscan.c,v 1.23 2003/11/29 19:51:48 pgsql Exp $ + * $PostgreSQL: pgsql/src/backend/executor/nodeFunctionscan.c,v 1.24 2004/04/01 21:28:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -32,6 +32,7 @@ #include "parser/parse_expr.h" #include "parser/parse_type.h" #include "utils/lsyscache.h" +#include "utils/typcache.h" static TupleTableSlot *FunctionNext(FunctionScanState *node); @@ -194,25 +195,12 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate) if (functyptype == 'c') { - /* - * Composite data type, i.e. a table's row type - */ - Oid funcrelid; - Relation rel; - - funcrelid = typeidTypeRelid(funcrettype); - if (!OidIsValid(funcrelid)) - elog(ERROR, "invalid typrelid for complex type %u", - funcrettype); - rel = relation_open(funcrelid, AccessShareLock); - tupdesc = CreateTupleDescCopy(RelationGetDescr(rel)); - relation_close(rel, AccessShareLock); + /* Composite data type, e.g. a table's row type */ + tupdesc = CreateTupleDescCopy(lookup_rowtype_tupdesc(funcrettype, -1)); } else if (functyptype == 'b' || functyptype == 'd') { - /* - * Must be a base data type, i.e. scalar - */ + /* Must be a base data type, i.e. scalar */ char *attname = strVal(lfirst(rte->eref->colnames)); tupdesc = CreateTemplateTupleDesc(1, false); @@ -221,14 +209,11 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate) attname, funcrettype, -1, - 0, - false); + 0); } - else if (functyptype == 'p' && funcrettype == RECORDOID) + else if (funcrettype == RECORDOID) { - /* - * Must be a pseudo type, i.e. record - */ + /* Must be a pseudo type, i.e. record */ tupdesc = BuildDescForRelation(rte->coldeflist); } else @@ -237,6 +222,14 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate) elog(ERROR, "function in FROM has unsupported return type"); } + /* + * For RECORD results, make sure a typmod has been assigned. (The + * function should do this for itself, but let's cover things in case + * it doesn't.) + */ + if (tupdesc->tdtypeid == RECORDOID && tupdesc->tdtypmod < 0) + assign_record_type_typmod(tupdesc); + scanstate->tupdesc = tupdesc; ExecSetSlotDescriptor(scanstate->ss.ss_ScanTupleSlot, tupdesc, false); diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index 1fb60fff02d..bcf7f8d52b6 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.112 2004/03/21 22:29:11 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.113 2004/04/01 21:28:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -19,6 +19,7 @@ #include "executor/spi_priv.h" #include "tcop/tcopprot.h" #include "utils/lsyscache.h" +#include "utils/typcache.h" uint32 SPI_processed = 0; @@ -380,40 +381,11 @@ SPI_copytuple(HeapTuple tuple) return ctuple; } -TupleDesc -SPI_copytupledesc(TupleDesc tupdesc) +HeapTupleHeader +SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc) { MemoryContext oldcxt = NULL; - TupleDesc ctupdesc; - - if (tupdesc == NULL) - { - SPI_result = SPI_ERROR_ARGUMENT; - return NULL; - } - - if (_SPI_curid + 1 == _SPI_connected) /* connected */ - { - if (_SPI_current != &(_SPI_stack[_SPI_curid + 1])) - elog(ERROR, "SPI stack corrupted"); - oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt); - } - - ctupdesc = CreateTupleDescCopy(tupdesc); - - if (oldcxt) - MemoryContextSwitchTo(oldcxt); - - return ctupdesc; -} - -TupleTableSlot * -SPI_copytupleintoslot(HeapTuple tuple, TupleDesc tupdesc) -{ - MemoryContext oldcxt = NULL; - TupleTableSlot *cslot; - HeapTuple ctuple; - TupleDesc ctupdesc; + HeapTupleHeader dtup; if (tuple == NULL || tupdesc == NULL) { @@ -421,6 +393,11 @@ SPI_copytupleintoslot(HeapTuple tuple, TupleDesc tupdesc) return NULL; } + /* For RECORD results, make sure a typmod has been assigned */ + if (tupdesc->tdtypeid == RECORDOID && + tupdesc->tdtypmod < 0) + assign_record_type_typmod(tupdesc); + if (_SPI_curid + 1 == _SPI_connected) /* connected */ { if (_SPI_current != &(_SPI_stack[_SPI_curid + 1])) @@ -428,17 +405,17 @@ SPI_copytupleintoslot(HeapTuple tuple, TupleDesc tupdesc) oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt); } - ctuple = heap_copytuple(tuple); - ctupdesc = CreateTupleDescCopy(tupdesc); + dtup = (HeapTupleHeader) palloc(tuple->t_len); + memcpy((char *) dtup, (char *) tuple->t_data, tuple->t_len); - cslot = MakeTupleTableSlot(); - ExecSetSlotDescriptor(cslot, ctupdesc, true); - cslot = ExecStoreTuple(ctuple, cslot, InvalidBuffer, true); + HeapTupleHeaderSetDatumLength(dtup, tuple->t_len); + HeapTupleHeaderSetTypeId(dtup, tupdesc->tdtypeid); + HeapTupleHeaderSetTypMod(dtup, tupdesc->tdtypmod); if (oldcxt) MemoryContextSwitchTo(oldcxt); - return cslot; + return dtup; } HeapTuple |