X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fbackend%2Fexecutor%2FexecQual.c;h=234441c0f6c80cce5e2fccf430062b9647197d58;hb=c7ff7663e47fc4e295700101912f2b7dd53c1f4b;hp=8a963f88cb604cd9274b17f2b7644834d195951a;hpb=d51368dbbdde9cc11299426ec199c142f0a2e062;p=postgresql diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 8a963f88cb..234441c0f6 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -3,89 +3,128 @@ * execQual.c * Routines to evaluate qualification and targetlist expressions * - * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.147 2003/10/11 16:30:54 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.214 2007/02/27 01:11:25 tgl Exp $ * *------------------------------------------------------------------------- */ /* * INTERFACE ROUTINES - * ExecEvalExpr - evaluate an expression and return a datum + * ExecEvalExpr - (now a macro) evaluate an expression, return a datum * ExecEvalExprSwitchContext - same, but switch into eval memory context * ExecQual - return true/false if qualification is satisfied * ExecProject - form a new tuple by projecting the given tuple * * NOTES - * ExecEvalExpr() and ExecEvalVar() are hotspots. making these faster - * will speed up the entire system. Unfortunately they are currently - * implemented recursively. Eliminating the recursion is bound to - * improve the speed of the executor. + * The more heavily used ExecEvalExpr routines, such as ExecEvalVar(), + * are hotspots. Making these faster will speed up the entire system. * * ExecProject() is used to make tuple projections. Rather then * trying to speed it up, the execution plan should be pre-processed * to facilitate attribute sharing between nodes wherever possible, * instead of doing needless copying. -cim 5/31/91 * + * During expression evaluation, we check_stack_depth only in + * ExecMakeFunctionResult rather than at every single node. This + * is a compromise that trades off precision of the stack limit setting + * to gain speed. */ #include "postgres.h" #include "access/heapam.h" +#include "access/nbtree.h" #include "catalog/pg_type.h" #include "commands/typecmds.h" #include "executor/execdebug.h" -#include "executor/functions.h" #include "executor/nodeSubplan.h" +#include "funcapi.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "optimizer/planmain.h" #include "parser/parse_expr.h" #include "utils/acl.h" -#include "utils/array.h" #include "utils/builtins.h" #include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/typcache.h" +#include "utils/xml.h" /* static function decls */ -static Datum ExecEvalAggref(AggrefExprState *aggref, - ExprContext *econtext, - bool *isNull); static Datum ExecEvalArrayRef(ArrayRefExprState *astate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); -static Datum ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull); -static Datum ExecEvalParam(Param *expression, ExprContext *econtext, - bool *isNull); +static Datum ExecEvalAggref(AggrefExprState *aggref, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalVar(ExprState *exprstate, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalConst(ExprState *exprstate, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalParam(ExprState *exprstate, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); +static void ShutdownFuncExpr(Datum arg); +static TupleDesc get_cached_rowtype(Oid type_id, int32 typmod, + TupleDesc *cache_field, ExprContext *econtext); +static void ShutdownTupleDescRef(Datum arg); +static ExprDoneCond ExecEvalFuncArgs(FunctionCallInfo fcinfo, + List *argList, ExprContext *econtext); +static Datum ExecMakeFunctionResultNoSets(FuncExprState *fcache, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalFunc(FuncExprState *fcache, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalOper(FuncExprState *fcache, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalDistinct(FuncExprState *fcache, ExprContext *econtext, - bool *isNull); + bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate, - ExprContext *econtext, bool *isNull); -static ExprDoneCond ExecEvalFuncArgs(FunctionCallInfo fcinfo, - List *argList, ExprContext *econtext); + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalNot(BoolExprState *notclause, ExprContext *econtext, - bool *isNull); + bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalOr(BoolExprState *orExpr, ExprContext *econtext, - bool *isNull); + bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalAnd(BoolExprState *andExpr, ExprContext *econtext, - bool *isNull); + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalCaseTestExpr(ExprState *exprstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalArray(ArrayExprState *astate, ExprContext *econtext, - bool *isNull); + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalRow(RowExprState *rstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalRowCompare(RowCompareExprState *rstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalCoalesce(CoalesceExprState *coalesceExpr, ExprContext *econtext, - bool *isNull); -static Datum ExecEvalNullIf(FuncExprState *nullIfExpr, ExprContext *econtext, - bool *isNull); -static Datum ExecEvalNullTest(GenericExprState *nstate, + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalMinMax(MinMaxExprState *minmaxExpr, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalNullIf(FuncExprState *nullIfExpr, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalNullTest(NullTestState *nstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalBooleanTest(GenericExprState *bstate, @@ -94,11 +133,77 @@ static Datum ExecEvalBooleanTest(GenericExprState *bstate, static Datum ExecEvalCoerceToDomain(CoerceToDomainState *cstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); -static Datum ExecEvalCoerceToDomainValue(CoerceToDomainValue *conVal, - ExprContext *econtext, bool *isNull); -static Datum ExecEvalFieldSelect(GenericExprState *fstate, +static Datum ExecEvalCoerceToDomainValue(ExprState *exprstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalFieldSelect(FieldSelectState *fstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalFieldStore(FieldStoreState *fstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalRelabelType(GenericExprState *exprstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); + + +/* ---------------------------------------------------------------- + * ExecEvalExpr routines + * + * Recursively evaluate a targetlist or qualification expression. + * + * Each of the following routines having the signature + * Datum ExecEvalFoo(ExprState *expression, + * ExprContext *econtext, + * bool *isNull, + * ExprDoneCond *isDone); + * is responsible for evaluating one type or subtype of ExprState node. + * They are normally called via the ExecEvalExpr macro, which makes use of + * the function pointer set up when the ExprState node was built by + * ExecInitExpr. (In some cases, we change this pointer later to avoid + * re-executing one-time overhead.) + * + * Note: for notational simplicity we declare these functions as taking the + * specific type of ExprState that they work on. This requires casting when + * assigning the function pointer in ExecInitExpr. Be careful that the + * function signature is declared correctly, because the cast suppresses + * automatic checking! + * + * + * All these functions share this calling convention: + * + * Inputs: + * expression: the expression state tree to evaluate + * econtext: evaluation context information + * + * Outputs: + * return value: Datum value of result + * *isNull: set to TRUE if result is NULL (actual return value is + * meaningless if so); set to FALSE if non-null result + * *isDone: set to indicator of set-result status + * + * A caller that can only accept a singleton (non-set) result should pass + * NULL for isDone; if the expression computes a set result then an error + * will be reported via ereport. If the caller does pass an isDone pointer + * then *isDone is set to one of these three states: + * ExprSingleResult singleton result (not a set) + * ExprMultipleResult return value is one element of a set + * ExprEndResult there are no more elements in the set + * When ExprMultipleResult is returned, the caller should invoke + * ExecEvalExpr() repeatedly until ExprEndResult is returned. ExprEndResult + * is returned after the last real set element. For convenience isNull will + * always be set TRUE when ExprEndResult is returned, but this should not be + * taken as indicating a NULL element of the set. Note that these return + * conventions allow us to distinguish among a singleton NULL, a NULL element + * of a set, and an empty set. + * + * The caller should already have switched into the temporary memory + * context econtext->ecxt_per_tuple_memory. The convenience entry point + * ExecEvalExprSwitchContext() is provided for callers who don't prefer to + * do the switch in an outer loop. We do not do the switch in these routines + * because it'd be a waste of cycles during nested expression evaluation. + * ---------------------------------------------------------------- + */ /*---------- @@ -108,16 +213,8 @@ static Datum ExecEvalFieldSelect(GenericExprState *fstate, * if it's a simple reference, or the modified array value if it's * an array assignment (i.e., array element or slice insertion). * - * NOTE: if we get a NULL result from a subexpression, we return NULL when - * it's an array reference, or the unmodified source array when it's an - * array assignment. This may seem peculiar, but if we return NULL (as was - * done in versions up through 7.0) then an assignment like - * UPDATE table SET arrayfield[4] = NULL - * will result in setting the whole array to NULL, which is certainly not - * very desirable. By returning the source array we make the assignment - * into a no-op, instead. (Eventually we need to redesign arrays so that - * individual elements can be NULL, but for now, let's try to protect users - * from shooting themselves in the foot.) + * NOTE: if we get a NULL result from a subscript expression, we return NULL + * when it's an array reference, or raise an error when it's an assignment. * * NOTE: we deliberately refrain from applying DatumGetArrayTypeP() here, * even though that might seem natural, because this code needs to support @@ -136,89 +233,83 @@ ExecEvalArrayRef(ArrayRefExprState *astate, ArrayType *array_source; ArrayType *resultArray; bool isAssignment = (arrayRef->refassgnexpr != NULL); - List *elt; + bool eisnull; + ListCell *l; int i = 0, j = 0; IntArray upper, lower; int *lIndex; - if (arrayRef->refexpr != NULL) - { - array_source = (ArrayType *) - DatumGetPointer(ExecEvalExpr(astate->refexpr, - econtext, - isNull, - isDone)); + array_source = (ArrayType *) + DatumGetPointer(ExecEvalExpr(astate->refexpr, + econtext, + isNull, + isDone)); - /* - * If refexpr yields NULL, result is always NULL, for now anyway. - * (This means you cannot assign to an element or slice of an - * array that's NULL; it'll just stay NULL.) - */ - if (*isNull) - return (Datum) NULL; - } - else + /* + * If refexpr yields NULL, and it's a fetch, then result is NULL. In the + * assignment case, we'll cons up something below. + */ + if (*isNull) { - /* - * Empty refexpr indicates we are doing an INSERT into an array - * column. For now, we just take the refassgnexpr (which the - * parser will have ensured is an array value) and return it - * as-is, ignoring any subscripts that may have been supplied in - * the INSERT column list. This is a kluge, but it's not real - * clear what the semantics ought to be... - */ - array_source = NULL; + if (isDone && *isDone == ExprEndResult) + return (Datum) NULL; /* end of set result */ + if (!isAssignment) + return (Datum) NULL; } - foreach(elt, astate->refupperindexpr) + foreach(l, astate->refupperindexpr) { + ExprState *eltstate = (ExprState *) lfirst(l); + if (i >= MAXDIM) ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", i, MAXDIM))); - upper.indx[i++] = DatumGetInt32(ExecEvalExpr((ExprState *) lfirst(elt), + upper.indx[i++] = DatumGetInt32(ExecEvalExpr(eltstate, econtext, - isNull, + &eisnull, NULL)); - /* If any index expr yields NULL, result is NULL or source array */ - if (*isNull) + /* If any index expr yields NULL, result is NULL or error */ + if (eisnull) { - if (!isAssignment || array_source == NULL) - return (Datum) NULL; - *isNull = false; - return PointerGetDatum(array_source); + if (isAssignment) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("array subscript in assignment must not be null"))); + *isNull = true; + return (Datum) NULL; } } if (astate->reflowerindexpr != NIL) { - foreach(elt, astate->reflowerindexpr) + foreach(l, astate->reflowerindexpr) { + ExprState *eltstate = (ExprState *) lfirst(l); + if (j >= MAXDIM) ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", i, MAXDIM))); - lower.indx[j++] = DatumGetInt32(ExecEvalExpr((ExprState *) lfirst(elt), + lower.indx[j++] = DatumGetInt32(ExecEvalExpr(eltstate, econtext, - isNull, + &eisnull, NULL)); - - /* - * If any index expr yields NULL, result is NULL or source - * array - */ - if (*isNull) + /* If any index expr yields NULL, result is NULL or error */ + if (eisnull) { - if (!isAssignment || array_source == NULL) - return (Datum) NULL; - *isNull = false; - return PointerGetDatum(array_source); + if (isAssignment) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("array subscript in assignment must not be null"))); + *isNull = true; + return (Datum) NULL; } } /* this can't happen unless parser messed up */ @@ -231,44 +322,62 @@ ExecEvalArrayRef(ArrayRefExprState *astate, if (isAssignment) { - Datum sourceData = ExecEvalExpr(astate->refassgnexpr, - econtext, - isNull, - NULL); + Datum sourceData; + + /* + * Evaluate the value to be assigned into the array. + * + * XXX At some point we'll need to look into making the old value of + * the array element available via CaseTestExpr, as is done by + * ExecEvalFieldStore. This is not needed now but will be needed to + * support arrays of composite types; in an assignment to a field of + * an array member, the parser would generate a FieldStore that + * expects to fetch its input tuple via CaseTestExpr. + */ + sourceData = ExecEvalExpr(astate->refassgnexpr, + econtext, + &eisnull, + NULL); + + /* + * For an assignment to a fixed-length array type, both the original + * array and the value to be assigned into it must be non-NULL, else + * we punt and return the original array. + */ + if (astate->refattrlength > 0) /* fixed-length array? */ + if (eisnull || *isNull) + return PointerGetDatum(array_source); /* - * For now, can't cope with inserting NULL into an array, so make - * it a no-op per discussion above... + * For assignment to varlena arrays, we handle a NULL original array + * by substituting an empty (zero-dimensional) array; insertion of the + * new element will result in a singleton array value. It does not + * matter whether the new element is NULL. */ if (*isNull) { - if (array_source == NULL) - return (Datum) NULL; + array_source = construct_empty_array(arrayRef->refelemtype); *isNull = false; - return PointerGetDatum(array_source); } - if (array_source == NULL) - return sourceData; /* XXX do something else? */ - if (lIndex == NULL) resultArray = array_set(array_source, i, upper.indx, sourceData, + eisnull, astate->refattrlength, astate->refelemlength, astate->refelembyval, - astate->refelemalign, - isNull); + astate->refelemalign); else resultArray = array_set_slice(array_source, i, upper.indx, lower.indx, - (ArrayType *) DatumGetPointer(sourceData), + (ArrayType *) DatumGetPointer(sourceData), + eisnull, astate->refattrlength, astate->refelemlength, astate->refelembyval, - astate->refelemalign, - isNull); + astate->refelemalign); return PointerGetDatum(resultArray); } @@ -286,8 +395,7 @@ ExecEvalArrayRef(ArrayRefExprState *astate, astate->refattrlength, astate->refelemlength, astate->refelembyval, - astate->refelemalign, - isNull); + astate->refelemalign); return PointerGetDatum(resultArray); } } @@ -301,8 +409,12 @@ ExecEvalArrayRef(ArrayRefExprState *astate, * ---------------------------------------------------------------- */ static Datum -ExecEvalAggref(AggrefExprState *aggref, ExprContext *econtext, bool *isNull) +ExecEvalAggref(AggrefExprState *aggref, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) { + if (isDone) + *isDone = ExprSingleResult; + if (econtext->ecxt_aggvalues == NULL) /* safety check */ elog(ERROR, "no aggregates in this expression context"); @@ -315,27 +427,43 @@ ExecEvalAggref(AggrefExprState *aggref, ExprContext *econtext, bool *isNull) * * Returns a Datum whose value is the value of a range * variable with respect to given expression context. - * ---------------------------------------------------------------- */ + * + * Note: ExecEvalVar is executed only the first time through in a given plan; + * it changes the ExprState's function pointer to pass control directly to + * ExecEvalScalarVar or ExecEvalWholeRowVar after making one-time checks. + * ---------------------------------------------------------------- + */ static Datum -ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull) +ExecEvalVar(ExprState *exprstate, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) { - Datum result; + Var *variable = (Var *) exprstate->expr; TupleTableSlot *slot; AttrNumber attnum; - HeapTuple heapTuple; - TupleDesc tuple_type; + + if (isDone) + *isDone = ExprSingleResult; /* - * get the slot we want + * Get the input 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 @@ -344,87 +472,245 @@ ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull) break; } - /* - * extract tuple information from the slot - */ - 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). - */ - if (attnum > 0) + if (attnum != InvalidAttrNumber) { /* - * This assert checks that the attnum is valid. - */ - Assert(attnum <= tuple_type->natts && - tuple_type->attrs[attnum - 1] != NULL); - - /* - * If the attribute's column has been dropped, we force a NULL result. - * This case should not happen in normal use, but it could happen if - * we are executing a plan cached before the column was dropped. + * Scalar variable case. + * + * If it's a user attribute, check validity (bogus system attnums will + * be caught inside slot_getattr). What we have to check for here + * is the possibility of an attribute having been changed in type + * since the plan tree was created. Ideally the plan would get + * invalidated and not re-used, but until that day arrives, we need + * defenses. Fortunately it's sufficient to check once on the first + * time through. + * + * Note: we allow a reference to a dropped attribute. slot_getattr + * will force a NULL result in such cases. + * + * Note: ideally we'd check typmod as well as typid, but that seems + * impractical at the moment: in many cases the tupdesc will have + * been generated by ExecTypeFromTL(), and that can't guarantee to + * generate an accurate typmod in all cases, because some expression + * node types don't carry typmod. */ - if (tuple_type->attrs[attnum - 1]->attisdropped) + if (attnum > 0) { - *isNull = true; - return (Datum) 0; + TupleDesc slot_tupdesc = slot->tts_tupleDescriptor; + Form_pg_attribute attr; + + if (attnum > slot_tupdesc->natts) /* should never happen */ + elog(ERROR, "attribute number %d exceeds number of columns %d", + attnum, slot_tupdesc->natts); + + attr = slot_tupdesc->attrs[attnum - 1]; + + /* can't check type if dropped, since atttypid is probably 0 */ + if (!attr->attisdropped) + { + if (variable->vartype != attr->atttypid) + ereport(ERROR, + (errmsg("attribute %d has wrong type", attnum), + errdetail("Table has type %s, but query expects %s.", + format_type_be(attr->atttypid), + format_type_be(variable->vartype)))); + } } + /* Skip the checking on future executions of node */ + exprstate->evalfunc = ExecEvalScalarVar; + + /* Fetch the value from the slot */ + return slot_getattr(slot, attnum, isNull); + } + else + { /* - * This assert checks that the datatype the plan expects to get (as - * told by our "variable" argument) is in fact the datatype of the - * attribute being fetched (as seen in the current context, identified - * by our "econtext" argument). Otherwise crashes are likely. + * Whole-row variable. + * + * If it's a RECORD Var, we'll use the slot's type ID info. It's + * likely that the slot's type is also RECORD; if so, make sure it's + * been "blessed", so that the Datum can be interpreted later. * - * Note that we can't check dropped columns, since their atttypid - * has been zeroed. + * If the Var identifies a named composite type, we must check that + * the actual tuple type is compatible with it. */ - Assert(variable->vartype == tuple_type->attrs[attnum - 1]->atttypid); + TupleDesc slot_tupdesc = slot->tts_tupleDescriptor; + + if (variable->vartype == RECORDOID) + { + if (slot_tupdesc->tdtypeid == RECORDOID && + slot_tupdesc->tdtypmod < 0) + assign_record_type_typmod(slot_tupdesc); + } + else + { + TupleDesc var_tupdesc; + int i; + + /* + * We really only care about number of attributes and data type. + * Also, we can ignore type mismatch on columns that are dropped + * in the destination type, so long as the physical storage + * matches. This is helpful in some cases involving out-of-date + * cached plans. + */ + var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1); + + if (var_tupdesc->natts != slot_tupdesc->natts) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("table row type and query-specified row type do not match"), + errdetail("Table row contains %d attributes, but query expects %d.", + slot_tupdesc->natts, var_tupdesc->natts))); + + for (i = 0; i < var_tupdesc->natts; i++) + { + Form_pg_attribute vattr = var_tupdesc->attrs[i]; + Form_pg_attribute sattr = slot_tupdesc->attrs[i]; + + if (vattr->atttypid == sattr->atttypid) + continue; /* no worries */ + if (!vattr->attisdropped) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("table row type and query-specified row type do not match"), + errdetail("Table has type %s at ordinal position %d, but query expects %s.", + format_type_be(sattr->atttypid), + i + 1, + format_type_be(vattr->atttypid)))); + + if (vattr->attlen != sattr->attlen || + vattr->attalign != sattr->attalign) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("table row type and query-specified row type do not match"), + errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.", + i + 1))); + } + + ReleaseTupleDesc(var_tupdesc); + } + + /* Skip the checking on future executions of node */ + exprstate->evalfunc = ExecEvalWholeRowVar; + + /* Fetch the value */ + return ExecEvalWholeRowVar(exprstate, econtext, isNull, isDone); + } +} + +/* ---------------------------------------------------------------- + * ExecEvalScalarVar + * + * Returns a Datum for a scalar variable. + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + Var *variable = (Var *) exprstate->expr; + TupleTableSlot *slot; + AttrNumber attnum; + + if (isDone) + *isDone = ExprSingleResult; + + /* Get the input slot and attribute number we want */ + switch (variable->varno) + { + case INNER: /* get the tuple from the inner node */ + slot = econtext->ecxt_innertuple; + break; + + case OUTER: /* get the tuple from the outer node */ + slot = econtext->ecxt_outertuple; + break; + + default: /* get the tuple from the relation being + * scanned */ + slot = econtext->ecxt_scantuple; + break; } + attnum = variable->varattno; + + /* Fetch the value from the slot */ + return slot_getattr(slot, attnum, isNull); +} + +/* ---------------------------------------------------------------- + * ExecEvalWholeRowVar + * + * Returns a Datum for a whole-row variable. + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + Var *variable = (Var *) exprstate->expr; + TupleTableSlot *slot = econtext->ecxt_scantuple; + HeapTuple tuple; + TupleDesc tupleDesc; + HeapTupleHeader dtuple; + + if (isDone) + *isDone = ExprSingleResult; + *isNull = false; + + tuple = ExecFetchSlotTuple(slot); + tupleDesc = slot->tts_tupleDescriptor; + /* - * 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. + * We have to make a copy of the tuple so we can safely insert the Datum + * overhead fields, which are not set in on-disk tuples. */ - if (attnum == InvalidAttrNumber) - { - MemoryContext oldContext; - TupleTableSlot *tempSlot; - HeapTuple tup; + dtuple = (HeapTupleHeader) palloc(tuple->t_len); + memcpy((char *) dtuple, (char *) tuple->t_data, tuple->t_len); - 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); - return PointerGetDatum(tempSlot); + HeapTupleHeaderSetDatumLength(dtuple, tuple->t_len); + + /* + * If the Var identifies a named composite type, label the tuple with that + * type; otherwise use what is in the tupleDesc. + */ + if (variable->vartype != RECORDOID) + { + HeapTupleHeaderSetTypeId(dtuple, variable->vartype); + HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod); + } + else + { + HeapTupleHeaderSetTypeId(dtuple, tupleDesc->tdtypeid); + HeapTupleHeaderSetTypMod(dtuple, tupleDesc->tdtypmod); } - result = heap_getattr(heapTuple, /* tuple containing attribute */ - attnum, /* attribute number of desired - * attribute */ - tuple_type, /* tuple descriptor of tuple */ - isNull); /* return: is attribute null? */ + return PointerGetDatum(dtuple); +} - return result; +/* ---------------------------------------------------------------- + * ExecEvalConst + * + * Returns the value of a constant. + * + * Note that for pass-by-ref datatypes, we return a pointer to the + * actual constant node. This is one of the reasons why functions + * must treat their input arguments as read-only. + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalConst(ExprState *exprstate, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + Const *con = (Const *) exprstate->expr; + + if (isDone) + *isDone = ExprSingleResult; + + *isNull = con->constisnull; + return con->constvalue; } /* ---------------------------------------------------------------- @@ -434,25 +720,23 @@ ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull) * something like ($.name) and the expression context contains * the current parameter bindings (name = "sam") (age = 34)... * so our job is to find and return the appropriate datum ("sam"). - * - * Q: if we have a parameter ($.foo) without a binding, i.e. - * there is no (foo = xxx) in the parameter list info, - * is this a fatal error or should this be a "not available" - * (in which case we could return NULL)? -cim 10/13/89 * ---------------------------------------------------------------- */ static Datum -ExecEvalParam(Param *expression, ExprContext *econtext, bool *isNull) +ExecEvalParam(ExprState *exprstate, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) { - int thisParamKind = expression->paramkind; - AttrNumber thisParamId = expression->paramid; + Param *expression = (Param *) exprstate->expr; + int thisParamId = expression->paramid; + + if (isDone) + *isDone = ExprSingleResult; - if (thisParamKind == PARAM_EXEC) + if (expression->paramkind == PARAM_EXEC) { /* - * PARAM_EXEC params (internal executor parameters) are stored in - * the ecxt_param_exec_vals array, and can be accessed by array - * index. + * PARAM_EXEC params (internal executor parameters) are stored in the + * ecxt_param_exec_vals array, and can be accessed by array index. */ ParamExecData *prm; @@ -470,56 +754,27 @@ ExecEvalParam(Param *expression, ExprContext *econtext, bool *isNull) else { /* - * All other parameter types must be sought in - * ecxt_param_list_info. NOTE: The last entry in the param array - * is always an entry with kind == PARAM_INVALID. + * PARAM_EXTERN parameters must be sought in ecxt_param_list_info. */ - ParamListInfo paramList = econtext->ecxt_param_list_info; - char *thisParamName = expression->paramname; - bool matchFound = false; + ParamListInfo paramInfo = econtext->ecxt_param_list_info; - if (paramList != NULL) + Assert(expression->paramkind == PARAM_EXTERN); + if (paramInfo && + thisParamId > 0 && thisParamId <= paramInfo->numParams) { - while (paramList->kind != PARAM_INVALID && !matchFound) - { - if (thisParamKind == paramList->kind) - { - switch (thisParamKind) - { - case PARAM_NAMED: - if (strcmp(paramList->name, thisParamName) == 0) - matchFound = true; - break; - case PARAM_NUM: - if (paramList->id == thisParamId) - matchFound = true; - break; - default: - elog(ERROR, "unrecognized paramkind: %d", - thisParamKind); - } - } - if (!matchFound) - paramList++; - } /* while */ - } /* if */ + ParamExternData *prm = ¶mInfo->params[thisParamId - 1]; - if (!matchFound) - { - if (thisParamKind == PARAM_NAMED) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("no value found for parameter \"%s\"", - thisParamName))); - else - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("no value found for parameter %d", - thisParamId))); + if (OidIsValid(prm->ptype)) + { + Assert(prm->ptype == expression->paramtype); + *isNull = prm->isnull; + return prm->value; + } } - - *isNull = paramList->isnull; - return paramList->value; + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("no value found for parameter %d", thisParamId))); + return (Datum) 0; /* keep compiler quiet */ } } @@ -533,70 +788,95 @@ ExecEvalParam(Param *expression, ExprContext *econtext, bool *isNull) * 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); - if (isNull == (bool *) NULL) + 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; + ReleaseTupleDesc(tupDesc); + + 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) elog(ERROR, "invalid attribute name"); - if (isNull == (bool *) NULL) + 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; } } @@ -604,14 +884,24 @@ 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; + ReleaseTupleDesc(tupDesc); + + return result; } /* @@ -627,18 +917,98 @@ init_fcache(Oid foid, FuncExprState *fcache, MemoryContext fcacheCxt) if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(foid)); - /* Safety check (should never fail, as parser should check sooner) */ - if (length(fcache->args) > FUNC_MAX_ARGS) - elog(ERROR, "too many arguments"); + /* + * Safety check on nargs. Under normal circumstances this should never + * fail, as parser should check sooner. But possibly it might fail if + * server has been compiled with FUNC_MAX_ARGS smaller than some functions + * declared in pg_proc? + */ + if (list_length(fcache->args) > FUNC_MAX_ARGS) + ereport(ERROR, + (errcode(ERRCODE_TOO_MANY_ARGUMENTS), + errmsg("cannot pass more than %d arguments to a function", + FUNC_MAX_ARGS))); /* Set up the primary fmgr lookup information */ fmgr_info_cxt(foid, &(fcache->func), fcacheCxt); /* Initialize additional info */ fcache->setArgsValid = false; + fcache->shutdown_reg = false; fcache->func.fn_expr = (Node *) fcache->xprstate.expr; } +/* + * callback function in case a FuncExpr returning a set needs to be shut down + * before it has been run to completion + */ +static void +ShutdownFuncExpr(Datum arg) +{ + FuncExprState *fcache = (FuncExprState *) DatumGetPointer(arg); + + /* Clear any active set-argument state */ + fcache->setArgsValid = false; + + /* execUtils will deregister the callback... */ + fcache->shutdown_reg = false; +} + +/* + * get_cached_rowtype: utility function to lookup a rowtype tupdesc + * + * type_id, typmod: identity of the rowtype + * cache_field: where to cache the TupleDesc pointer in expression state node + * (field must be initialized to NULL) + * econtext: expression context we are executing in + * + * NOTE: because the shutdown callback will be called during plan rescan, + * must be prepared to re-do this during any node execution; cannot call + * just once during expression initialization + */ +static TupleDesc +get_cached_rowtype(Oid type_id, int32 typmod, + TupleDesc *cache_field, ExprContext *econtext) +{ + TupleDesc tupDesc = *cache_field; + + /* Do lookup if no cached value or if requested type changed */ + if (tupDesc == NULL || + type_id != tupDesc->tdtypeid || + typmod != tupDesc->tdtypmod) + { + tupDesc = lookup_rowtype_tupdesc(type_id, typmod); + + if (*cache_field) + { + /* Release old tupdesc; but callback is already registered */ + ReleaseTupleDesc(*cache_field); + } + else + { + /* Need to register shutdown callback to release tupdesc */ + RegisterExprContextCallback(econtext, + ShutdownTupleDescRef, + PointerGetDatum(cache_field)); + } + *cache_field = tupDesc; + } + return tupDesc; +} + +/* + * Callback function to release a tupdesc refcount at expression tree shutdown + */ +static void +ShutdownTupleDescRef(Datum arg) +{ + TupleDesc *cache_field = (TupleDesc *) DatumGetPointer(arg); + + if (*cache_field) + ReleaseTupleDesc(*cache_field); + *cache_field = NULL; +} + /* * Evaluate arguments for a function. */ @@ -649,16 +1019,17 @@ ExecEvalFuncArgs(FunctionCallInfo fcinfo, { ExprDoneCond argIsDone; int i; - List *arg; + ListCell *arg; argIsDone = ExprSingleResult; /* default assumption */ i = 0; foreach(arg, argList) { + ExprState *argstate = (ExprState *) lfirst(arg); ExprDoneCond thisArgIsDone; - fcinfo->arg[i] = ExecEvalExpr((ExprState *) lfirst(arg), + fcinfo->arg[i] = ExecEvalExpr(argstate, econtext, &fcinfo->argnull[i], &thisArgIsDone); @@ -666,10 +1037,9 @@ ExecEvalFuncArgs(FunctionCallInfo fcinfo, if (thisArgIsDone != ExprSingleResult) { /* - * We allow only one argument to have a set value; we'd need - * much more complexity to keep track of multiple set - * arguments (cf. ExecTargetList) and it doesn't seem worth - * it. + * We allow only one argument to have a set value; we'd need much + * more complexity to keep track of multiple set arguments (cf. + * ExecTargetList) and it doesn't seem worth it. */ if (argIsDone != ExprSingleResult) ereport(ERROR, @@ -704,18 +1074,19 @@ ExecMakeFunctionResult(FuncExprState *fcache, bool hasSetArg; int i; + /* Guard against stack overflow due to overly complex expressions */ + check_stack_depth(); + /* - * arguments is a list of expressions to evaluate before passing to - * the function manager. We skip the evaluation if it was already - * done in the previous call (ie, we are continuing the evaluation of - * a set-valued function). Otherwise, collect the current argument - * values into fcinfo. + * arguments is a list of expressions to evaluate before passing to the + * function manager. We skip the evaluation if it was already done in the + * previous call (ie, we are continuing the evaluation of a set-valued + * function). Otherwise, collect the current argument values into fcinfo. */ if (!fcache->setArgsValid) { /* Need to prep callinfo structure */ - MemSet(&fcinfo, 0, sizeof(fcinfo)); - fcinfo.flinfo = &(fcache->func); + InitFunctionCallInfoData(fcinfo, &(fcache->func), 0, NULL, NULL); argDone = ExecEvalFuncArgs(&fcinfo, arguments, econtext); if (argDone == ExprEndResult) { @@ -741,12 +1112,11 @@ ExecMakeFunctionResult(FuncExprState *fcache, } /* - * If function returns set, prepare a resultinfo node for - * communication + * If function returns set, prepare a resultinfo node for communication */ if (fcache->func.fn_retset) { - fcinfo.resultinfo = (void *) &rsinfo; + fcinfo.resultinfo = (Node *) &rsinfo; rsinfo.type = T_ReturnSetInfo; rsinfo.econtext = econtext; rsinfo.expectedDesc = NULL; @@ -758,14 +1128,14 @@ ExecMakeFunctionResult(FuncExprState *fcache, } /* - * now return the value gotten by calling the function manager, - * passing the function the evaluated parameter values. + * now return the value gotten by calling the function manager, passing + * the function the evaluated parameter values. */ if (fcache->func.fn_retset || hasSetArg) { /* - * We need to return a set result. Complain if caller not ready - * to accept one. + * We need to return a set result. Complain if caller not ready to + * accept one. */ if (isDone == NULL) ereport(ERROR, @@ -773,18 +1143,18 @@ ExecMakeFunctionResult(FuncExprState *fcache, errmsg("set-valued function called in context that cannot accept a set"))); /* - * This loop handles the situation where we have both a set - * argument and a set-valued function. Once we have exhausted the - * function's value(s) for a particular argument value, we have to - * get the next argument value and start the function over again. - * We might have to do it more than once, if the function produces - * an empty result set for a particular input value. + * This loop handles the situation where we have both a set argument + * and a set-valued function. Once we have exhausted the function's + * value(s) for a particular argument value, we have to get the next + * argument value and start the function over again. We might have to + * do it more than once, if the function produces an empty result set + * for a particular input value. */ for (;;) { /* - * If function is strict, and there are any NULL arguments, - * skip calling the function (at least for this set of args). + * If function is strict, and there are any NULL arguments, skip + * calling the function (at least for this set of args). */ bool callit = true; @@ -819,21 +1189,30 @@ ExecMakeFunctionResult(FuncExprState *fcache, { /* * Got a result from current argument. If function itself - * returns set, save the current argument values to re-use - * on the next call. + * returns set, save the current argument values to re-use on + * the next call. */ - if (fcache->func.fn_retset) + if (fcache->func.fn_retset && *isDone == ExprMultipleResult) { memcpy(&fcache->setArgs, &fcinfo, sizeof(fcinfo)); fcache->setHasSetArg = hasSetArg; fcache->setArgsValid = true; + /* Register cleanup callback if we didn't already */ + if (!fcache->shutdown_reg) + { + RegisterExprContextCallback(econtext, + ShutdownFuncExpr, + PointerGetDatum(fcache)); + fcache->shutdown_reg = true; + } } /* * Make sure we say we are returning a set, even if the * function itself doesn't return sets. */ - *isDone = ExprMultipleResult; + if (hasSetArg) + *isDone = ExprMultipleResult; break; } @@ -854,8 +1233,8 @@ ExecMakeFunctionResult(FuncExprState *fcache, } /* - * If we reach here, loop around to run the function on the - * new argument. + * If we reach here, loop around to run the function on the new + * argument. */ } } @@ -864,6 +1243,17 @@ ExecMakeFunctionResult(FuncExprState *fcache, /* * Non-set case: much easier. * + * We change the ExprState function pointer to use the simpler + * ExecMakeFunctionResultNoSets on subsequent calls. This amounts to + * assuming that no argument can return a set if it didn't do so the + * first time. + */ + fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResultNoSets; + + if (isDone) + *isDone = ExprSingleResult; + + /* * If function is strict, and there are any NULL arguments, skip * calling the function and return NULL. */ @@ -886,12 +1276,73 @@ ExecMakeFunctionResult(FuncExprState *fcache, return result; } +/* + * ExecMakeFunctionResultNoSets + * + * Simplified version of ExecMakeFunctionResult that can only handle + * non-set cases. Hand-tuned for speed. + */ +static Datum +ExecMakeFunctionResultNoSets(FuncExprState *fcache, + ExprContext *econtext, + bool *isNull, + ExprDoneCond *isDone) +{ + ListCell *arg; + Datum result; + FunctionCallInfoData fcinfo; + int i; + + /* Guard against stack overflow due to overly complex expressions */ + check_stack_depth(); + + if (isDone) + *isDone = ExprSingleResult; + + /* inlined, simplified version of ExecEvalFuncArgs */ + i = 0; + foreach(arg, fcache->args) + { + ExprState *argstate = (ExprState *) lfirst(arg); + + fcinfo.arg[i] = ExecEvalExpr(argstate, + econtext, + &fcinfo.argnull[i], + NULL); + i++; + } + + InitFunctionCallInfoData(fcinfo, &(fcache->func), i, NULL, NULL); + + /* + * If function is strict, and there are any NULL arguments, skip calling + * the function and return NULL. + */ + if (fcache->func.fn_strict) + { + while (--i >= 0) + { + if (fcinfo.argnull[i]) + { + *isNull = true; + return (Datum) 0; + } + } + } + /* fcinfo.isnull = false; */ /* handled by InitFunctionCallInfoData */ + result = FunctionCallInvoke(&fcinfo); + *isNull = fcinfo.isnull; + + return result; +} + /* * ExecMakeTableFunctionResult * * Evaluate a table function, producing a materialized result in a Tuplestore - * object. (If function returns an empty set, we just return NULL instead.) + * object. *returnDesc is set to the tupledesc actually returned by the + * function, or NULL if it didn't provide one. */ Tuplestorestate * ExecMakeTableFunctionResult(ExprState *funcexpr, @@ -902,24 +1353,48 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, Tuplestorestate *tupstore = NULL; TupleDesc tupdesc = NULL; Oid funcrettype; + bool returnsTuple; + bool returnsSet = false; FunctionCallInfoData fcinfo; ReturnSetInfo rsinfo; + HeapTupleData tmptup; MemoryContext callerContext; MemoryContext oldcontext; - TupleTableSlot *slot; bool direct_function_call; bool first_time = true; - bool returnsTuple = false; + + callerContext = CurrentMemoryContext; + + funcrettype = exprType((Node *) funcexpr->expr); + + returnsTuple = type_is_rowtype(funcrettype); + + /* + * Prepare a resultinfo node for communication. We always do this even if + * not expecting a set result, so that we can pass expectedDesc. In the + * generic-expression case, the expression doesn't actually get to see the + * resultinfo, but set it up anyway because we use some of the fields as + * our own state variables. + */ + InitFunctionCallInfoData(fcinfo, NULL, 0, NULL, (Node *) &rsinfo); + rsinfo.type = T_ReturnSetInfo; + rsinfo.econtext = econtext; + rsinfo.expectedDesc = expectedDesc; + rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize); + rsinfo.returnMode = SFRM_ValuePerCall; + /* isDone is filled below */ + rsinfo.setResult = NULL; + rsinfo.setDesc = NULL; /* - * Normally the passed expression tree will be a FuncExprState, since - * the grammar only allows a function call at the top level of a table - * function reference. However, if the function doesn't return set - * then the planner might have replaced the function call via - * constant-folding or inlining. So if we see any other kind of - * expression node, execute it via the general ExecEvalExpr() code; - * the only difference is that we don't get a chance to pass a special - * ReturnSetInfo to any functions buried in the expression. + * Normally the passed expression tree will be a FuncExprState, since the + * grammar only allows a function call at the top level of a table + * function reference. However, if the function doesn't return set then + * the planner might have replaced the function call via constant-folding + * or inlining. So if we see any other kind of expression node, execute + * it via the general ExecEvalExpr() code; the only difference is that we + * don't get a chance to pass a special ReturnSetInfo to any functions + * buried in the expression. */ if (funcexpr && IsA(funcexpr, FuncExprState) && IsA(funcexpr->expr, FuncExpr)) @@ -941,16 +1416,16 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, init_fcache(func->funcid, fcache, econtext->ecxt_per_query_memory); } + returnsSet = fcache->func.fn_retset; /* * Evaluate the function's argument list. * * Note: ideally, we'd do this in the per-tuple context, but then the - * argument values would disappear when we reset the context in - * the inner loop. So do it in caller context. Perhaps we should - * make a separate context just to hold the evaluated arguments? + * argument values would disappear when we reset the context in the + * inner loop. So do it in caller context. Perhaps we should make a + * separate context just to hold the evaluated arguments? */ - MemSet(&fcinfo, 0, sizeof(fcinfo)); fcinfo.flinfo = &(fcache->func); argDone = ExecEvalFuncArgs(&fcinfo, fcache->args, econtext); /* We don't allow sets in the arguments of the table function */ @@ -961,7 +1436,8 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, /* * If function is strict, and there are any NULL arguments, skip - * calling the function and return NULL (actually an empty set). + * calling the function and act like it returned NULL (or an empty + * set, in the returns-set case). */ if (fcache->func.fn_strict) { @@ -970,10 +1446,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, for (i = 0; i < fcinfo.nargs; i++) { if (fcinfo.argnull[i]) - { - *returnDesc = NULL; - return NULL; - } + goto no_function_result; } } } @@ -983,30 +1456,10 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, direct_function_call = false; } - funcrettype = exprType((Node *) funcexpr->expr); - - /* - * Prepare a resultinfo node for communication. We always do this - * even if not expecting a set result, so that we can pass - * expectedDesc. In the generic-expression case, the expression - * doesn't actually get to see the resultinfo, but set it up anyway - * because we use some of the fields as our own state variables. - */ - fcinfo.resultinfo = (void *) &rsinfo; - rsinfo.type = T_ReturnSetInfo; - rsinfo.econtext = econtext; - rsinfo.expectedDesc = expectedDesc; - rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize); - rsinfo.returnMode = SFRM_ValuePerCall; - /* isDone is filled below */ - rsinfo.setResult = NULL; - rsinfo.setDesc = NULL; - /* - * Switch to short-lived context for calling the function or - * expression. + * Switch to short-lived context for calling the function or expression. */ - callerContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); + MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); /* * Loop to handle the ValuePerCall protocol (which is also the same @@ -1017,10 +1470,12 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, Datum result; HeapTuple tuple; + CHECK_FOR_INTERRUPTS(); + /* - * reset per-tuple memory context before each call of the function - * or expression. This cleans up any local memory the function may - * leak when called. + * reset per-tuple memory context before each call of the function or + * expression. This cleans up any local memory the function may leak + * when called. */ ResetExprContext(econtext); @@ -1042,40 +1497,44 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, { /* * Check for end of result set. - * - * Note: if function returns an empty set, we don't build a - * tupdesc or tuplestore (since we can't get a tupdesc in the - * function-returning-tuple case) */ if (rsinfo.isDone == ExprEndResult) break; /* - * If first time through, build tupdesc and tuplestore for - * result + * Can't do anything very useful with NULL rowtype values. For a + * function returning set, we consider this a protocol violation + * (but another alternative would be to just ignore the result and + * "continue" to get another row). For a function not returning + * set, we fall out of the loop; we'll cons up an all-nulls result + * row below. + */ + if (returnsTuple && fcinfo.isnull) + { + if (!returnsSet) + break; + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("function returning set of rows 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_copy(HeapTupleHeaderGetTypeId(td), + HeapTupleHeaderGetTypMod(td)); } else { @@ -1088,10 +1547,9 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, "column", funcrettype, -1, - 0, - false); + 0); } - tupstore = tuplestore_begin_heap(true, false, SortMem); + tupstore = tuplestore_begin_heap(true, false, work_mem); MemoryContextSwitchTo(oldcontext); rsinfo.setResult = tupstore; rsinfo.setDesc = tupdesc; @@ -1102,22 +1560,21 @@ 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 { - char nullflag; - - nullflag = fcinfo.isnull ? 'n' : ' '; - tuple = heap_formtuple(tupdesc, &result, &nullflag); + tuple = heap_form_tuple(tupdesc, &result, &fcinfo.isnull); } oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); @@ -1149,6 +1606,35 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, first_time = false; } +no_function_result: + + /* + * If we got nothing from the function (ie, an empty-set or NULL result), + * we have to create the tuplestore to return, and if it's a + * non-set-returning function then insert a single all-nulls row. + */ + if (rsinfo.setResult == NULL) + { + MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo.setResult = tupstore; + if (!returnsSet) + { + int natts = expectedDesc->natts; + Datum *nulldatums; + bool *nullflags; + HeapTuple tuple; + + MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); + nulldatums = (Datum *) palloc0(natts * sizeof(Datum)); + nullflags = (bool *) palloc(natts * sizeof(bool)); + memset(nullflags, true, natts * sizeof(bool)); + tuple = heap_form_tuple(expectedDesc, nulldatums, nullflags); + MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + tuplestore_puttuple(tupstore, tuple); + } + } + MemoryContextSwitchTo(callerContext); /* The returned pointers are those in rsinfo */ @@ -1176,15 +1662,14 @@ ExecEvalFunc(FuncExprState *fcache, bool *isNull, ExprDoneCond *isDone) { - /* - * Initialize function cache if first time through - */ - if (fcache->func.fn_oid == InvalidOid) - { - FuncExpr *func = (FuncExpr *) fcache->xprstate.expr; + /* This is called only the first time through */ + FuncExpr *func = (FuncExpr *) fcache->xprstate.expr; - init_fcache(func->funcid, fcache, econtext->ecxt_per_query_memory); - } + /* Initialize function lookup info */ + init_fcache(func->funcid, fcache, econtext->ecxt_per_query_memory); + + /* Go directly to ExecMakeFunctionResult on subsequent uses */ + fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResult; return ExecMakeFunctionResult(fcache, econtext, isNull, isDone); } @@ -1199,15 +1684,14 @@ ExecEvalOper(FuncExprState *fcache, bool *isNull, ExprDoneCond *isDone) { - /* - * Initialize function cache if first time through - */ - if (fcache->func.fn_oid == InvalidOid) - { - OpExpr *op = (OpExpr *) fcache->xprstate.expr; + /* This is called only the first time through */ + OpExpr *op = (OpExpr *) fcache->xprstate.expr; - init_fcache(op->opfuncid, fcache, econtext->ecxt_per_query_memory); - } + /* Initialize function lookup info */ + init_fcache(op->opfuncid, fcache, econtext->ecxt_per_query_memory); + + /* Go directly to ExecMakeFunctionResult on subsequent uses */ + fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResult; return ExecMakeFunctionResult(fcache, econtext, isNull, isDone); } @@ -1226,13 +1710,19 @@ ExecEvalOper(FuncExprState *fcache, static Datum ExecEvalDistinct(FuncExprState *fcache, ExprContext *econtext, - bool *isNull) + bool *isNull, + ExprDoneCond *isDone) { Datum result; FunctionCallInfoData fcinfo; ExprDoneCond argDone; List *argList; + /* Set default values for result flags: non-null, not a set result */ + *isNull = false; + if (isDone) + *isDone = ExprSingleResult; + /* * Initialize function cache if first time through */ @@ -1250,13 +1740,12 @@ ExecEvalDistinct(FuncExprState *fcache, argList = fcache->args; /* Need to prep callinfo structure */ - MemSet(&fcinfo, 0, sizeof(fcinfo)); - fcinfo.flinfo = &(fcache->func); + InitFunctionCallInfoData(fcinfo, &(fcache->func), 0, NULL, NULL); argDone = ExecEvalFuncArgs(&fcinfo, argList, econtext); if (argDone != ExprSingleResult) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("IS DISTINCT FROM does not support set arguments"))); + errmsg("IS DISTINCT FROM does not support set arguments"))); Assert(fcinfo.nargs == 2); if (fcinfo.argnull[0] && fcinfo.argnull[1]) @@ -1291,7 +1780,8 @@ ExecEvalDistinct(FuncExprState *fcache, */ static Datum ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate, - ExprContext *econtext, bool *isNull) + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) { ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) sstate->fxprstate.xprstate.expr; bool useOr = opexpr->useOr; @@ -1306,6 +1796,13 @@ ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate, bool typbyval; char typalign; char *s; + bits8 *bitmap; + int bitmask; + + /* Set default values for result flags: non-null, not a set result */ + *isNull = false; + if (isDone) + *isDone = ExprSingleResult; /* * Initialize function cache if first time through @@ -1318,18 +1815,17 @@ ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate, } /* Need to prep callinfo structure */ - MemSet(&fcinfo, 0, sizeof(fcinfo)); - fcinfo.flinfo = &(sstate->fxprstate.func); + InitFunctionCallInfoData(fcinfo, &(sstate->fxprstate.func), 0, NULL, NULL); argDone = ExecEvalFuncArgs(&fcinfo, sstate->fxprstate.args, econtext); if (argDone != ExprSingleResult) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("op ANY/ALL (array) does not support set arguments"))); + errmsg("op ANY/ALL (array) does not support set arguments"))); Assert(fcinfo.nargs == 2); /* - * If the array is NULL then we return NULL --- it's not very - * meaningful to do anything else, even if the operator isn't strict. + * If the array is NULL then we return NULL --- it's not very meaningful + * to do anything else, even if the operator isn't strict. */ if (fcinfo.argnull[1]) { @@ -1342,18 +1838,16 @@ ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate, /* * If the array is empty, we return either FALSE or TRUE per the useOr * flag. This is correct even if the scalar is NULL; since we would - * evaluate the operator zero times, it matters not whether it would - * want to return NULL. + * evaluate the operator zero times, it matters not whether it would want + * to return NULL. */ nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr)); if (nitems <= 0) return BoolGetDatum(!useOr); /* - * If the scalar is NULL, and the function is strict, return NULL. - * This is just to avoid having to test for strictness inside the - * loop. (XXX but if arrays could have null elements, we'd need a - * test anyway.) + * If the scalar is NULL, and the function is strict, return NULL; no + * point in iterating the loop. */ if (fcinfo.argnull[0] && sstate->fxprstate.func.fn_strict) { @@ -1362,9 +1856,8 @@ ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate, } /* - * We arrange to look up info about the element type only once per - * series of calls, assuming the element type doesn't change - * underneath us. + * We arrange to look up info about the element type only once per series + * of calls, assuming the element type doesn't change underneath us. */ if (sstate->element_type != ARR_ELEMTYPE(arr)) { @@ -1383,22 +1876,40 @@ ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate, /* Loop over the array elements */ s = (char *) ARR_DATA_PTR(arr); + bitmap = ARR_NULLBITMAP(arr); + bitmask = 1; + for (i = 0; i < nitems; i++) { Datum elt; Datum thisresult; - /* Get array element */ - elt = fetch_att(s, typbyval, typlen); - - s = att_addlength(s, typlen, PointerGetDatum(s)); - s = (char *) att_align(s, typalign); + /* Get array element, checking for NULL */ + if (bitmap && (*bitmap & bitmask) == 0) + { + fcinfo.arg[1] = (Datum) 0; + fcinfo.argnull[1] = true; + } + else + { + elt = fetch_att(s, typbyval, typlen); + s = att_addlength(s, typlen, PointerGetDatum(s)); + s = (char *) att_align(s, typalign); + fcinfo.arg[1] = elt; + fcinfo.argnull[1] = false; + } /* Call comparison function */ - fcinfo.arg[1] = elt; - fcinfo.argnull[1] = false; - fcinfo.isnull = false; - thisresult = FunctionCallInvoke(&fcinfo); + if (fcinfo.argnull[1] && sstate->fxprstate.func.fn_strict) + { + fcinfo.isnull = true; + thisresult = (Datum) 0; + } + else + { + fcinfo.isnull = false; + thisresult = FunctionCallInvoke(&fcinfo); + } /* Combine results per OR or AND semantics */ if (fcinfo.isnull) @@ -1421,6 +1932,17 @@ ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate, break; /* needn't look at any more elements */ } } + + /* advance bitmap pointer if any */ + if (bitmap) + { + bitmask <<= 1; + if (bitmask == 0x100) + { + bitmap++; + bitmask = 1; + } + } } *isNull = resultnull; @@ -1443,25 +1965,27 @@ ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate, * ---------------------------------------------------------------- */ static Datum -ExecEvalNot(BoolExprState *notclause, ExprContext *econtext, bool *isNull) +ExecEvalNot(BoolExprState *notclause, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) { - ExprState *clause; + ExprState *clause = linitial(notclause->args); Datum expr_value; - clause = lfirst(notclause->args); + if (isDone) + *isDone = ExprSingleResult; expr_value = ExecEvalExpr(clause, econtext, isNull, NULL); /* - * if the expression evaluates to null, then we just cascade the null - * back to whoever called us. + * if the expression evaluates to null, then we just cascade the null back + * to whoever called us. */ if (*isNull) return expr_value; /* - * evaluation of 'not' is simple.. expr is false, then return 'true' - * and vice versa. + * evaluation of 'not' is simple.. expr is false, then return 'true' and + * vice versa. */ return BoolGetDatum(!DatumGetBool(expr_value)); } @@ -1471,34 +1995,37 @@ ExecEvalNot(BoolExprState *notclause, ExprContext *econtext, bool *isNull) * ---------------------------------------------------------------- */ static Datum -ExecEvalOr(BoolExprState *orExpr, ExprContext *econtext, bool *isNull) +ExecEvalOr(BoolExprState *orExpr, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) { - List *clauses; - List *clause; + List *clauses = orExpr->args; + ListCell *clause; bool AnyNull; - Datum clause_value; - clauses = orExpr->args; + if (isDone) + *isDone = ExprSingleResult; + AnyNull = false; /* - * If any of the clauses is TRUE, the OR result is TRUE regardless of - * the states of the rest of the clauses, so we can stop evaluating - * and return TRUE immediately. If none are TRUE and one or more is - * NULL, we return NULL; otherwise we return FALSE. This makes sense - * when you interpret NULL as "don't know": if we have a TRUE then the - * OR is TRUE even if we aren't sure about some of the other inputs. - * If all the known inputs are FALSE, but we have one or more "don't - * knows", then we have to report that we "don't know" what the OR's - * result should be --- perhaps one of the "don't knows" would have - * been TRUE if we'd known its value. Only when all the inputs are - * known to be FALSE can we state confidently that the OR's result is - * FALSE. + * If any of the clauses is TRUE, the OR result is TRUE regardless of the + * states of the rest of the clauses, so we can stop evaluating and return + * TRUE immediately. If none are TRUE and one or more is NULL, we return + * NULL; otherwise we return FALSE. This makes sense when you interpret + * NULL as "don't know": if we have a TRUE then the OR is TRUE even if we + * aren't sure about some of the other inputs. If all the known inputs are + * FALSE, but we have one or more "don't knows", then we have to report + * that we "don't know" what the OR's result should be --- perhaps one of + * the "don't knows" would have been TRUE if we'd known its value. Only + * when all the inputs are known to be FALSE can we state confidently that + * the OR's result is FALSE. */ foreach(clause, clauses) { - clause_value = ExecEvalExpr((ExprState *) lfirst(clause), - econtext, isNull, NULL); + ExprState *clausestate = (ExprState *) lfirst(clause); + Datum clause_value; + + clause_value = ExecEvalExpr(clausestate, econtext, isNull, NULL); /* * if we have a non-null true result, then return it. @@ -1519,28 +2046,33 @@ ExecEvalOr(BoolExprState *orExpr, ExprContext *econtext, bool *isNull) * ---------------------------------------------------------------- */ static Datum -ExecEvalAnd(BoolExprState *andExpr, ExprContext *econtext, bool *isNull) +ExecEvalAnd(BoolExprState *andExpr, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) { - List *clauses; - List *clause; + List *clauses = andExpr->args; + ListCell *clause; bool AnyNull; - Datum clause_value; - clauses = andExpr->args; + if (isDone) + *isDone = ExprSingleResult; + AnyNull = false; /* - * If any of the clauses is FALSE, the AND result is FALSE regardless - * of the states of the rest of the clauses, so we can stop evaluating - * and return FALSE immediately. If none are FALSE and one or more is - * NULL, we return NULL; otherwise we return TRUE. This makes sense - * when you interpret NULL as "don't know", using the same sort of - * reasoning as for OR, above. + * If any of the clauses is FALSE, the AND result is FALSE regardless of + * the states of the rest of the clauses, so we can stop evaluating and + * return FALSE immediately. If none are FALSE and one or more is NULL, + * we return NULL; otherwise we return TRUE. This makes sense when you + * interpret NULL as "don't know", using the same sort of reasoning as for + * OR, above. */ + foreach(clause, clauses) { - clause_value = ExecEvalExpr((ExprState *) lfirst(clause), - econtext, isNull, NULL); + ExprState *clausestate = (ExprState *) lfirst(clause); + Datum clause_value; + + clause_value = ExecEvalExpr(clausestate, econtext, isNull, NULL); /* * if we have a non-null false result, then return it. @@ -1556,6 +2088,148 @@ ExecEvalAnd(BoolExprState *andExpr, ExprContext *econtext, bool *isNull) return BoolGetDatum(!AnyNull); } +/* ---------------------------------------------------------------- + * ExecEvalConvertRowtype + * + * Evaluate a rowtype coercion operation. This may require + * rearranging field positions. + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) cstate->xprstate.expr; + HeapTuple result; + Datum tupDatum; + HeapTupleHeader tuple; + HeapTupleData tmptup; + AttrNumber *attrMap; + Datum *invalues; + bool *inisnull; + Datum *outvalues; + bool *outisnull; + int i; + int outnatts; + + tupDatum = ExecEvalExpr(cstate->arg, econtext, isNull, isDone); + + /* this test covers the isDone exception too: */ + if (*isNull) + return tupDatum; + + tuple = DatumGetHeapTupleHeader(tupDatum); + + /* Lookup tupdescs if first time through or after rescan */ + if (cstate->indesc == NULL) + get_cached_rowtype(exprType((Node *) convert->arg), -1, + &cstate->indesc, econtext); + if (cstate->outdesc == NULL) + get_cached_rowtype(convert->resulttype, -1, + &cstate->outdesc, econtext); + + Assert(HeapTupleHeaderGetTypeId(tuple) == cstate->indesc->tdtypeid); + Assert(HeapTupleHeaderGetTypMod(tuple) == cstate->indesc->tdtypmod); + + /* if first time through, initialize */ + if (cstate->attrMap == NULL) + { + MemoryContext old_cxt; + int n; + + /* allocate state in long-lived memory context */ + old_cxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + + /* prepare map from old to new attribute numbers */ + n = cstate->outdesc->natts; + cstate->attrMap = (AttrNumber *) palloc0(n * sizeof(AttrNumber)); + for (i = 0; i < n; i++) + { + Form_pg_attribute att = cstate->outdesc->attrs[i]; + char *attname; + Oid atttypid; + int32 atttypmod; + int j; + + if (att->attisdropped) + continue; /* attrMap[i] is already 0 */ + attname = NameStr(att->attname); + atttypid = att->atttypid; + atttypmod = att->atttypmod; + for (j = 0; j < cstate->indesc->natts; j++) + { + att = cstate->indesc->attrs[j]; + if (att->attisdropped) + continue; + if (strcmp(attname, NameStr(att->attname)) == 0) + { + /* Found it, check type */ + if (atttypid != att->atttypid || atttypmod != att->atttypmod) + elog(ERROR, "attribute \"%s\" of type %s does not match corresponding attribute of type %s", + attname, + format_type_be(cstate->indesc->tdtypeid), + format_type_be(cstate->outdesc->tdtypeid)); + cstate->attrMap[i] = (AttrNumber) (j + 1); + break; + } + } + if (cstate->attrMap[i] == 0) + elog(ERROR, "attribute \"%s\" of type %s does not exist", + attname, + format_type_be(cstate->indesc->tdtypeid)); + } + /* preallocate workspace for Datum arrays */ + n = cstate->indesc->natts + 1; /* +1 for NULL */ + cstate->invalues = (Datum *) palloc(n * sizeof(Datum)); + cstate->inisnull = (bool *) palloc(n * sizeof(bool)); + n = cstate->outdesc->natts; + cstate->outvalues = (Datum *) palloc(n * sizeof(Datum)); + cstate->outisnull = (bool *) palloc(n * sizeof(bool)); + + MemoryContextSwitchTo(old_cxt); + } + + attrMap = cstate->attrMap; + invalues = cstate->invalues; + inisnull = cstate->inisnull; + outvalues = cstate->outvalues; + outisnull = cstate->outisnull; + outnatts = cstate->outdesc->natts; + + /* + * heap_deform_tuple needs a HeapTuple not a bare HeapTupleHeader. + */ + tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); + tmptup.t_data = tuple; + + /* + * Extract all the values of the old tuple, offsetting the arrays so that + * invalues[0] is NULL and invalues[1] is the first source attribute; this + * exactly matches the numbering convention in attrMap. + */ + heap_deform_tuple(&tmptup, cstate->indesc, invalues + 1, inisnull + 1); + invalues[0] = (Datum) 0; + inisnull[0] = true; + + /* + * Transpose into proper fields of the new tuple. + */ + for (i = 0; i < outnatts; i++) + { + int j = attrMap[i]; + + outvalues[i] = invalues[j]; + outisnull[i] = inisnull[j]; + } + + /* + * Now form the new tuple. + */ + result = heap_form_tuple(cstate->outdesc, outvalues, outisnull); + + return HeapTupleGetDatum(result); +} /* ---------------------------------------------------------------- * ExecEvalCase @@ -1570,20 +2244,40 @@ static Datum ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone) { - List *clauses; - List *clause; - Datum clause_value; + List *clauses = caseExpr->args; + ListCell *clause; + Datum save_datum; + bool save_isNull; - clauses = caseExpr->args; + if (isDone) + *isDone = ExprSingleResult; + + /* + * If there's a test expression, we have to evaluate it and save the value + * where the CaseTestExpr placeholders can find it. We must save and + * restore prior setting of econtext's caseValue fields, in case this node + * is itself within a larger CASE. + */ + save_datum = econtext->caseValue_datum; + save_isNull = econtext->caseValue_isNull; + + if (caseExpr->arg) + { + econtext->caseValue_datum = ExecEvalExpr(caseExpr->arg, + econtext, + &econtext->caseValue_isNull, + NULL); + } /* - * we evaluate each of the WHEN clauses in turn, as soon as one is - * true we return the corresponding result. If none are true then we - * return the value of the default clause, or NULL if there is none. + * we evaluate each of the WHEN clauses in turn, as soon as one is true we + * return the corresponding result. If none are true then we return the + * value of the default clause, or NULL if there is none. */ foreach(clause, clauses) { CaseWhenState *wclause = lfirst(clause); + Datum clause_value; clause_value = ExecEvalExpr(wclause->expr, econtext, @@ -1591,12 +2285,14 @@ ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext, NULL); /* - * if we have a true test, then we return the result, since the - * case statement is satisfied. A NULL result from the test is - * not considered true. + * if we have a true test, then we return the result, since the case + * statement is satisfied. A NULL result from the test is not + * considered true. */ if (DatumGetBool(clause_value) && !*isNull) { + econtext->caseValue_datum = save_datum; + econtext->caseValue_isNull = save_isNull; return ExecEvalExpr(wclause->result, econtext, isNull, @@ -1604,6 +2300,9 @@ ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext, } } + econtext->caseValue_datum = save_datum; + econtext->caseValue_isNull = save_isNull; + if (caseExpr->defresult) { return ExecEvalExpr(caseExpr->defresult, @@ -1616,206 +2315,664 @@ ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext, return (Datum) 0; } +/* + * ExecEvalCaseTestExpr + * + * Return the value stored by CASE. + */ +static Datum +ExecEvalCaseTestExpr(ExprState *exprstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + if (isDone) + *isDone = ExprSingleResult; + *isNull = econtext->caseValue_isNull; + return econtext->caseValue_datum; +} + /* ---------------------------------------------------------------- * ExecEvalArray - ARRAY[] expressions - * - * NOTE: currently, if any input value is NULL then we return a NULL array, - * so the ARRAY[] construct can be considered strict. Eventually this will - * change; when it does, be sure to fix contain_nonstrict_functions(). * ---------------------------------------------------------------- */ static Datum ExecEvalArray(ArrayExprState *astate, ExprContext *econtext, - bool *isNull) + bool *isNull, ExprDoneCond *isDone) { ArrayExpr *arrayExpr = (ArrayExpr *) astate->xprstate.expr; ArrayType *result; - List *element; + ListCell *element; Oid element_type = arrayExpr->element_typeid; int ndims = 0; int dims[MAXDIM]; int lbs[MAXDIM]; + /* Set default values for result flags: non-null, not a set result */ + *isNull = false; + if (isDone) + *isDone = ExprSingleResult; + if (!arrayExpr->multidims) { /* Elements are presumably of scalar type */ int nelems; Datum *dvalues; + bool *dnulls; int i = 0; ndims = 1; - nelems = length(astate->elements); + nelems = list_length(astate->elements); - /* Shouldn't happen here, but if length is 0, return NULL */ + /* Shouldn't happen here, but if length is 0, return empty array */ if (nelems == 0) + return PointerGetDatum(construct_empty_array(element_type)); + + dvalues = (Datum *) palloc(nelems * sizeof(Datum)); + dnulls = (bool *) palloc(nelems * sizeof(bool)); + + /* loop through and build array of datums */ + foreach(element, astate->elements) { - *isNull = true; - return (Datum) 0; + ExprState *e = (ExprState *) lfirst(element); + + dvalues[i] = ExecEvalExpr(e, econtext, &dnulls[i], NULL); + i++; + } + + /* setup for 1-D array of the given length */ + dims[0] = nelems; + lbs[0] = 1; + + result = construct_md_array(dvalues, dnulls, ndims, dims, lbs, + element_type, + astate->elemlength, + astate->elembyval, + astate->elemalign); + } + else + { + /* Must be nested array expressions */ + int nbytes = 0; + int nitems = 0; + int outer_nelems = 0; + int elem_ndims = 0; + int *elem_dims = NULL; + int *elem_lbs = NULL; + bool firstone = true; + bool havenulls = false; + bool haveempty = false; + char **subdata; + bits8 **subbitmaps; + int *subbytes; + int *subnitems; + int i; + int32 dataoffset; + char *dat; + int iitem; + + i = list_length(astate->elements); + subdata = (char **) palloc(i * sizeof(char *)); + subbitmaps = (bits8 **) palloc(i * sizeof(bits8 *)); + subbytes = (int *) palloc(i * sizeof(int)); + subnitems = (int *) palloc(i * sizeof(int)); + + /* loop through and get data area from each element */ + foreach(element, astate->elements) + { + ExprState *e = (ExprState *) lfirst(element); + bool eisnull; + Datum arraydatum; + ArrayType *array; + int this_ndims; + + arraydatum = ExecEvalExpr(e, econtext, &eisnull, NULL); + /* temporarily ignore null subarrays */ + if (eisnull) + { + haveempty = true; + continue; + } + + array = DatumGetArrayTypeP(arraydatum); + + /* run-time double-check on element type */ + if (element_type != ARR_ELEMTYPE(array)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot merge incompatible arrays"), + errdetail("Array with element type %s cannot be " + "included in ARRAY construct with element type %s.", + format_type_be(ARR_ELEMTYPE(array)), + format_type_be(element_type)))); + + this_ndims = ARR_NDIM(array); + /* temporarily ignore zero-dimensional subarrays */ + if (this_ndims <= 0) + { + haveempty = true; + continue; + } + + if (firstone) + { + /* Get sub-array details from first member */ + elem_ndims = this_ndims; + ndims = elem_ndims + 1; + if (ndims <= 0 || ndims > MAXDIM) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of array dimensions (%d) exceeds " \ + "the maximum allowed (%d)", ndims, MAXDIM))); + + elem_dims = (int *) palloc(elem_ndims * sizeof(int)); + memcpy(elem_dims, ARR_DIMS(array), elem_ndims * sizeof(int)); + elem_lbs = (int *) palloc(elem_ndims * sizeof(int)); + memcpy(elem_lbs, ARR_LBOUND(array), elem_ndims * sizeof(int)); + + firstone = false; + } + else + { + /* Check other sub-arrays are compatible */ + if (elem_ndims != this_ndims || + memcmp(elem_dims, ARR_DIMS(array), + elem_ndims * sizeof(int)) != 0 || + memcmp(elem_lbs, ARR_LBOUND(array), + elem_ndims * sizeof(int)) != 0) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("multidimensional arrays must have array " + "expressions with matching dimensions"))); + } + + subdata[outer_nelems] = ARR_DATA_PTR(array); + subbitmaps[outer_nelems] = ARR_NULLBITMAP(array); + subbytes[outer_nelems] = ARR_SIZE(array) - ARR_DATA_OFFSET(array); + nbytes += subbytes[outer_nelems]; + subnitems[outer_nelems] = ArrayGetNItems(this_ndims, + ARR_DIMS(array)); + nitems += subnitems[outer_nelems]; + havenulls |= ARR_HASNULL(array); + outer_nelems++; + } + + /* + * If all items were null or empty arrays, return an empty array; + * otherwise, if some were and some weren't, raise error. (Note: + * we must special-case this somehow to avoid trying to generate + * a 1-D array formed from empty arrays. It's not ideal...) + */ + if (haveempty) + { + if (ndims == 0) /* didn't find any nonempty array */ + return PointerGetDatum(construct_empty_array(element_type)); + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("multidimensional arrays must have array " + "expressions with matching dimensions"))); + } + + /* setup for multi-D array */ + dims[0] = outer_nelems; + lbs[0] = 1; + for (i = 1; i < ndims; i++) + { + dims[i] = elem_dims[i - 1]; + lbs[i] = elem_lbs[i - 1]; + } + + if (havenulls) + { + dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nitems); + nbytes += dataoffset; + } + else + { + dataoffset = 0; /* marker for no null bitmap */ + nbytes += ARR_OVERHEAD_NONULLS(ndims); } - dvalues = (Datum *) palloc(nelems * sizeof(Datum)); + result = (ArrayType *) palloc(nbytes); + result->size = nbytes; + result->ndim = ndims; + result->dataoffset = dataoffset; + result->elemtype = element_type; + memcpy(ARR_DIMS(result), dims, ndims * sizeof(int)); + memcpy(ARR_LBOUND(result), lbs, ndims * sizeof(int)); + + dat = ARR_DATA_PTR(result); + iitem = 0; + for (i = 0; i < outer_nelems; i++) + { + memcpy(dat, subdata[i], subbytes[i]); + dat += subbytes[i]; + if (havenulls) + array_bitmap_copy(ARR_NULLBITMAP(result), iitem, + subbitmaps[i], 0, + subnitems[i]); + iitem += subnitems[i]; + } + } + + return PointerGetDatum(result); +} + +/* ---------------------------------------------------------------- + * ExecEvalRow - ROW() expressions + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalRow(RowExprState *rstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + HeapTuple tuple; + Datum *values; + bool *isnull; + int natts; + ListCell *arg; + int i; + + /* Set default values for result flags: non-null, not a set result */ + *isNull = false; + if (isDone) + *isDone = ExprSingleResult; + + /* Allocate workspace */ + natts = rstate->tupdesc->natts; + values = (Datum *) palloc0(natts * sizeof(Datum)); + isnull = (bool *) palloc(natts * sizeof(bool)); + + /* preset to nulls in case rowtype has some later-added columns */ + memset(isnull, true, natts * sizeof(bool)); + + /* Evaluate field values */ + i = 0; + foreach(arg, rstate->args) + { + ExprState *e = (ExprState *) lfirst(arg); + + values[i] = ExecEvalExpr(e, econtext, &isnull[i], NULL); + i++; + } + + tuple = heap_form_tuple(rstate->tupdesc, values, isnull); + + pfree(values); + pfree(isnull); + + return HeapTupleGetDatum(tuple); +} + +/* ---------------------------------------------------------------- + * ExecEvalRowCompare - ROW() comparison-op ROW() + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalRowCompare(RowCompareExprState *rstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + bool result; + RowCompareType rctype = ((RowCompareExpr *) rstate->xprstate.expr)->rctype; + int32 cmpresult = 0; + ListCell *l; + ListCell *r; + int i; + + if (isDone) + *isDone = ExprSingleResult; + *isNull = true; /* until we get a result */ + + i = 0; + forboth(l, rstate->largs, r, rstate->rargs) + { + ExprState *le = (ExprState *) lfirst(l); + ExprState *re = (ExprState *) lfirst(r); + FunctionCallInfoData locfcinfo; + + InitFunctionCallInfoData(locfcinfo, &(rstate->funcs[i]), 2, + NULL, NULL); + locfcinfo.arg[0] = ExecEvalExpr(le, econtext, + &locfcinfo.argnull[0], NULL); + locfcinfo.arg[1] = ExecEvalExpr(re, econtext, + &locfcinfo.argnull[1], NULL); + if (rstate->funcs[i].fn_strict && + (locfcinfo.argnull[0] || locfcinfo.argnull[1])) + return (Datum) 0; /* force NULL result */ + locfcinfo.isnull = false; + cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo)); + if (locfcinfo.isnull) + return (Datum) 0; /* force NULL result */ + if (cmpresult != 0) + break; /* no need to compare remaining columns */ + i++; + } + + switch (rctype) + { + /* EQ and NE cases aren't allowed here */ + case ROWCOMPARE_LT: + result = (cmpresult < 0); + break; + case ROWCOMPARE_LE: + result = (cmpresult <= 0); + break; + case ROWCOMPARE_GE: + result = (cmpresult >= 0); + break; + case ROWCOMPARE_GT: + result = (cmpresult > 0); + break; + default: + elog(ERROR, "unrecognized RowCompareType: %d", (int) rctype); + result = 0; /* keep compiler quiet */ + break; + } + + *isNull = false; + return BoolGetDatum(result); +} + +/* ---------------------------------------------------------------- + * ExecEvalCoalesce + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalCoalesce(CoalesceExprState *coalesceExpr, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + ListCell *arg; + + if (isDone) + *isDone = ExprSingleResult; + + /* Simply loop through until something NOT NULL is found */ + foreach(arg, coalesceExpr->args) + { + ExprState *e = (ExprState *) lfirst(arg); + Datum value; + + value = ExecEvalExpr(e, econtext, isNull, NULL); + if (!*isNull) + return value; + } + + /* Else return NULL */ + *isNull = true; + return (Datum) 0; +} + +/* ---------------------------------------------------------------- + * ExecEvalMinMax + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalMinMax(MinMaxExprState *minmaxExpr, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + Datum result = (Datum) 0; + MinMaxOp op = ((MinMaxExpr *) minmaxExpr->xprstate.expr)->op; + FunctionCallInfoData locfcinfo; + ListCell *arg; + + if (isDone) + *isDone = ExprSingleResult; + *isNull = true; /* until we get a result */ + + InitFunctionCallInfoData(locfcinfo, &minmaxExpr->cfunc, 2, NULL, NULL); + locfcinfo.argnull[0] = false; + locfcinfo.argnull[1] = false; + + foreach(arg, minmaxExpr->args) + { + ExprState *e = (ExprState *) lfirst(arg); + Datum value; + bool valueIsNull; + int32 cmpresult; + + value = ExecEvalExpr(e, econtext, &valueIsNull, NULL); + if (valueIsNull) + continue; /* ignore NULL inputs */ + + if (*isNull) + { + /* first nonnull input, adopt value */ + result = value; + *isNull = false; + } + else + { + /* apply comparison function */ + locfcinfo.arg[0] = result; + locfcinfo.arg[1] = value; + locfcinfo.isnull = false; + cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo)); + if (locfcinfo.isnull) /* probably should not happen */ + continue; + if (cmpresult > 0 && op == IS_LEAST) + result = value; + else if (cmpresult < 0 && op == IS_GREATEST) + result = value; + } + } + + return result; +} + +/* ---------------------------------------------------------------- + * ExecEvalXml + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + XmlExpr *xexpr = (XmlExpr *) xmlExpr->xprstate.expr; + text *result; + StringInfoData buf; + Datum value; + bool isnull; + ListCell *arg; + ListCell *narg; + int i; + + if (isDone) + *isDone = ExprSingleResult; + *isNull = true; /* until we get a result */ + + switch (xexpr->op) + { + case IS_XMLCONCAT: + { + List *values = NIL; + + foreach(arg, xmlExpr->args) + { + ExprState *e = (ExprState *) lfirst(arg); + + value = ExecEvalExpr(e, econtext, &isnull, NULL); + if (!isnull) + values = lappend(values, DatumGetPointer(value)); + } - /* loop through and build array of datums */ - foreach(element, astate->elements) - { - ExprState *e = (ExprState *) lfirst(element); - bool eisnull; + if (list_length(values) > 0) + { + *isNull = false; + return PointerGetDatum(xmlconcat(values)); + } + } + break; - dvalues[i++] = ExecEvalExpr(e, econtext, &eisnull, NULL); - if (eisnull) + case IS_XMLFOREST: + initStringInfo(&buf); + i = 0; + forboth(arg, xmlExpr->named_args, narg, xexpr->arg_names) { - *isNull = true; - return (Datum) 0; + ExprState *e = (ExprState *) lfirst(arg); + char *argname = strVal(lfirst(narg)); + + value = ExecEvalExpr(e, econtext, &isnull, NULL); + if (!isnull) + { + appendStringInfo(&buf, "<%s>%s", + argname, + map_sql_value_to_xml_value(value, exprType((Node *) e->expr)), + argname); + *isNull = false; + } + i++; } - } + break; - /* setup for 1-D array of the given length */ - dims[0] = nelems; - lbs[0] = 1; + /* The remaining cases don't need to set up buf */ + case IS_XMLELEMENT: + *isNull = false; + return PointerGetDatum(xmlelement(xmlExpr, econtext)); + break; - result = construct_md_array(dvalues, ndims, dims, lbs, - element_type, - astate->elemlength, - astate->elembyval, - astate->elemalign); - } - else - { - /* Must be nested array expressions */ - char *dat = NULL; - Size ndatabytes = 0; - int nbytes; - int outer_nelems = length(astate->elements); - int elem_ndims = 0; - int *elem_dims = NULL; - int *elem_lbs = NULL; - bool firstone = true; - int i; + case IS_XMLPARSE: + { + ExprState *e; + text *data; + bool preserve_whitespace; - /* loop through and get data area from each element */ - foreach(element, astate->elements) - { - ExprState *e = (ExprState *) lfirst(element); - bool eisnull; - Datum arraydatum; - ArrayType *array; - int elem_ndatabytes; + /* arguments are known to be text, bool */ + Assert(list_length(xmlExpr->args) == 2); - arraydatum = ExecEvalExpr(e, econtext, &eisnull, NULL); - if (eisnull) - { - *isNull = true; - return (Datum) 0; - } + e = (ExprState *) linitial(xmlExpr->args); + value = ExecEvalExpr(e, econtext, &isnull, NULL); + if (isnull) + return (Datum) 0; + data = DatumGetTextP(value); - array = DatumGetArrayTypeP(arraydatum); + e = (ExprState *) lsecond(xmlExpr->args); + value = ExecEvalExpr(e, econtext, &isnull, NULL); + if (isnull) /* probably can't happen */ + return (Datum) 0; + preserve_whitespace = DatumGetBool(value); - /* run-time double-check on element type */ - if (element_type != ARR_ELEMTYPE(array)) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("cannot merge incompatible arrays"), - errdetail("Array with element type %s cannot be " - "included in ARRAY construct with element type %s.", - format_type_be(ARR_ELEMTYPE(array)), - format_type_be(element_type)))); + *isNull = false; - if (firstone) + return PointerGetDatum(xmlparse(data, + xexpr->xmloption, + preserve_whitespace)); + } + break; + + case IS_XMLPI: { - /* Get sub-array details from first member */ - elem_ndims = ARR_NDIM(array); - ndims = elem_ndims + 1; - if (ndims <= 0 || ndims > MAXDIM) - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("number of array dimensions (%d) exceeds " \ - "the maximum allowed (%d)", ndims, MAXDIM))); + ExprState *e; + text *arg; - elem_dims = (int *) palloc(elem_ndims * sizeof(int)); - memcpy(elem_dims, ARR_DIMS(array), elem_ndims * sizeof(int)); - elem_lbs = (int *) palloc(elem_ndims * sizeof(int)); - memcpy(elem_lbs, ARR_LBOUND(array), elem_ndims * sizeof(int)); + /* optional argument is known to be text */ + Assert(list_length(xmlExpr->args) <= 1); - firstone = false; + if (xmlExpr->args) + { + e = (ExprState *) linitial(xmlExpr->args); + value = ExecEvalExpr(e, econtext, &isnull, NULL); + if (isnull) + arg = NULL; + else + arg = DatumGetTextP(value); + } + else + { + arg = NULL; + isnull = false; + } + + return PointerGetDatum(xmlpi(xexpr->name, arg, isnull, isNull)); } - else + break; + + case IS_XMLROOT: { - /* Check other sub-arrays are compatible */ - if (elem_ndims != ARR_NDIM(array) || - memcmp(elem_dims, ARR_DIMS(array), - elem_ndims * sizeof(int)) != 0 || - memcmp(elem_lbs, ARR_LBOUND(array), - elem_ndims * sizeof(int)) != 0) - ereport(ERROR, - (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), - errmsg("multidimensional arrays must have array " - "expressions with matching dimensions"))); + ExprState *e; + xmltype *data; + text *version; + int standalone; + + /* arguments are known to be xml, text, int */ + Assert(list_length(xmlExpr->args) == 3); + + e = (ExprState *) linitial(xmlExpr->args); + value = ExecEvalExpr(e, econtext, &isnull, NULL); + if (isnull) + return (Datum) 0; + data = DatumGetXmlP(value); + + e = (ExprState *) lsecond(xmlExpr->args); + value = ExecEvalExpr(e, econtext, &isnull, NULL); + if (isnull) + version = NULL; + else + version = DatumGetTextP(value); + + e = (ExprState *) lthird(xmlExpr->args); + value = ExecEvalExpr(e, econtext, &isnull, NULL); + standalone = DatumGetInt32(value); + + *isNull = false; + + return PointerGetDatum(xmlroot(data, + version, + standalone)); } + break; - elem_ndatabytes = ARR_SIZE(array) - ARR_OVERHEAD(elem_ndims); - ndatabytes += elem_ndatabytes; - if (dat == NULL) - dat = (char *) palloc(ndatabytes); - else - dat = (char *) repalloc(dat, ndatabytes); + case IS_XMLSERIALIZE: + { + ExprState *e; - memcpy(dat + (ndatabytes - elem_ndatabytes), - ARR_DATA_PTR(array), - elem_ndatabytes); - } + /* argument type is known to be xml */ + Assert(list_length(xmlExpr->args) == 1); - /* setup for multi-D array */ - dims[0] = outer_nelems; - lbs[0] = 1; - for (i = 1; i < ndims; i++) - { - dims[i] = elem_dims[i - 1]; - lbs[i] = elem_lbs[i - 1]; - } + e = (ExprState *) linitial(xmlExpr->args); + value = ExecEvalExpr(e, econtext, &isnull, NULL); + if (isnull) + return (Datum) 0; - nbytes = ndatabytes + ARR_OVERHEAD(ndims); - result = (ArrayType *) palloc(nbytes); + *isNull = false; - result->size = nbytes; - result->ndim = ndims; - result->flags = 0; - result->elemtype = element_type; - memcpy(ARR_DIMS(result), dims, ndims * sizeof(int)); - memcpy(ARR_LBOUND(result), lbs, ndims * sizeof(int)); - if (ndatabytes > 0) - memcpy(ARR_DATA_PTR(result), dat, ndatabytes); + return PointerGetDatum(xmltotext_with_xmloption(DatumGetXmlP(value), xexpr->xmloption)); + } + break; - if (dat != NULL) - pfree(dat); - } + case IS_DOCUMENT: + { + ExprState *e; - return PointerGetDatum(result); -} + /* optional argument is known to be xml */ + Assert(list_length(xmlExpr->args) == 1); -/* ---------------------------------------------------------------- - * ExecEvalCoalesce - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalCoalesce(CoalesceExprState *coalesceExpr, ExprContext *econtext, - bool *isNull) -{ - List *arg; + e = (ExprState *) linitial(xmlExpr->args); + value = ExecEvalExpr(e, econtext, &isnull, NULL); + if (isnull) + return (Datum) 0; + else + { + *isNull = false; + return BoolGetDatum(xml_is_document(DatumGetXmlP(value))); + } + } + break; + } - /* Simply loop through until something NOT NULL is found */ - foreach(arg, coalesceExpr->args) + if (*isNull) + result = NULL; + else { - ExprState *e = (ExprState *) lfirst(arg); - Datum value; + int len = buf.len + VARHDRSZ; - value = ExecEvalExpr(e, econtext, isNull, NULL); - if (!*isNull) - return value; + result = palloc(len); + VARATT_SIZEP(result) = len; + memcpy(VARDATA(result), buf.data, buf.len); } - /* Else return NULL */ - *isNull = true; - return (Datum) 0; + pfree(buf.data); + return PointerGetDatum(result); } /* ---------------------------------------------------------------- @@ -1827,33 +2984,36 @@ ExecEvalCoalesce(CoalesceExprState *coalesceExpr, ExprContext *econtext, * ---------------------------------------------------------------- */ static Datum -ExecEvalNullIf(FuncExprState *fcache, ExprContext *econtext, - bool *isNull) +ExecEvalNullIf(FuncExprState *nullIfExpr, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) { Datum result; FunctionCallInfoData fcinfo; ExprDoneCond argDone; List *argList; + if (isDone) + *isDone = ExprSingleResult; + /* * Initialize function cache if first time through */ - if (fcache->func.fn_oid == InvalidOid) + if (nullIfExpr->func.fn_oid == InvalidOid) { - NullIfExpr *op = (NullIfExpr *) fcache->xprstate.expr; + NullIfExpr *op = (NullIfExpr *) nullIfExpr->xprstate.expr; - init_fcache(op->opfuncid, fcache, econtext->ecxt_per_query_memory); - Assert(!fcache->func.fn_retset); + init_fcache(op->opfuncid, nullIfExpr, econtext->ecxt_per_query_memory); + Assert(!nullIfExpr->func.fn_retset); } /* - * extract info from fcache + * extract info from nullIfExpr */ - argList = fcache->args; + argList = nullIfExpr->args; /* Need to prep callinfo structure */ - MemSet(&fcinfo, 0, sizeof(fcinfo)); - fcinfo.flinfo = &(fcache->func); + InitFunctionCallInfoData(fcinfo, &(nullIfExpr->func), 0, NULL, NULL); argDone = ExecEvalFuncArgs(&fcinfo, argList, econtext); if (argDone != ExprSingleResult) ereport(ERROR, @@ -1886,7 +3046,7 @@ ExecEvalNullIf(FuncExprState *fcache, ExprContext *econtext, * ---------------------------------------------------------------- */ static Datum -ExecEvalNullTest(GenericExprState *nstate, +ExecEvalNullTest(NullTestState *nstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone) @@ -1899,28 +3059,77 @@ ExecEvalNullTest(GenericExprState *nstate, if (isDone && *isDone == ExprEndResult) return result; /* nothing to check */ - switch (ntest->nulltesttype) + if (nstate->argisrow && !(*isNull)) { - case IS_NULL: - if (*isNull) + HeapTupleHeader tuple; + Oid tupType; + int32 tupTypmod; + TupleDesc tupDesc; + HeapTupleData tmptup; + int att; + + tuple = DatumGetHeapTupleHeader(result); + + tupType = HeapTupleHeaderGetTypeId(tuple); + tupTypmod = HeapTupleHeaderGetTypMod(tuple); + + /* Lookup tupdesc if first time through or if type changes */ + tupDesc = get_cached_rowtype(tupType, tupTypmod, + &nstate->argdesc, econtext); + + /* + * heap_attisnull needs a HeapTuple not a bare HeapTupleHeader. + */ + tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); + tmptup.t_data = tuple; + + for (att = 1; att <= tupDesc->natts; att++) + { + /* ignore dropped columns */ + if (tupDesc->attrs[att - 1]->attisdropped) + continue; + if (heap_attisnull(&tmptup, att)) { - *isNull = false; - return BoolGetDatum(true); + /* null field disproves IS NOT NULL */ + if (ntest->nulltesttype == IS_NOT_NULL) + return BoolGetDatum(false); } else - return BoolGetDatum(false); - case IS_NOT_NULL: - if (*isNull) { - *isNull = false; - return BoolGetDatum(false); + /* non-null field disproves IS NULL */ + if (ntest->nulltesttype == IS_NULL) + return BoolGetDatum(false); } - else - return BoolGetDatum(true); - default: - elog(ERROR, "unrecognized nulltesttype: %d", - (int) ntest->nulltesttype); - return (Datum) 0; /* keep compiler quiet */ + } + + return BoolGetDatum(true); + } + else + { + /* Simple scalar-argument case, or a null rowtype datum */ + switch (ntest->nulltesttype) + { + case IS_NULL: + if (*isNull) + { + *isNull = false; + return BoolGetDatum(true); + } + else + return BoolGetDatum(false); + case IS_NOT_NULL: + if (*isNull) + { + *isNull = false; + return BoolGetDatum(false); + } + else + return BoolGetDatum(true); + default: + elog(ERROR, "unrecognized nulltesttype: %d", + (int) ntest->nulltesttype); + return (Datum) 0; /* keep compiler quiet */ + } } } @@ -2022,7 +3231,7 @@ ExecEvalCoerceToDomain(CoerceToDomainState *cstate, ExprContext *econtext, { CoerceToDomain *ctest = (CoerceToDomain *) cstate->xprstate.expr; Datum result; - List *l; + ListCell *l; result = ExecEvalExpr(cstate->arg, econtext, isNull, isDone); @@ -2039,8 +3248,8 @@ ExecEvalCoerceToDomain(CoerceToDomainState *cstate, ExprContext *econtext, if (*isNull) ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION), - errmsg("domain %s does not allow null values", - format_type_be(ctest->resulttype)))); + errmsg("domain %s does not allow null values", + format_type_be(ctest->resulttype)))); break; case DOM_CONSTRAINT_CHECK: { @@ -2053,8 +3262,7 @@ ExecEvalCoerceToDomain(CoerceToDomainState *cstate, ExprContext *econtext, * Set up value to be returned by CoerceToDomainValue * nodes. We must save and restore prior setting of * econtext's domainValue fields, in case this node is - * itself within a check expression for another - * domain. + * itself within a check expression for another domain. */ save_datum = econtext->domainValue_datum; save_isNull = econtext->domainValue_isNull; @@ -2094,9 +3302,12 @@ ExecEvalCoerceToDomain(CoerceToDomainState *cstate, ExprContext *econtext, * Return the value stored by CoerceToDomain. */ static Datum -ExecEvalCoerceToDomainValue(CoerceToDomainValue *conVal, - ExprContext *econtext, bool *isNull) +ExecEvalCoerceToDomainValue(ExprState *exprstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) { + if (isDone) + *isDone = ExprSingleResult; *isNull = econtext->domainValue_isNull; return econtext->domainValue_datum; } @@ -2108,235 +3319,193 @@ ExecEvalCoerceToDomainValue(CoerceToDomainValue *conVal, * ---------------------------------------------------------------- */ static Datum -ExecEvalFieldSelect(GenericExprState *fstate, +ExecEvalFieldSelect(FieldSelectState *fstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone) { FieldSelect *fselect = (FieldSelect *) fstate->xprstate.expr; + AttrNumber fieldnum = fselect->fieldnum; Datum result; - TupleTableSlot *resSlot; + Datum tupDatum; + HeapTupleHeader tuple; + Oid tupType; + int32 tupTypmod; + TupleDesc tupDesc; + Form_pg_attribute attr; + 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 = get_cached_rowtype(tupType, tupTypmod, + &fstate->argdesc, econtext); + + /* Check for dropped column, and force a NULL result if so */ + if (fieldnum <= 0 || + fieldnum > tupDesc->natts) /* should never happen */ + elog(ERROR, "attribute number %d exceeds number of columns %d", + fieldnum, tupDesc->natts); + attr = tupDesc->attrs[fieldnum - 1]; + if (attr->attisdropped) + { + *isNull = true; + return (Datum) 0; + } + + /* Check for type mismatch --- possible after ALTER COLUMN TYPE? */ + /* As in ExecEvalVar, we should but can't check typmod */ + if (fselect->resulttype != attr->atttypid) + ereport(ERROR, + (errmsg("attribute %d has wrong type", fieldnum), + errdetail("Table has type %s, but query expects %s.", + format_type_be(attr->atttypid), + format_type_be(fselect->resulttype)))); - resSlot = (TupleTableSlot *) DatumGetPointer(result); - Assert(resSlot != NULL && IsA(resSlot, TupleTableSlot)); - result = heap_getattr(resSlot->val, - fselect->fieldnum, - resSlot->ttc_tupleDescriptor, + /* + * 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, + fieldnum, + tupDesc, isNull); return result; } /* ---------------------------------------------------------------- - * ExecEvalExpr - * - * Recursively evaluate a targetlist or qualification expression. - * - * Inputs: - * expression: the expression state tree to evaluate - * econtext: evaluation context information - * - * Outputs: - * return value: Datum value of result - * *isNull: set to TRUE if result is NULL (actual return value is - * meaningless if so); set to FALSE if non-null result - * *isDone: set to indicator of set-result status - * - * A caller that can only accept a singleton (non-set) result should pass - * NULL for isDone; if the expression computes a set result then an error - * will be reported via ereport. If the caller does pass an isDone pointer - * then *isDone is set to one of these three states: - * ExprSingleResult singleton result (not a set) - * ExprMultipleResult return value is one element of a set - * ExprEndResult there are no more elements in the set - * When ExprMultipleResult is returned, the caller should invoke - * ExecEvalExpr() repeatedly until ExprEndResult is returned. ExprEndResult - * is returned after the last real set element. For convenience isNull will - * always be set TRUE when ExprEndResult is returned, but this should not be - * taken as indicating a NULL element of the set. Note that these return - * conventions allow us to distinguish among a singleton NULL, a NULL element - * of a set, and an empty set. - * - * The caller should already have switched into the temporary memory - * context econtext->ecxt_per_tuple_memory. The convenience entry point - * ExecEvalExprSwitchContext() is provided for callers who don't prefer to - * do the switch in an outer loop. We do not do the switch here because - * it'd be a waste of cycles during recursive entries to ExecEvalExpr(). + * ExecEvalFieldStore * - * This routine is an inner loop routine and must be as fast as possible. + * Evaluate a FieldStore node. * ---------------------------------------------------------------- */ -Datum -ExecEvalExpr(ExprState *expression, - ExprContext *econtext, - bool *isNull, - ExprDoneCond *isDone) +static Datum +ExecEvalFieldStore(FieldStoreState *fstate, + ExprContext *econtext, + bool *isNull, + ExprDoneCond *isDone) { - Datum retDatum; - Expr *expr; + FieldStore *fstore = (FieldStore *) fstate->xprstate.expr; + HeapTuple tuple; + Datum tupDatum; + TupleDesc tupDesc; + Datum *values; + bool *isnull; + Datum save_datum; + bool save_isNull; + ListCell *l1, + *l2; + + tupDatum = ExecEvalExpr(fstate->arg, econtext, isNull, isDone); + + if (isDone && *isDone == ExprEndResult) + return tupDatum; + + /* Lookup tupdesc if first time through or after rescan */ + tupDesc = get_cached_rowtype(fstore->resulttype, -1, + &fstate->argdesc, econtext); + + /* Allocate workspace */ + values = (Datum *) palloc(tupDesc->natts * sizeof(Datum)); + isnull = (bool *) palloc(tupDesc->natts * sizeof(bool)); - /* Set default values for result flags: non-null, not a set result */ - *isNull = false; - if (isDone) - *isDone = ExprSingleResult; + if (!*isNull) + { + /* + * heap_deform_tuple needs a HeapTuple not a bare HeapTupleHeader. We + * set all the fields in the struct just in case. + */ + HeapTupleHeader tuphdr; + HeapTupleData tmptup; + + tuphdr = DatumGetHeapTupleHeader(tupDatum); + tmptup.t_len = HeapTupleHeaderGetDatumLength(tuphdr); + ItemPointerSetInvalid(&(tmptup.t_self)); + tmptup.t_tableOid = InvalidOid; + tmptup.t_data = tuphdr; - /* Is this still necessary? Doubtful... */ - if (expression == NULL) + heap_deform_tuple(&tmptup, tupDesc, values, isnull); + } + else { - *isNull = true; - return (Datum) 0; + /* Convert null input tuple into an all-nulls row */ + memset(isnull, true, tupDesc->natts * sizeof(bool)); } - /* - * here we dispatch the work to the appropriate type of function given - * the type of our expression. - */ - expr = expression->expr; - switch (nodeTag(expr)) + /* Result is never null */ + *isNull = false; + + save_datum = econtext->caseValue_datum; + save_isNull = econtext->caseValue_isNull; + + forboth(l1, fstate->newvals, l2, fstore->fieldnums) { - case T_Var: - retDatum = ExecEvalVar((Var *) expr, econtext, isNull); - break; - case T_Const: - { - Const *con = (Const *) expr; + ExprState *newval = (ExprState *) lfirst(l1); + AttrNumber fieldnum = lfirst_int(l2); - retDatum = con->constvalue; - *isNull = con->constisnull; - break; - } - case T_Param: - retDatum = ExecEvalParam((Param *) expr, econtext, isNull); - break; - case T_Aggref: - retDatum = ExecEvalAggref((AggrefExprState *) expression, - econtext, - isNull); - break; - case T_ArrayRef: - retDatum = ExecEvalArrayRef((ArrayRefExprState *) expression, - econtext, - isNull, - isDone); - break; - case T_FuncExpr: - retDatum = ExecEvalFunc((FuncExprState *) expression, econtext, - isNull, isDone); - break; - case T_OpExpr: - retDatum = ExecEvalOper((FuncExprState *) expression, econtext, - isNull, isDone); - break; - case T_DistinctExpr: - retDatum = ExecEvalDistinct((FuncExprState *) expression, econtext, - isNull); - break; - case T_ScalarArrayOpExpr: - retDatum = ExecEvalScalarArrayOp((ScalarArrayOpExprState *) expression, - econtext, isNull); - break; - case T_BoolExpr: - { - BoolExprState *state = (BoolExprState *) expression; + Assert(fieldnum > 0 && fieldnum <= tupDesc->natts); - switch (((BoolExpr *) expr)->boolop) - { - case AND_EXPR: - retDatum = ExecEvalAnd(state, econtext, isNull); - break; - case OR_EXPR: - retDatum = ExecEvalOr(state, econtext, isNull); - break; - case NOT_EXPR: - retDatum = ExecEvalNot(state, econtext, isNull); - break; - default: - elog(ERROR, "unrecognized boolop: %d", - (int) ((BoolExpr *) expr)->boolop); - retDatum = 0; /* keep compiler quiet */ - break; - } - break; - } - case T_SubPlan: - retDatum = ExecSubPlan((SubPlanState *) expression, - econtext, - isNull); - break; - case T_FieldSelect: - retDatum = ExecEvalFieldSelect((GenericExprState *) expression, - econtext, - isNull, - isDone); - break; - case T_RelabelType: - retDatum = ExecEvalExpr(((GenericExprState *) expression)->arg, - econtext, - isNull, - isDone); - break; - case T_CaseExpr: - retDatum = ExecEvalCase((CaseExprState *) expression, - econtext, - isNull, - isDone); - break; - case T_ArrayExpr: - retDatum = ExecEvalArray((ArrayExprState *) expression, - econtext, - isNull); - break; - case T_CoalesceExpr: - retDatum = ExecEvalCoalesce((CoalesceExprState *) expression, - econtext, - isNull); - break; - case T_NullIfExpr: - retDatum = ExecEvalNullIf((FuncExprState *) expression, - econtext, - isNull); - break; - case T_NullTest: - retDatum = ExecEvalNullTest((GenericExprState *) expression, - econtext, - isNull, - isDone); - break; - case T_BooleanTest: - retDatum = ExecEvalBooleanTest((GenericExprState *) expression, - econtext, - isNull, - isDone); - break; - case T_CoerceToDomain: - retDatum = ExecEvalCoerceToDomain((CoerceToDomainState *) expression, - econtext, - isNull, - isDone); - break; - case T_CoerceToDomainValue: - retDatum = ExecEvalCoerceToDomainValue((CoerceToDomainValue *) expr, - econtext, - isNull); - break; - default: - elog(ERROR, "unrecognized node type: %d", - (int) nodeTag(expression)); - retDatum = 0; /* keep compiler quiet */ - break; + /* + * Use the CaseTestExpr mechanism to pass down the old value of the + * field being replaced; this is useful in case we have a nested field + * update situation. It's safe to reuse the CASE mechanism because + * there cannot be a CASE between here and where the value would be + * needed. + */ + econtext->caseValue_datum = values[fieldnum - 1]; + econtext->caseValue_isNull = isnull[fieldnum - 1]; + + values[fieldnum - 1] = ExecEvalExpr(newval, + econtext, + &isnull[fieldnum - 1], + NULL); } - return retDatum; -} /* ExecEvalExpr() */ + econtext->caseValue_datum = save_datum; + econtext->caseValue_isNull = save_isNull; + + tuple = heap_form_tuple(tupDesc, values, isnull); + + pfree(values); + pfree(isnull); + + return HeapTupleGetDatum(tuple); +} + +/* ---------------------------------------------------------------- + * ExecEvalRelabelType + * + * Evaluate a RelabelType node. + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalRelabelType(GenericExprState *exprstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + return ExecEvalExpr(exprstate->arg, econtext, isNull, isDone); +} /* - * Same as above, but get into the right allocation context explicitly. + * ExecEvalExprSwitchContext + * + * Same as ExecEvalExpr, but get into the right allocation context explicitly. */ Datum ExecEvalExprSwitchContext(ExprState *expression, @@ -2393,20 +3562,38 @@ ExecInitExpr(Expr *node, PlanState *parent) if (node == NULL) return NULL; + + /* Guard against stack overflow due to overly complex expressions */ + check_stack_depth(); + switch (nodeTag(node)) { case T_Var: + state = (ExprState *) makeNode(ExprState); + state->evalfunc = ExecEvalVar; + break; case T_Const: + state = (ExprState *) makeNode(ExprState); + state->evalfunc = ExecEvalConst; + break; case T_Param: + state = (ExprState *) makeNode(ExprState); + state->evalfunc = ExecEvalParam; + break; case T_CoerceToDomainValue: - /* No special setup needed for these node types */ state = (ExprState *) makeNode(ExprState); + state->evalfunc = ExecEvalCoerceToDomainValue; + break; + case T_CaseTestExpr: + state = (ExprState *) makeNode(ExprState); + state->evalfunc = ExecEvalCaseTestExpr; break; case T_Aggref: { Aggref *aggref = (Aggref *) node; AggrefExprState *astate = makeNode(AggrefExprState); + astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalAggref; if (parent && IsA(parent, AggState)) { AggState *aggstate = (AggState *) parent; @@ -2415,18 +3602,19 @@ ExecInitExpr(Expr *node, PlanState *parent) aggstate->aggs = lcons(astate, aggstate->aggs); naggs = ++aggstate->numaggs; - astate->target = ExecInitExpr(aggref->target, parent); + astate->args = (List *) ExecInitExpr((Expr *) aggref->args, + parent); /* - * Complain if the aggregate's argument contains any + * Complain if the aggregate's arguments contain any * aggregates; nested agg functions are semantically - * nonsensical. (This should have been caught - * earlier, but we defend against it here anyway.) + * nonsensical. (This should have been caught earlier, + * but we defend against it here anyway.) */ if (naggs != aggstate->numaggs) ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR), - errmsg("aggregate function calls may not be nested"))); + errmsg("aggregate function calls cannot be nested"))); } else { @@ -2441,6 +3629,7 @@ ExecInitExpr(Expr *node, PlanState *parent) ArrayRef *aref = (ArrayRef *) node; ArrayRefExprState *astate = makeNode(ArrayRefExprState); + astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArrayRef; astate->refupperindexpr = (List *) ExecInitExpr((Expr *) aref->refupperindexpr, parent); astate->reflowerindexpr = (List *) @@ -2462,6 +3651,7 @@ ExecInitExpr(Expr *node, PlanState *parent) FuncExpr *funcexpr = (FuncExpr *) node; FuncExprState *fstate = makeNode(FuncExprState); + fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFunc; fstate->args = (List *) ExecInitExpr((Expr *) funcexpr->args, parent); fstate->func.fn_oid = InvalidOid; /* not initialized */ @@ -2473,6 +3663,7 @@ ExecInitExpr(Expr *node, PlanState *parent) OpExpr *opexpr = (OpExpr *) node; FuncExprState *fstate = makeNode(FuncExprState); + fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalOper; fstate->args = (List *) ExecInitExpr((Expr *) opexpr->args, parent); fstate->func.fn_oid = InvalidOid; /* not initialized */ @@ -2484,6 +3675,7 @@ ExecInitExpr(Expr *node, PlanState *parent) DistinctExpr *distinctexpr = (DistinctExpr *) node; FuncExprState *fstate = makeNode(FuncExprState); + fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalDistinct; fstate->args = (List *) ExecInitExpr((Expr *) distinctexpr->args, parent); fstate->func.fn_oid = InvalidOid; /* not initialized */ @@ -2495,6 +3687,7 @@ ExecInitExpr(Expr *node, PlanState *parent) ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node; ScalarArrayOpExprState *sstate = makeNode(ScalarArrayOpExprState); + sstate->fxprstate.xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalScalarArrayOp; sstate->fxprstate.args = (List *) ExecInitExpr((Expr *) opexpr->args, parent); sstate->fxprstate.func.fn_oid = InvalidOid; /* not initialized */ @@ -2507,6 +3700,22 @@ ExecInitExpr(Expr *node, PlanState *parent) BoolExpr *boolexpr = (BoolExpr *) node; BoolExprState *bstate = makeNode(BoolExprState); + switch (boolexpr->boolop) + { + case AND_EXPR: + bstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalAnd; + break; + case OR_EXPR: + bstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalOr; + break; + case NOT_EXPR: + bstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNot; + break; + default: + elog(ERROR, "unrecognized boolop: %d", + (int) boolexpr->boolop); + break; + } bstate->args = (List *) ExecInitExpr((Expr *) boolexpr->args, parent); state = (ExprState *) bstate; @@ -2514,26 +3723,16 @@ ExecInitExpr(Expr *node, PlanState *parent) break; case T_SubPlan: { - /* Keep this in sync with ExecInitExprInitPlan, below */ SubPlan *subplan = (SubPlan *) node; - SubPlanState *sstate = makeNode(SubPlanState); + SubPlanState *sstate; if (!parent) elog(ERROR, "SubPlan found with no parent plan"); - /* - * Here we just add the SubPlanState nodes to - * parent->subPlan. The subplans will be initialized - * later. - */ - parent->subPlan = lcons(sstate, parent->subPlan); - sstate->sub_estate = NULL; - sstate->planstate = NULL; + sstate = ExecInitSubPlan(subplan, parent); - sstate->exprs = (List *) - ExecInitExpr((Expr *) subplan->exprs, parent); - sstate->args = (List *) - ExecInitExpr((Expr *) subplan->args, parent); + /* Add SubPlanState nodes to parent->subPlan */ + parent->subPlan = lcons(sstate, parent->subPlan); state = (ExprState *) sstate; } @@ -2541,10 +3740,24 @@ ExecInitExpr(Expr *node, PlanState *parent) case T_FieldSelect: { FieldSelect *fselect = (FieldSelect *) node; - GenericExprState *gstate = makeNode(GenericExprState); + FieldSelectState *fstate = makeNode(FieldSelectState); - 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_FieldStore: + { + FieldStore *fstore = (FieldStore *) node; + FieldStoreState *fstate = makeNode(FieldStoreState); + + fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFieldStore; + fstate->arg = ExecInitExpr(fstore->arg, parent); + fstate->newvals = (List *) ExecInitExpr((Expr *) fstore->newvals, parent); + fstate->argdesc = NULL; + state = (ExprState *) fstate; } break; case T_RelabelType: @@ -2552,32 +3765,43 @@ ExecInitExpr(Expr *node, PlanState *parent) RelabelType *relabel = (RelabelType *) node; GenericExprState *gstate = makeNode(GenericExprState); + gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRelabelType; gstate->arg = ExecInitExpr(relabel->arg, parent); state = (ExprState *) gstate; } break; + case T_ConvertRowtypeExpr: + { + ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node; + ConvertRowtypeExprState *cstate = makeNode(ConvertRowtypeExprState); + + cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalConvertRowtype; + cstate->arg = ExecInitExpr(convert->arg, parent); + state = (ExprState *) cstate; + } + break; case T_CaseExpr: { CaseExpr *caseexpr = (CaseExpr *) node; CaseExprState *cstate = makeNode(CaseExprState); - FastList outlist; - List *inlist; + List *outlist = NIL; + ListCell *l; - FastListInit(&outlist); - foreach(inlist, caseexpr->args) + cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCase; + cstate->arg = ExecInitExpr(caseexpr->arg, parent); + foreach(l, caseexpr->args) { - CaseWhen *when = (CaseWhen *) lfirst(inlist); + CaseWhen *when = (CaseWhen *) lfirst(l); CaseWhenState *wstate = makeNode(CaseWhenState); Assert(IsA(when, CaseWhen)); + wstate->xprstate.evalfunc = NULL; /* not used */ wstate->xprstate.expr = (Expr *) when; wstate->expr = ExecInitExpr(when->expr, parent); wstate->result = ExecInitExpr(when->result, parent); - FastAppend(&outlist, wstate); + outlist = lappend(outlist, wstate); } - cstate->args = FastListValue(&outlist); - /* caseexpr->arg should be null by now */ - Assert(caseexpr->arg == NULL); + cstate->args = outlist; cstate->defresult = ExecInitExpr(caseexpr->defresult, parent); state = (ExprState *) cstate; } @@ -2586,19 +3810,19 @@ ExecInitExpr(Expr *node, PlanState *parent) { ArrayExpr *arrayexpr = (ArrayExpr *) node; ArrayExprState *astate = makeNode(ArrayExprState); - FastList outlist; - List *inlist; + List *outlist = NIL; + ListCell *l; - FastListInit(&outlist); - foreach(inlist, arrayexpr->elements) + astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArray; + foreach(l, arrayexpr->elements) { - Expr *e = (Expr *) lfirst(inlist); + Expr *e = (Expr *) lfirst(l); ExprState *estate; estate = ExecInitExpr(e, parent); - FastAppend(&outlist, estate); + outlist = lappend(outlist, estate); } - astate->elements = FastListValue(&outlist); + astate->elements = outlist; /* do one-time catalog lookup for type info */ get_typlenbyvalalign(arrayexpr->element_typeid, &astate->elemlength, @@ -2607,31 +3831,245 @@ ExecInitExpr(Expr *node, PlanState *parent) state = (ExprState *) astate; } break; + case T_RowExpr: + { + RowExpr *rowexpr = (RowExpr *) node; + RowExprState *rstate = makeNode(RowExprState); + Form_pg_attribute *attrs; + List *outlist = NIL; + ListCell *l; + int i; + + rstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRow; + /* Build tupdesc to describe result tuples */ + if (rowexpr->row_typeid == RECORDOID) + { + /* generic record, use runtime type assignment */ + rstate->tupdesc = ExecTypeFromExprList(rowexpr->args); + BlessTupleDesc(rstate->tupdesc); + /* we won't need to redo this at runtime */ + } + else + { + /* it's been cast to a named type, use that */ + rstate->tupdesc = lookup_rowtype_tupdesc_copy(rowexpr->row_typeid, -1); + } + /* Set up evaluation, skipping any deleted columns */ + Assert(list_length(rowexpr->args) <= rstate->tupdesc->natts); + attrs = rstate->tupdesc->attrs; + i = 0; + foreach(l, rowexpr->args) + { + Expr *e = (Expr *) lfirst(l); + ExprState *estate; + + if (!attrs[i]->attisdropped) + { + /* + * Guard against ALTER COLUMN TYPE on rowtype since + * the RowExpr was created. XXX should we check + * typmod too? Not sure we can be sure it'll be the + * same. + */ + if (exprType((Node *) e) != attrs[i]->atttypid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("ROW() column has type %s instead of type %s", + format_type_be(exprType((Node *) e)), + format_type_be(attrs[i]->atttypid)))); + } + else + { + /* + * Ignore original expression and insert a NULL. We + * don't really care what type of NULL it is, so + * always make an int4 NULL. + */ + e = (Expr *) makeNullConst(INT4OID); + } + estate = ExecInitExpr(e, parent); + outlist = lappend(outlist, estate); + i++; + } + rstate->args = outlist; + state = (ExprState *) rstate; + } + break; + case T_RowCompareExpr: + { + RowCompareExpr *rcexpr = (RowCompareExpr *) node; + RowCompareExprState *rstate = makeNode(RowCompareExprState); + int nopers = list_length(rcexpr->opnos); + List *outlist; + ListCell *l; + ListCell *l2; + int i; + + rstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRowCompare; + Assert(list_length(rcexpr->largs) == nopers); + outlist = NIL; + foreach(l, rcexpr->largs) + { + Expr *e = (Expr *) lfirst(l); + ExprState *estate; + + estate = ExecInitExpr(e, parent); + outlist = lappend(outlist, estate); + } + rstate->largs = outlist; + Assert(list_length(rcexpr->rargs) == nopers); + outlist = NIL; + foreach(l, rcexpr->rargs) + { + Expr *e = (Expr *) lfirst(l); + ExprState *estate; + + estate = ExecInitExpr(e, parent); + outlist = lappend(outlist, estate); + } + rstate->rargs = outlist; + Assert(list_length(rcexpr->opfamilies) == nopers); + rstate->funcs = (FmgrInfo *) palloc(nopers * sizeof(FmgrInfo)); + i = 0; + forboth(l, rcexpr->opnos, l2, rcexpr->opfamilies) + { + Oid opno = lfirst_oid(l); + Oid opfamily = lfirst_oid(l2); + int strategy; + Oid lefttype; + Oid righttype; + bool recheck; + Oid proc; + + get_op_opfamily_properties(opno, opfamily, + &strategy, + &lefttype, + &righttype, + &recheck); + proc = get_opfamily_proc(opfamily, + lefttype, + righttype, + BTORDER_PROC); + + /* + * If we enforced permissions checks on index support + * functions, we'd need to make a check here. But the + * index support machinery doesn't do that, and neither + * does this code. + */ + fmgr_info(proc, &(rstate->funcs[i])); + i++; + } + state = (ExprState *) rstate; + } + break; case T_CoalesceExpr: { CoalesceExpr *coalesceexpr = (CoalesceExpr *) node; CoalesceExprState *cstate = makeNode(CoalesceExprState); - FastList outlist; - List *inlist; + List *outlist = NIL; + ListCell *l; - FastListInit(&outlist); - foreach(inlist, coalesceexpr->args) + cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoalesce; + foreach(l, coalesceexpr->args) { - Expr *e = (Expr *) lfirst(inlist); + Expr *e = (Expr *) lfirst(l); ExprState *estate; estate = ExecInitExpr(e, parent); - FastAppend(&outlist, estate); + outlist = lappend(outlist, estate); } - cstate->args = FastListValue(&outlist); + cstate->args = outlist; state = (ExprState *) cstate; } break; + case T_MinMaxExpr: + { + MinMaxExpr *minmaxexpr = (MinMaxExpr *) node; + MinMaxExprState *mstate = makeNode(MinMaxExprState); + List *outlist = NIL; + ListCell *l; + TypeCacheEntry *typentry; + + mstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalMinMax; + foreach(l, minmaxexpr->args) + { + Expr *e = (Expr *) lfirst(l); + ExprState *estate; + + estate = ExecInitExpr(e, parent); + outlist = lappend(outlist, estate); + } + mstate->args = outlist; + /* Look up the btree comparison function for the datatype */ + typentry = lookup_type_cache(minmaxexpr->minmaxtype, + TYPECACHE_CMP_PROC); + if (!OidIsValid(typentry->cmp_proc)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify a comparison function for type %s", + format_type_be(minmaxexpr->minmaxtype)))); + + /* + * If we enforced permissions checks on index support + * functions, we'd need to make a check here. But the index + * support machinery doesn't do that, and neither does this + * code. + */ + fmgr_info(typentry->cmp_proc, &(mstate->cfunc)); + state = (ExprState *) mstate; + } + break; + case T_XmlExpr: + { + XmlExpr *xexpr = (XmlExpr *) node; + XmlExprState *xstate = makeNode(XmlExprState); + List *outlist; + ListCell *arg; + int i; + + xstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalXml; + xstate->named_outfuncs = (FmgrInfo *) + palloc0(list_length(xexpr->named_args) * sizeof(FmgrInfo)); + outlist = NIL; + i = 0; + foreach(arg, xexpr->named_args) + { + Expr *e = (Expr *) lfirst(arg); + ExprState *estate; + Oid typOutFunc; + bool typIsVarlena; + + estate = ExecInitExpr(e, parent); + outlist = lappend(outlist, estate); + + getTypeOutputInfo(exprType((Node *) e), + &typOutFunc, &typIsVarlena); + fmgr_info(typOutFunc, &xstate->named_outfuncs[i]); + i++; + } + xstate->named_args = outlist; + + outlist = NIL; + foreach(arg, xexpr->args) + { + Expr *e = (Expr *) lfirst(arg); + ExprState *estate; + + estate = ExecInitExpr(e, parent); + outlist = lappend(outlist, estate); + } + xstate->args = outlist; + + state = (ExprState *) xstate; + } + break; case T_NullIfExpr: { NullIfExpr *nullifexpr = (NullIfExpr *) node; FuncExprState *fstate = makeNode(FuncExprState); + fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullIf; fstate->args = (List *) ExecInitExpr((Expr *) nullifexpr->args, parent); fstate->func.fn_oid = InvalidOid; /* not initialized */ @@ -2641,10 +4079,13 @@ ExecInitExpr(Expr *node, PlanState *parent) case T_NullTest: { NullTest *ntest = (NullTest *) node; - GenericExprState *gstate = makeNode(GenericExprState); + NullTestState *nstate = makeNode(NullTestState); - gstate->arg = ExecInitExpr(ntest->arg, parent); - state = (ExprState *) gstate; + nstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullTest; + nstate->arg = ExecInitExpr(ntest->arg, parent); + nstate->argisrow = type_is_rowtype(exprType((Node *) ntest->arg)); + nstate->argdesc = NULL; + state = (ExprState *) nstate; } break; case T_BooleanTest: @@ -2652,6 +4093,7 @@ ExecInitExpr(Expr *node, PlanState *parent) BooleanTest *btest = (BooleanTest *) node; GenericExprState *gstate = makeNode(GenericExprState); + gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalBooleanTest; gstate->arg = ExecInitExpr(btest->arg, parent); state = (ExprState *) gstate; } @@ -2661,6 +4103,7 @@ ExecInitExpr(Expr *node, PlanState *parent) CoerceToDomain *ctest = (CoerceToDomain *) node; CoerceToDomainState *cstate = makeNode(CoerceToDomainState); + cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoerceToDomain; cstate->arg = ExecInitExpr(ctest->arg, parent); cstate->constraints = GetDomainConstraints(ctest->resulttype); state = (ExprState *) cstate; @@ -2671,24 +4114,24 @@ ExecInitExpr(Expr *node, PlanState *parent) TargetEntry *tle = (TargetEntry *) node; GenericExprState *gstate = makeNode(GenericExprState); + gstate->xprstate.evalfunc = NULL; /* not used */ gstate->arg = ExecInitExpr(tle->expr, parent); state = (ExprState *) gstate; } break; case T_List: { - FastList outlist; - List *inlist; + List *outlist = NIL; + ListCell *l; - FastListInit(&outlist); - foreach(inlist, (List *) node) + foreach(l, (List *) node) { - FastAppend(&outlist, - ExecInitExpr((Expr *) lfirst(inlist), - parent)); + outlist = lappend(outlist, + ExecInitExpr((Expr *) lfirst(l), + parent)); } /* Don't fall through to the "common" code below */ - return (ExprState *) FastListValue(&outlist); + return (ExprState *) outlist; } default: elog(ERROR, "unrecognized node type: %d", @@ -2703,32 +4146,6 @@ ExecInitExpr(Expr *node, PlanState *parent) return state; } -/* - * ExecInitExprInitPlan --- initialize a subplan expr that's being handled - * as an InitPlan. This is identical to ExecInitExpr's handling of a regular - * subplan expr, except we do NOT want to add the node to the parent's - * subplan list. - */ -SubPlanState * -ExecInitExprInitPlan(SubPlan *node, PlanState *parent) -{ - SubPlanState *sstate = makeNode(SubPlanState); - - if (!parent) - elog(ERROR, "SubPlan found with no parent plan"); - - /* The subplan's state will be initialized later */ - sstate->sub_estate = NULL; - sstate->planstate = NULL; - - sstate->exprs = (List *) ExecInitExpr((Expr *) node->exprs, parent); - sstate->args = (List *) ExecInitExpr((Expr *) node->args, parent); - - sstate->xprstate.expr = (Expr *) node; - - return sstate; -} - /* * ExecPrepareExpr --- initialize for expression execution outside a normal * Plan tree context. @@ -2795,7 +4212,7 @@ ExecQual(List *qual, ExprContext *econtext, bool resultForNull) { bool result; MemoryContext oldContext; - List *qlist; + ListCell *l; /* * debugging stuff @@ -2812,22 +4229,22 @@ ExecQual(List *qual, ExprContext *econtext, bool resultForNull) oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); /* - * Evaluate the qual conditions one at a time. If we find a FALSE - * result, we can stop evaluating and return FALSE --- the AND result - * must be FALSE. Also, if we find a NULL result when resultForNull - * is FALSE, we can stop and return FALSE --- the AND result must be - * FALSE or NULL in that case, and the caller doesn't care which. + * Evaluate the qual conditions one at a time. If we find a FALSE result, + * we can stop evaluating and return FALSE --- the AND result must be + * FALSE. Also, if we find a NULL result when resultForNull is FALSE, we + * can stop and return FALSE --- the AND result must be FALSE or NULL in + * that case, and the caller doesn't care which. * - * If we get to the end of the list, we can return TRUE. This will - * happen when the AND result is indeed TRUE, or when the AND result - * is NULL (one or more NULL subresult, with all the rest TRUE) and - * the caller has specified resultForNull = TRUE. + * If we get to the end of the list, we can return TRUE. This will happen + * when the AND result is indeed TRUE, or when the AND result is NULL (one + * or more NULL subresult, with all the rest TRUE) and the caller has + * specified resultForNull = TRUE. */ result = true; - foreach(qlist, qual) + foreach(l, qual) { - ExprState *clause = (ExprState *) lfirst(qlist); + ExprState *clause = (ExprState *) lfirst(l); Datum expr_value; bool isNull; @@ -2863,7 +4280,7 @@ int ExecTargetListLength(List *targetlist) { /* This used to be more complex, but fjoins are dead */ - return length(targetlist); + return list_length(targetlist); } /* @@ -2873,75 +4290,50 @@ int ExecCleanTargetListLength(List *targetlist) { int len = 0; - List *tl; + ListCell *tl; foreach(tl, targetlist) { TargetEntry *curTle = (TargetEntry *) lfirst(tl); Assert(IsA(curTle, TargetEntry)); - if (!curTle->resdom->resjunk) + if (!curTle->resjunk) len++; } return len; } -/* ---------------------------------------------------------------- - * ExecTargetList - * +/* + * ExecTargetList * Evaluates a targetlist with respect to the given - * expression context and returns a tuple. + * expression context. Returns TRUE if we were able to create + * a result, FALSE if we have exhausted a set-valued expression. * - * The caller must pass workspace for the values and nulls arrays - * as well as the itemIsDone array. This convention saves palloc'ing - * workspace on each call, and some callers may find it useful to examine - * the values array directly. + * Results are stored into the passed values and isnull arrays. + * The caller must provide an itemIsDone array that persists across calls. * * As with ExecEvalExpr, the caller should pass isDone = NULL if not * prepared to deal with sets of result tuples. Otherwise, a return * of *isDone = ExprMultipleResult signifies a set element, and a return * of *isDone = ExprEndResult signifies end of the set of tuple. - * ---------------------------------------------------------------- */ -static HeapTuple +static bool ExecTargetList(List *targetlist, - TupleDesc targettype, ExprContext *econtext, Datum *values, - char *nulls, + bool *isnull, ExprDoneCond *itemIsDone, ExprDoneCond *isDone) { MemoryContext oldContext; - List *tl; - bool isNull; + ListCell *tl; bool haveDoneSets; - static struct tupleDesc NullTupleDesc; /* we assume this inits to - * zeroes */ - - /* - * debugging stuff - */ - EV_printf("ExecTargetList: tl is "); - EV_nodeDisplay(targetlist); - EV_printf("\n"); /* * Run in short-lived per-tuple context while computing expressions. */ oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); - /* - * 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. - */ - if (targettype == NULL) - targettype = &NullTupleDesc; - /* * evaluate all the expressions in the target list */ @@ -2954,13 +4346,12 @@ ExecTargetList(List *targetlist, { GenericExprState *gstate = (GenericExprState *) lfirst(tl); TargetEntry *tle = (TargetEntry *) gstate->xprstate.expr; - AttrNumber resind = tle->resdom->resno - 1; + AttrNumber resind = tle->resno - 1; values[resind] = ExecEvalExpr(gstate->arg, econtext, - &isNull, + &isnull[resind], &itemIsDone[resind]); - nulls[resind] = isNull ? 'n' : ' '; if (itemIsDone[resind] != ExprSingleResult) { @@ -2990,38 +4381,36 @@ ExecTargetList(List *targetlist, if (*isDone == ExprSingleResult) { /* - * all sets are done, so report that tlist expansion is - * complete. + * all sets are done, so report that tlist expansion is complete. */ *isDone = ExprEndResult; MemoryContextSwitchTo(oldContext); - return NULL; + return false; } else { /* - * We have some done and some undone sets. Restart the done - * ones so that we can deliver a tuple (if possible). + * We have some done and some undone sets. Restart the done ones + * so that we can deliver a tuple (if possible). */ foreach(tl, targetlist) { GenericExprState *gstate = (GenericExprState *) lfirst(tl); TargetEntry *tle = (TargetEntry *) gstate->xprstate.expr; - AttrNumber resind = tle->resdom->resno - 1; + AttrNumber resind = tle->resno - 1; if (itemIsDone[resind] == ExprEndResult) { values[resind] = ExecEvalExpr(gstate->arg, econtext, - &isNull, + &isnull[resind], &itemIsDone[resind]); - nulls[resind] = isNull ? 'n' : ' '; if (itemIsDone[resind] == ExprEndResult) { /* - * Oh dear, this item is returning an empty set. - * Guess we can't make a tuple after all. + * Oh dear, this item is returning an empty set. Guess + * we can't make a tuple after all. */ *isDone = ExprEndResult; break; @@ -3030,9 +4419,9 @@ ExecTargetList(List *targetlist, } /* - * If we cannot make a tuple because some sets are empty, we - * still have to cycle the nonempty sets to completion, else - * resources will not be released from subplans etc. + * If we cannot make a tuple because some sets are empty, we still + * have to cycle the nonempty sets to completion, else resources + * will not be released from subplans etc. * * XXX is that still necessary? */ @@ -3042,79 +4431,132 @@ ExecTargetList(List *targetlist, { GenericExprState *gstate = (GenericExprState *) lfirst(tl); TargetEntry *tle = (TargetEntry *) gstate->xprstate.expr; - AttrNumber resind = tle->resdom->resno - 1; + AttrNumber resind = tle->resno - 1; while (itemIsDone[resind] == ExprMultipleResult) { - (void) ExecEvalExpr(gstate->arg, - econtext, - &isNull, - &itemIsDone[resind]); + values[resind] = ExecEvalExpr(gstate->arg, + econtext, + &isnull[resind], + &itemIsDone[resind]); } } MemoryContextSwitchTo(oldContext); - return NULL; + return false; } } } + /* Report success */ + MemoryContextSwitchTo(oldContext); + + return true; +} + +/* + * ExecVariableList + * Evaluates a simple-Variable-list projection. + * + * Results are stored into the passed values and isnull arrays. + */ +static void +ExecVariableList(ProjectionInfo *projInfo, + Datum *values, + bool *isnull) +{ + ExprContext *econtext = projInfo->pi_exprContext; + int *varSlotOffsets = projInfo->pi_varSlotOffsets; + int *varNumbers = projInfo->pi_varNumbers; + int i; + /* - * form the new result tuple (in the caller's memory context!) + * Force extraction of all input values that we need. */ - MemoryContextSwitchTo(oldContext); + if (projInfo->pi_lastInnerVar > 0) + slot_getsomeattrs(econtext->ecxt_innertuple, + projInfo->pi_lastInnerVar); + if (projInfo->pi_lastOuterVar > 0) + slot_getsomeattrs(econtext->ecxt_outertuple, + projInfo->pi_lastOuterVar); + if (projInfo->pi_lastScanVar > 0) + slot_getsomeattrs(econtext->ecxt_scantuple, + projInfo->pi_lastScanVar); + + /* + * Assign to result by direct extraction of fields from source slots ... a + * mite ugly, but fast ... + */ + for (i = list_length(projInfo->pi_targetlist) - 1; i >= 0; i--) + { + char *slotptr = ((char *) econtext) + varSlotOffsets[i]; + TupleTableSlot *varSlot = *((TupleTableSlot **) slotptr); + int varNumber = varNumbers[i] - 1; - return heap_formtuple(targettype, values, nulls); + values[i] = varSlot->tts_values[varNumber]; + isnull[i] = varSlot->tts_isnull[varNumber]; + } } -/* ---------------------------------------------------------------- - * ExecProject +/* + * ExecProject * * projects a tuple based on projection info and stores - * it in the specified tuple table slot. + * it in the previously specified tuple table slot. * - * Note: someday soon the executor can be extended to eliminate - * redundant projections by storing pointers to datums - * in the tuple table and then passing these around when - * possible. this should make things much quicker. - * -cim 6/3/91 - * ---------------------------------------------------------------- + * Note: the result is always a virtual tuple; therefore it + * may reference the contents of the exprContext's scan tuples + * and/or temporary results constructed in the exprContext. + * If the caller wishes the result to be valid longer than that + * data will be valid, he must call ExecMaterializeSlot on the + * result slot. */ TupleTableSlot * ExecProject(ProjectionInfo *projInfo, ExprDoneCond *isDone) { TupleTableSlot *slot; - TupleDesc tupType; - HeapTuple newTuple; /* * sanity checks */ - if (projInfo == NULL) - return (TupleTableSlot *) NULL; + Assert(projInfo != NULL); /* * get the projection info we want */ slot = projInfo->pi_slot; - tupType = slot->ttc_tupleDescriptor; /* - * form a new result tuple (if possible --- result can be NULL) + * Clear any former contents of the result slot. This makes it safe for + * us to use the slot's Datum/isnull arrays as workspace. (Also, we can + * return the slot as-is if we decide no rows can be projected.) */ - newTuple = ExecTargetList(projInfo->pi_targetlist, - tupType, - projInfo->pi_exprContext, - projInfo->pi_tupValues, - projInfo->pi_tupNulls, - projInfo->pi_itemIsDone, - isDone); + ExecClearTuple(slot); /* - * store the tuple in the projection slot and return the slot. + * form a new result tuple (if possible); if successful, mark the result + * slot as containing a valid virtual tuple */ - return ExecStoreTuple(newTuple, /* tuple to store */ - slot, /* slot to store in */ - InvalidBuffer, /* tuple has no buffer */ - true); + if (projInfo->pi_isVarList) + { + /* simple Var list: this always succeeds with one result row */ + if (isDone) + *isDone = ExprSingleResult; + ExecVariableList(projInfo, + slot->tts_values, + slot->tts_isnull); + ExecStoreVirtualTuple(slot); + } + else + { + if (ExecTargetList(projInfo->pi_targetlist, + projInfo->pi_exprContext, + slot->tts_values, + slot->tts_isnull, + projInfo->pi_itemIsDone, + isDone)) + ExecStoreVirtualTuple(slot); + } + + return slot; }