* execQual.c
* Routines to evaluate qualification and targetlist expressions
*
- * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2009, 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.107 2002/09/02 01:05:05 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.247 2009/06/04 18:33:07 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 (and substitute routines) 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 "utils/array.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
+#include "pgstat.h"
+#include "utils/acl.h"
#include "utils/builtins.h"
-#include "utils/fcache.h"
#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/typcache.h"
+#include "utils/xml.h"
/* static function decls */
-static Datum ExecEvalAggref(Aggref *aggref, ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalArrayRef(ArrayRef *arrayRef, ExprContext *econtext,
+static Datum ExecEvalArrayRef(ArrayRefExprState *astate,
+ ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
-static Datum ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull);
-static Datum ExecEvalOper(Expr *opClause, ExprContext *econtext,
- bool *isNull, ExprDoneCond *isDone);
-static Datum ExecEvalDistinct(Expr *opClause, ExprContext *econtext,
- bool *isNull, ExprDoneCond *isDone);
-static Datum ExecEvalFunc(Expr *funcClause, ExprContext *econtext,
- bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalAggref(AggrefExprState *aggref,
+ ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalWindowFunc(WindowFuncExprState *wfunc,
+ 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 ExecEvalWholeRowSlow(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 init_fcache(Oid foid, FuncExprState *fcache,
+ MemoryContext fcacheCxt, bool needDescForSets);
+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 ExecEvalNot(Expr *notclause, ExprContext *econtext, bool *isNull);
-static Datum ExecEvalAnd(Expr *andExpr, ExprContext *econtext, bool *isNull);
-static Datum ExecEvalOr(Expr *orExpr, ExprContext *econtext, bool *isNull);
-static Datum ExecEvalCase(CaseExpr *caseExpr, ExprContext *econtext,
+static void ExecPrepareTuplestoreResult(FuncExprState *fcache,
+ ExprContext *econtext,
+ Tuplestorestate *resultStore,
+ TupleDesc resultDesc);
+static void tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc);
+static Datum ExecMakeFunctionResult(FuncExprState *fcache,
+ ExprContext *econtext,
+ bool *isNull,
+ ExprDoneCond *isDone);
+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, ExprDoneCond *isDone);
+static Datum ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate,
+ ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalNot(BoolExprState *notclause, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalOr(BoolExprState *orExpr, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalAnd(BoolExprState *andExpr, ExprContext *econtext,
+ 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 ExecEvalNullTest(NullTest *ntest, ExprContext *econtext,
+static Datum ExecEvalCaseTestExpr(ExprState *exprstate,
+ ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalArray(ArrayExprState *astate,
+ ExprContext *econtext,
+ 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, 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(BooleanTest *btest, ExprContext *econtext,
+static Datum ExecEvalBooleanTest(GenericExprState *bstate,
+ ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalCoerceToDomain(CoerceToDomainState *cstate,
+ ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalCoerceToDomainValue(ExprState *exprstate,
+ ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalFieldSelect(FieldSelectState *fstate,
+ ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
-static Datum ExecEvalConstraintTest(ConstraintTest *constraint,
- 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);
+static Datum ExecEvalCoerceViaIO(CoerceViaIOState *iostate,
+ ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate,
+ ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalCurrentOfExpr(ExprState *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.
+ * ----------------------------------------------------------------
+ */
/*----------
* 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
*----------
*/
static Datum
-ExecEvalArrayRef(ArrayRef *arrayRef,
+ExecEvalArrayRef(ArrayRefExprState *astate,
ExprContext *econtext,
bool *isNull,
ExprDoneCond *isDone)
{
+ ArrayRef *arrayRef = (ArrayRef *) astate->xprstate.expr;
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(arrayRef->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, arrayRef->refupperindexpr)
+ foreach(l, astate->refupperindexpr)
{
+ ExprState *eltstate = (ExprState *) lfirst(l);
+
if (i >= MAXDIM)
- elog(ERROR, "ExecEvalArrayRef: can only handle %d dimensions",
- 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((Node *) 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 (arrayRef->reflowerindexpr != NIL)
+ if (astate->reflowerindexpr != NIL)
{
- foreach(elt, arrayRef->reflowerindexpr)
+ foreach(l, astate->reflowerindexpr)
{
+ ExprState *eltstate = (ExprState *) lfirst(l);
+
if (j >= MAXDIM)
- elog(ERROR, "ExecEvalArrayRef: can only handle %d dimensions",
- 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((Node *) 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 */
if (i != j)
- elog(ERROR,
- "ExecEvalArrayRef: upper and lower indices mismatch");
+ elog(ERROR, "upper and lower index lists are not same length");
lIndex = lower.indx;
}
else
if (isAssignment)
{
- Datum sourceData = ExecEvalExpr(arrayRef->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 now, can't cope with inserting NULL into an array, so make
- * it a no-op per discussion above...
+ * 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 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,
- arrayRef->refattrlength,
- arrayRef->refelemlength,
- arrayRef->refelembyval,
- arrayRef->refelemalign,
- isNull);
+ eisnull,
+ astate->refattrlength,
+ astate->refelemlength,
+ astate->refelembyval,
+ astate->refelemalign);
else
resultArray = array_set_slice(array_source, i,
upper.indx, lower.indx,
- (ArrayType *) DatumGetPointer(sourceData),
- arrayRef->refattrlength,
- arrayRef->refelemlength,
- arrayRef->refelembyval,
- arrayRef->refelemalign,
- isNull);
+ (ArrayType *) DatumGetPointer(sourceData),
+ eisnull,
+ astate->refattrlength,
+ astate->refelemlength,
+ astate->refelembyval,
+ astate->refelemalign);
return PointerGetDatum(resultArray);
}
if (lIndex == NULL)
return array_ref(array_source, i, upper.indx,
- arrayRef->refattrlength,
- arrayRef->refelemlength,
- arrayRef->refelembyval,
- arrayRef->refelemalign,
+ astate->refattrlength,
+ astate->refelemlength,
+ astate->refelembyval,
+ astate->refelemalign,
isNull);
else
{
resultArray = array_get_slice(array_source, i,
upper.indx, lower.indx,
- arrayRef->refattrlength,
- arrayRef->refelemlength,
- arrayRef->refelembyval,
- arrayRef->refelemalign,
- isNull);
+ astate->refattrlength,
+ astate->refelemlength,
+ astate->refelembyval,
+ astate->refelemalign);
return PointerGetDatum(resultArray);
}
}
* ----------------------------------------------------------------
*/
static Datum
-ExecEvalAggref(Aggref *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, "ExecEvalAggref: no aggregates in this expression context");
+ elog(ERROR, "no aggregates in this expression context");
*isNull = econtext->ecxt_aggnulls[aggref->aggno];
return econtext->ecxt_aggvalues[aggref->aggno];
}
+/* ----------------------------------------------------------------
+ * ExecEvalWindowFunc
+ *
+ * Returns a Datum whose value is the value of the precomputed
+ * window function found in the given expression context.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalWindowFunc(WindowFuncExprState *wfunc, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
+{
+ if (isDone)
+ *isDone = ExprSingleResult;
+
+ if (econtext->ecxt_aggvalues == NULL) /* safety check */
+ elog(ERROR, "no window functions in this expression context");
+
+ *isNull = econtext->ecxt_aggnulls[wfunc->wfuncno];
+ return econtext->ecxt_aggvalues[wfunc->wfuncno];
+}
+
/* ----------------------------------------------------------------
* ExecEvalVar
*
* Returns a Datum whose value is the value of a range
* variable with respect to given expression context.
*
- *
- * As an entry condition, we expect that the datatype the
- * plan expects to get (as told by our "variable" argument) is in
- * fact the datatype of the attribute the plan says to fetch (as
- * seen in the current context, identified by our "econtext"
- * argument).
- *
- * If we fetch a Type A attribute and Caller treats it as if it
- * were Type B, there will be undefined results (e.g. crash).
- * One way these might mismatch now is that we're accessing a
- * catalog class and the type information in the pg_attribute
- * class does not match the hardcoded pg_attribute information
- * (in pg_attribute.h) for the class in question.
- *
- * We have an Assert to make sure this entry condition is met.
- *
- * ---------------------------------------------------------------- */
+ * 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, ExecEvalWholeRowVar, or ExecEvalWholeRowSlow 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
break;
}
- /*
- * extract tuple information from the slot
- */
- heapTuple = slot->val;
- tuple_type = slot->ttc_tupleDescriptor;
+ if (attnum != InvalidAttrNumber)
+ {
+ /*
+ * 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 (attnum > 0)
+ {
+ TupleDesc slot_tupdesc = slot->tts_tupleDescriptor;
+ Form_pg_attribute attr;
- attnum = variable->varattno;
+ if (attnum > slot_tupdesc->natts) /* should never happen */
+ elog(ERROR, "attribute number %d exceeds number of columns %d",
+ attnum, slot_tupdesc->natts);
- /* (See prolog for explanation of this Assert) */
- Assert(attnum <= 0 ||
- (attnum - 1 <= tuple_type->natts - 1 &&
- tuple_type->attrs[attnum - 1] != NULL &&
- variable->vartype == tuple_type->attrs[attnum - 1]->atttypid));
+ attr = slot_tupdesc->attrs[attnum - 1];
- /*
- * 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
- * TransactionCommandContext which should be safe enough. This
- * represents a serious memory leak if many such tuples are processed
- * in one command, however. We ought to redesign the representation
- * of whole-tuple datums so that this is not necessary.
- *
- * We assume it's OK to point to the existing tupleDescriptor, rather
- * than copy that too.
- */
- if (attnum == InvalidAttrNumber)
- {
- MemoryContext oldContext;
- TupleTableSlot *tempSlot;
- HeapTuple tup;
+ /* 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))));
+ }
+ }
- oldContext = MemoryContextSwitchTo(TransactionCommandContext);
- tempSlot = MakeTupleTableSlot();
- tup = heap_copytuple(heapTuple);
- ExecStoreTuple(tup, tempSlot, InvalidBuffer, true);
- ExecSetSlotDescriptor(tempSlot, tuple_type, false);
- MemoryContextSwitchTo(oldContext);
- return PointerGetDatum(tempSlot);
+ /* Skip the checking on future executions of node */
+ exprstate->evalfunc = ExecEvalScalarVar;
+
+ /* Fetch the value from the slot */
+ return slot_getattr(slot, attnum, isNull);
}
+ else
+ {
+ /*
+ * 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.
+ *
+ * If the Var identifies a named composite type, we must check that
+ * the actual tuple type is compatible with it.
+ */
+ TupleDesc slot_tupdesc = slot->tts_tupleDescriptor;
+ bool needslow = false;
+
+ 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;
- result = heap_getattr(heapTuple, /* tuple containing attribute */
- attnum, /* attribute number of desired
- * attribute */
- tuple_type, /* tuple descriptor of tuple */
- isNull); /* return: is attribute null? */
+ /*
+ * 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. Also, we have to allow the case that the slot
+ * has more columns than the Var's type, because we might be
+ * looking at the output of a subplan that includes resjunk
+ * columns. (XXX it would be nice to verify that the extra
+ * columns are all marked resjunk, but we haven't got access to
+ * the subplan targetlist here...) Resjunk columns should always
+ * be at the end of a targetlist, so it's sufficient to ignore
+ * them here; but we need to use ExecEvalWholeRowSlow to get rid
+ * of them in the eventual output tuples.
+ */
+ 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_plural("Table row contains %d attribute, but query expects %d.",
+ "Table row contains %d attributes, but query expects %d.",
+ slot_tupdesc->natts,
+ slot_tupdesc->natts,
+ var_tupdesc->natts)));
+ else if (var_tupdesc->natts < slot_tupdesc->natts)
+ needslow = true;
+
+ 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)));
+ }
- return result;
+ ReleaseTupleDesc(var_tupdesc);
+ }
+
+ /* Skip the checking on future executions of node */
+ if (needslow)
+ exprstate->evalfunc = ExecEvalWholeRowSlow;
+ else
+ exprstate->evalfunc = ExecEvalWholeRowVar;
+
+ /* Fetch the value */
+ return ExecEvalWholeRowVar(exprstate, econtext, isNull, isDone);
+ }
}
/* ----------------------------------------------------------------
- * ExecEvalParam
+ * ExecEvalScalarVar
*
- * Returns the value of a parameter. A param node contains
- * something like ($.name) and the expression context contains
- * the current parameter bindings (name = "sam") (age = 34)...
- * so our job is to replace the param node with the datum
- * containing the appropriate information ("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 shoud return a Const node with the
- * isnull flag) ? -cim 10/13/89
- *
- * Minor modification: Param nodes now have an extra field,
- * `paramkind' which specifies the type of parameter
- * (see params.h). So while searching the paramList for
- * a paramname/value pair, we have also to check for `kind'.
- *
- * NOTE: The last entry in `paramList' is always an
- * entry with kind == PARAM_INVALID.
+ * Returns a Datum for a scalar variable.
* ----------------------------------------------------------------
*/
-Datum
-ExecEvalParam(Param *expression, ExprContext *econtext, bool *isNull)
+static Datum
+ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
{
- char *thisParameterName;
- int thisParameterKind = expression->paramkind;
- AttrNumber thisParameterId = expression->paramid;
- int matchFound;
- ParamListInfo paramList;
+ Var *variable = (Var *) exprstate->expr;
+ TupleTableSlot *slot;
+ AttrNumber attnum;
- if (thisParameterKind == PARAM_EXEC)
+ if (isDone)
+ *isDone = ExprSingleResult;
+
+ /* Get the input slot and attribute number we want */
+ switch (variable->varno)
{
- ParamExecData *prm;
+ case INNER: /* get the tuple from the inner node */
+ slot = econtext->ecxt_innertuple;
+ break;
- prm = &(econtext->ecxt_param_exec_vals[thisParameterId]);
- if (prm->execPlan != NULL)
- {
- ExecSetParamPlan(prm->execPlan, econtext);
- /* ExecSetParamPlan should have processed this param... */
- Assert(prm->execPlan == NULL);
- }
- *isNull = prm->isnull;
- return prm->value;
+ 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;
}
- thisParameterName = expression->paramname;
- paramList = econtext->ecxt_param_list_info;
+ 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;
+
/*
- * search the list with the parameter info to find a matching name. An
- * entry with an InvalidName denotes the last element in the array.
+ * 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.
*/
- matchFound = 0;
- if (paramList != NULL)
- {
- /*
- * search for an entry in 'paramList' that matches the
- * `expression'.
- */
- while (paramList->kind != PARAM_INVALID && !matchFound)
- {
- switch (thisParameterKind)
- {
- case PARAM_NAMED:
- if (thisParameterKind == paramList->kind &&
- strcmp(paramList->name, thisParameterName) == 0)
- matchFound = 1;
- break;
- case PARAM_NUM:
- if (thisParameterKind == paramList->kind &&
- paramList->id == thisParameterId)
- matchFound = 1;
- break;
- case PARAM_OLD:
- case PARAM_NEW:
- if (thisParameterKind == paramList->kind &&
- paramList->id == thisParameterId)
- {
- matchFound = 1;
-
- /*
- * sanity check
- */
- if (strcmp(paramList->name, thisParameterName) != 0)
- {
- elog(ERROR,
- "ExecEvalParam: new/old params with same id & diff names");
- }
- }
- break;
- default:
+ dtuple = (HeapTupleHeader) palloc(tuple->t_len);
+ memcpy((char *) dtuple, (char *) tuple->t_data, tuple->t_len);
- /*
- * oops! this is not supposed to happen!
- */
- elog(ERROR, "ExecEvalParam: invalid paramkind %d",
- thisParameterKind);
- }
- if (!matchFound)
- paramList++;
- } /* while */
- } /* if */
+ HeapTupleHeaderSetDatumLength(dtuple, tuple->t_len);
- if (!matchFound)
+ /*
+ * 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)
{
- /*
- * ooops! we couldn't find this parameter in the parameter list.
- * Signal an error
- */
- elog(ERROR, "ExecEvalParam: Unknown value for parameter %s",
- thisParameterName);
+ HeapTupleHeaderSetTypeId(dtuple, variable->vartype);
+ HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod);
+ }
+ else
+ {
+ HeapTupleHeaderSetTypeId(dtuple, tupleDesc->tdtypeid);
+ HeapTupleHeaderSetTypMod(dtuple, tupleDesc->tdtypmod);
}
- /*
- * return the value.
- */
- *isNull = paramList->isnull;
- return paramList->value;
+ return PointerGetDatum(dtuple);
}
-
/* ----------------------------------------------------------------
- * ExecEvalOper / ExecEvalFunc support routines
+ * ExecEvalWholeRowSlow
+ *
+ * Returns a Datum for a whole-row variable, in the "slow" case where
+ * we can't just copy the subplan's output.
* ----------------------------------------------------------------
*/
+static Datum
+ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
+{
+ Var *variable = (Var *) exprstate->expr;
+ TupleTableSlot *slot = econtext->ecxt_scantuple;
+ HeapTuple tuple;
+ TupleDesc var_tupdesc;
+ HeapTupleHeader dtuple;
-/*
- * GetAttributeByName
- * GetAttributeByNum
- *
- * These are functions which return the value of the
- * named attribute out of the tuple from the arg slot. User defined
- * C functions which take a tuple as an argument are expected
- * to use this. Ex: overpaid(EMP) might call GetAttributeByNum().
- */
-Datum
-GetAttributeByNum(TupleTableSlot *slot,
+ if (isDone)
+ *isDone = ExprSingleResult;
+ *isNull = false;
+
+ /*
+ * Currently, the only case handled here is stripping of trailing resjunk
+ * fields, which we do in a slightly chintzy way by just adjusting the
+ * tuple's natts header field. Possibly there will someday be a need for
+ * more-extensive rearrangements, in which case it'd be worth
+ * disassembling and reassembling the tuple (perhaps use a JunkFilter for
+ * that?)
+ */
+ Assert(variable->vartype != RECORDOID);
+ var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
+
+ tuple = ExecFetchSlotTuple(slot);
+
+ /*
+ * 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; not to mention
+ * fooling with its natts field.
+ */
+ dtuple = (HeapTupleHeader) palloc(tuple->t_len);
+ memcpy((char *) dtuple, (char *) tuple->t_data, tuple->t_len);
+
+ HeapTupleHeaderSetDatumLength(dtuple, tuple->t_len);
+ HeapTupleHeaderSetTypeId(dtuple, variable->vartype);
+ HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod);
+
+ Assert(HeapTupleHeaderGetNatts(dtuple) >= var_tupdesc->natts);
+ HeapTupleHeaderSetNatts(dtuple, var_tupdesc->natts);
+
+ ReleaseTupleDesc(var_tupdesc);
+
+ return PointerGetDatum(dtuple);
+}
+
+/* ----------------------------------------------------------------
+ * 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;
+}
+
+/* ----------------------------------------------------------------
+ * ExecEvalParam
+ *
+ * Returns the value of a parameter. A param node contains
+ * 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").
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalParam(ExprState *exprstate, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
+{
+ Param *expression = (Param *) exprstate->expr;
+ int thisParamId = expression->paramid;
+
+ if (isDone)
+ *isDone = ExprSingleResult;
+
+ 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.
+ */
+ ParamExecData *prm;
+
+ prm = &(econtext->ecxt_param_exec_vals[thisParamId]);
+ if (prm->execPlan != NULL)
+ {
+ /* Parameter not evaluated yet, so go do it */
+ ExecSetParamPlan(prm->execPlan, econtext);
+ /* ExecSetParamPlan should have processed this param... */
+ Assert(prm->execPlan == NULL);
+ }
+ *isNull = prm->isnull;
+ return prm->value;
+ }
+ else
+ {
+ /*
+ * PARAM_EXTERN parameters must be sought in ecxt_param_list_info.
+ */
+ ParamListInfo paramInfo = econtext->ecxt_param_list_info;
+
+ Assert(expression->paramkind == PARAM_EXTERN);
+ if (paramInfo &&
+ thisParamId > 0 && thisParamId <= paramInfo->numParams)
+ {
+ ParamExternData *prm = ¶mInfo->params[thisParamId - 1];
+
+ if (OidIsValid(prm->ptype))
+ {
+ Assert(prm->ptype == expression->paramtype);
+ *isNull = prm->isnull;
+ return prm->value;
+ }
+ }
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("no value found for parameter %d", thisParamId)));
+ return (Datum) 0; /* keep compiler quiet */
+ }
+}
+
+
+/* ----------------------------------------------------------------
+ * ExecEvalOper / ExecEvalFunc support routines
+ * ----------------------------------------------------------------
+ */
+
+/*
+ * GetAttributeByName
+ * GetAttributeByNum
+ *
+ * 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 these. Ex: overpaid(EMP) might call GetAttributeByNum().
+ * Note: these are actually rather slow because they do a typcache
+ * lookup on each call.
+ */
+Datum
+GetAttributeByNum(HeapTupleHeader tuple,
AttrNumber attrno,
bool *isNull)
{
- Datum retval;
+ Datum result;
+ Oid tupType;
+ int32 tupTypmod;
+ TupleDesc tupDesc;
+ HeapTupleData tmptup;
if (!AttributeNumberIsValid(attrno))
- elog(ERROR, "GetAttributeByNum: Invalid attribute number");
+ elog(ERROR, "invalid attribute number %d", attrno);
- if (!AttrNumberIsForUserDefinedAttr(attrno))
- elog(ERROR, "GetAttributeByNum: cannot access system attributes here");
+ if (isNull == NULL)
+ elog(ERROR, "a NULL isNull pointer was passed");
- if (isNull == (bool *) NULL)
- elog(ERROR, "GetAttributeByNum: a NULL isNull flag 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, "GetAttributeByName: Invalid attribute name");
+ elog(ERROR, "invalid attribute name");
- if (isNull == (bool *) NULL)
- elog(ERROR, "GetAttributeByName: a NULL isNull flag was passed");
+ 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;
}
}
if (attrno == InvalidAttrNumber)
- elog(ERROR, "GetAttributeByName: attribute %s not found", attname);
+ elog(ERROR, "attribute \"%s\" does not exist", attname);
+
+ /*
+ * 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;
- retval = heap_getattr(slot->val,
+ result = heap_getattr(&tmptup,
attrno,
- tupdesc,
+ tupDesc,
isNull);
- if (*isNull)
- return (Datum) 0;
- return retval;
+ ReleaseTupleDesc(tupDesc);
+
+ return result;
+}
+
+/*
+ * init_fcache - initialize a FuncExprState node during first use
+ */
+static void
+init_fcache(Oid foid, FuncExprState *fcache,
+ MemoryContext fcacheCxt, bool needDescForSets)
+{
+ AclResult aclresult;
+
+ /* Check permission to call function */
+ aclresult = pg_proc_aclcheck(foid, GetUserId(), ACL_EXECUTE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(foid));
+
+ /*
+ * 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_plural("cannot pass more than %d argument to a function",
+ "cannot pass more than %d arguments to a function",
+ FUNC_MAX_ARGS,
+ FUNC_MAX_ARGS)));
+
+ /* Set up the primary fmgr lookup information */
+ fmgr_info_cxt(foid, &(fcache->func), fcacheCxt);
+ fcache->func.fn_expr = (Node *) fcache->xprstate.expr;
+
+ /* If function returns set, prepare expected tuple descriptor */
+ if (fcache->func.fn_retset && needDescForSets)
+ {
+ TypeFuncClass functypclass;
+ Oid funcrettype;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+
+ functypclass = get_expr_result_type(fcache->func.fn_expr,
+ &funcrettype,
+ &tupdesc);
+
+ /* Must save tupdesc in fcache's context */
+ oldcontext = MemoryContextSwitchTo(fcacheCxt);
+
+ if (functypclass == TYPEFUNC_COMPOSITE)
+ {
+ /* Composite data type, e.g. a table's row type */
+ Assert(tupdesc);
+ /* Must copy it out of typcache for safety */
+ fcache->funcResultDesc = CreateTupleDescCopy(tupdesc);
+ fcache->funcReturnsTuple = true;
+ }
+ else if (functypclass == TYPEFUNC_SCALAR)
+ {
+ /* Base data type, i.e. scalar */
+ tupdesc = CreateTemplateTupleDesc(1, false);
+ TupleDescInitEntry(tupdesc,
+ (AttrNumber) 1,
+ NULL,
+ funcrettype,
+ -1,
+ 0);
+ fcache->funcResultDesc = tupdesc;
+ fcache->funcReturnsTuple = false;
+ }
+ else
+ {
+ /* Else, we will complain if function wants materialize mode */
+ fcache->funcResultDesc = NULL;
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
+ else
+ fcache->funcResultDesc = NULL;
+
+ /* Initialize additional state */
+ fcache->funcResultStore = NULL;
+ fcache->funcResultSlot = NULL;
+ fcache->setArgsValid = false;
+ fcache->shutdown_reg = false;
+}
+
+/*
+ * 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);
+
+ /* If we have a slot, make sure it's let go of any tuplestore pointer */
+ if (fcache->funcResultSlot)
+ ExecClearTuple(fcache->funcResultSlot);
+
+ /* Release any open tuplestore */
+ if (fcache->funcResultStore)
+ tuplestore_end(fcache->funcResultStore);
+ fcache->funcResultStore = NULL;
+
+ /* 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;
}
/*
{
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((Node *) lfirst(arg),
+ fcinfo->arg[i] = ExecEvalExpr(argstate,
econtext,
&fcinfo->argnull[i],
&thisArgIsDone);
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)
- elog(ERROR, "Functions and operators can take only one set argument");
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("functions and operators can take at most one set argument")));
argIsDone = thisArgIsDone;
}
i++;
return argIsDone;
}
+/*
+ * ExecPrepareTuplestoreResult
+ *
+ * Subroutine for ExecMakeFunctionResult: prepare to extract rows from a
+ * tuplestore function result. We must set up a funcResultSlot (unless
+ * already done in a previous call cycle) and verify that the function
+ * returned the expected tuple descriptor.
+ */
+static void
+ExecPrepareTuplestoreResult(FuncExprState *fcache,
+ ExprContext *econtext,
+ Tuplestorestate *resultStore,
+ TupleDesc resultDesc)
+{
+ fcache->funcResultStore = resultStore;
+
+ if (fcache->funcResultSlot == NULL)
+ {
+ /* Create a slot so we can read data out of the tuplestore */
+ MemoryContext oldcontext;
+
+ /* We must have been able to determine the result rowtype */
+ if (fcache->funcResultDesc == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("function returning setof record called in "
+ "context that cannot accept type record")));
+
+ oldcontext = MemoryContextSwitchTo(fcache->func.fn_mcxt);
+ fcache->funcResultSlot =
+ MakeSingleTupleTableSlot(fcache->funcResultDesc);
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ /*
+ * If function provided a tupdesc, cross-check it. We only really
+ * need to do this for functions returning RECORD, but might as well
+ * do it always.
+ */
+ if (resultDesc)
+ {
+ if (fcache->funcResultDesc)
+ tupledesc_match(fcache->funcResultDesc, resultDesc);
+
+ /*
+ * If it is a dynamically-allocated TupleDesc, free it: it is
+ * typically allocated in a per-query context, so we must avoid
+ * leaking it across multiple usages.
+ */
+ if (resultDesc->tdrefcount == -1)
+ FreeTupleDesc(resultDesc);
+ }
+
+ /* Register cleanup callback if we didn't already */
+ if (!fcache->shutdown_reg)
+ {
+ RegisterExprContextCallback(econtext,
+ ShutdownFuncExpr,
+ PointerGetDatum(fcache));
+ fcache->shutdown_reg = true;
+ }
+}
+
+/*
+ * Check that function result tuple type (src_tupdesc) matches or can
+ * be considered to match what the query expects (dst_tupdesc). If
+ * they don't match, ereport.
+ *
+ * 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.
+ */
+static void
+tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc)
+{
+ int i;
+
+ if (dst_tupdesc->natts != src_tupdesc->natts)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("function return row and query-specified return row do not match"),
+ errdetail_plural("Returned row contains %d attribute, but query expects %d.",
+ "Returned row contains %d attributes, but query expects %d.",
+ src_tupdesc->natts,
+ src_tupdesc->natts, dst_tupdesc->natts)));
+
+ for (i = 0; i < dst_tupdesc->natts; i++)
+ {
+ Form_pg_attribute dattr = dst_tupdesc->attrs[i];
+ Form_pg_attribute sattr = src_tupdesc->attrs[i];
+
+ if (dattr->atttypid == sattr->atttypid)
+ continue; /* no worries */
+ if (!dattr->attisdropped)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("function return row and query-specified return row do not match"),
+ errdetail("Returned type %s at ordinal position %d, but query expects %s.",
+ format_type_be(sattr->atttypid),
+ i + 1,
+ format_type_be(dattr->atttypid))));
+
+ if (dattr->attlen != sattr->attlen ||
+ dattr->attalign != sattr->attalign)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("function return row and query-specified return row do not match"),
+ errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.",
+ i + 1)));
+ }
+}
+
/*
* ExecMakeFunctionResult
*
* Evaluate the arguments to a function and then the function itself.
+ * init_fcache is presumed already run on the FuncExprState.
+ *
+ * This function handles the most general case, wherein the function or
+ * one of its arguments might (or might not) return a set. If we find
+ * no sets involved, we will change the FuncExprState's function pointer
+ * to use a simpler method on subsequent calls.
*/
-Datum
-ExecMakeFunctionResult(FunctionCachePtr fcache,
- List *arguments,
+static Datum
+ExecMakeFunctionResult(FuncExprState *fcache,
ExprContext *econtext,
bool *isNull,
ExprDoneCond *isDone)
{
+ List *arguments;
Datum result;
- FunctionCallInfoData fcinfo;
+ FunctionCallInfoData fcinfo_data;
+ FunctionCallInfo fcinfo;
+ PgStat_FunctionCallUsage fcusage;
ReturnSetInfo rsinfo; /* for functions returning sets */
ExprDoneCond argDone;
bool hasSetArg;
int i;
+restart:
+
+ /* Guard against stack overflow due to overly complex expressions */
+ check_stack_depth();
+
+ /*
+ * If a previous call of the function returned a set result in the form
+ * of a tuplestore, continue reading rows from the tuplestore until it's
+ * empty.
+ */
+ if (fcache->funcResultStore)
+ {
+ Assert(isDone); /* it was provided before ... */
+ if (tuplestore_gettupleslot(fcache->funcResultStore, true, false,
+ fcache->funcResultSlot))
+ {
+ *isDone = ExprMultipleResult;
+ if (fcache->funcReturnsTuple)
+ {
+ /* We must return the whole tuple as a Datum. */
+ *isNull = false;
+ return ExecFetchSlotTupleDatum(fcache->funcResultSlot);
+ }
+ else
+ {
+ /* Extract the first column and return it as a scalar. */
+ return slot_getattr(fcache->funcResultSlot, 1, isNull);
+ }
+ }
+ /* Exhausted the tuplestore, so clean up */
+ tuplestore_end(fcache->funcResultStore);
+ fcache->funcResultStore = NULL;
+ /* We are done unless there was a set-valued argument */
+ if (!fcache->setHasSetArg)
+ {
+ *isDone = ExprEndResult;
+ *isNull = true;
+ return (Datum) 0;
+ }
+ /* If there was, continue evaluating the argument values */
+ Assert(!fcache->setArgsValid);
+ }
+
+ /*
+ * For non-set-returning functions, we just use a local-variable
+ * FunctionCallInfoData. For set-returning functions we keep the callinfo
+ * record in fcache->setArgs so that it can survive across multiple
+ * value-per-call invocations. (The reason we don't just do the latter
+ * all the time is that plpgsql expects to be able to use simple expression
+ * trees re-entrantly. Which might not be a good idea, but the penalty
+ * for not doing so is high.)
+ */
+ if (fcache->func.fn_retset)
+ fcinfo = &fcache->setArgs;
+ else
+ fcinfo = &fcinfo_data;
+
/*
- * 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.
*/
+ arguments = fcache->args;
if (!fcache->setArgsValid)
{
/* Need to prep callinfo structure */
- MemSet(&fcinfo, 0, sizeof(fcinfo));
- fcinfo.flinfo = &(fcache->func);
- argDone = ExecEvalFuncArgs(&fcinfo, arguments, econtext);
+ InitFunctionCallInfoData(*fcinfo, &(fcache->func), 0, NULL, NULL);
+ argDone = ExecEvalFuncArgs(fcinfo, arguments, econtext);
if (argDone == ExprEndResult)
{
/* input is an empty set, so return an empty set. */
if (isDone)
*isDone = ExprEndResult;
else
- elog(ERROR, "Set-valued function called in context that cannot accept a set");
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
return (Datum) 0;
}
hasSetArg = (argDone != ExprSingleResult);
}
else
{
- /* Copy callinfo from previous evaluation */
- memcpy(&fcinfo, &fcache->setArgs, sizeof(fcinfo));
+ /* Re-use callinfo from previous evaluation */
hasSetArg = fcache->setHasSetArg;
/* Reset flag (we may set it again below) */
fcache->setArgsValid = false;
}
/*
- * If function returns set, prepare a resultinfo node for
- * communication
+ * Now call the function, passing the evaluated parameter values.
*/
- if (fcache->func.fn_retset)
+ if (fcache->func.fn_retset || hasSetArg)
{
- fcinfo.resultinfo = (Node *) &rsinfo;
+ /*
+ * We need to return a set result. Complain if caller not ready to
+ * accept one.
+ */
+ if (isDone == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ /*
+ * Prepare a resultinfo node for communication. If the function
+ * doesn't itself return set, we don't pass the resultinfo to the
+ * function, but we need to fill it in anyway for internal use.
+ */
+ if (fcache->func.fn_retset)
+ fcinfo->resultinfo = (Node *) &rsinfo;
rsinfo.type = T_ReturnSetInfo;
rsinfo.econtext = econtext;
- rsinfo.expectedDesc = NULL;
- rsinfo.allowedModes = (int) SFRM_ValuePerCall;
+ rsinfo.expectedDesc = fcache->funcResultDesc;
+ rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize);
+ /* note we do not set SFRM_Materialize_Random or _Preferred */
rsinfo.returnMode = SFRM_ValuePerCall;
/* isDone is filled below */
rsinfo.setResult = NULL;
rsinfo.setDesc = NULL;
- }
-
- /*
- * 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.
- */
- if (isDone == NULL)
- elog(ERROR, "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;
if (fcache->func.fn_strict)
{
- for (i = 0; i < fcinfo.nargs; i++)
+ for (i = 0; i < fcinfo->nargs; i++)
{
- if (fcinfo.argnull[i])
+ if (fcinfo->argnull[i])
{
callit = false;
break;
if (callit)
{
- fcinfo.isnull = false;
+ pgstat_init_function_usage(fcinfo, &fcusage);
+
+ fcinfo->isnull = false;
rsinfo.isDone = ExprSingleResult;
- result = FunctionCallInvoke(&fcinfo);
- *isNull = fcinfo.isnull;
+ result = FunctionCallInvoke(fcinfo);
+ *isNull = fcinfo->isnull;
*isDone = rsinfo.isDone;
+
+ pgstat_end_function_usage(&fcusage,
+ rsinfo.isDone != ExprMultipleResult);
}
else
{
*isDone = ExprEndResult;
}
- if (*isDone != ExprEndResult)
+ /* Which protocol does function want to use? */
+ if (rsinfo.returnMode == SFRM_ValuePerCall)
{
- /*
- * Got a result from current argument. If function itself
- * returns set, save the current argument values to re-use
- * on the next call.
- */
- if (fcache->func.fn_retset)
+ if (*isDone != ExprEndResult)
{
- memcpy(&fcache->setArgs, &fcinfo, sizeof(fcinfo));
- fcache->setHasSetArg = hasSetArg;
- fcache->setArgsValid = true;
- }
+ /*
+ * Got a result from current argument. If function itself
+ * returns set, save the current argument values to re-use
+ * on the next call.
+ */
+ if (fcache->func.fn_retset &&
+ *isDone == ExprMultipleResult)
+ {
+ Assert(fcinfo == &fcache->setArgs);
+ 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;
- break;
+ /*
+ * Make sure we say we are returning a set, even if the
+ * function itself doesn't return sets.
+ */
+ if (hasSetArg)
+ *isDone = ExprMultipleResult;
+ break;
+ }
}
+ else if (rsinfo.returnMode == SFRM_Materialize)
+ {
+ /* check we're on the same page as the function author */
+ if (rsinfo.isDone != ExprSingleResult)
+ ereport(ERROR,
+ (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
+ errmsg("table-function protocol for materialize mode was not followed")));
+ if (rsinfo.setResult != NULL)
+ {
+ /* prepare to return values from the tuplestore */
+ ExecPrepareTuplestoreResult(fcache, econtext,
+ rsinfo.setResult,
+ rsinfo.setDesc);
+ /* remember whether we had set arguments */
+ fcache->setHasSetArg = hasSetArg;
+ /* loop back to top to start returning from tuplestore */
+ goto restart;
+ }
+ /* if setResult was left null, treat it as empty set */
+ *isDone = ExprEndResult;
+ *isNull = true;
+ result = (Datum) 0;
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
+ errmsg("unrecognized table-function returnMode: %d",
+ (int) rsinfo.returnMode)));
/* Else, done with this argument */
if (!hasSetArg)
break; /* input not a set, so done */
/* Re-eval args to get the next element of the input set */
- argDone = ExecEvalFuncArgs(&fcinfo, arguments, econtext);
+ argDone = ExecEvalFuncArgs(fcinfo, arguments, econtext);
if (argDone != ExprMultipleResult)
{
}
/*
- * 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.
*/
}
}
/*
* 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.
*/
if (fcache->func.fn_strict)
{
- for (i = 0; i < fcinfo.nargs; i++)
+ for (i = 0; i < fcinfo->nargs; i++)
{
- if (fcinfo.argnull[i])
+ if (fcinfo->argnull[i])
{
*isNull = true;
return (Datum) 0;
}
}
}
- fcinfo.isnull = false;
- result = FunctionCallInvoke(&fcinfo);
- *isNull = fcinfo.isnull;
+
+ pgstat_init_function_usage(fcinfo, &fcusage);
+
+ fcinfo->isnull = false;
+ result = FunctionCallInvoke(fcinfo);
+ *isNull = fcinfo->isnull;
+
+ pgstat_end_function_usage(&fcusage, true);
+ }
+
+ 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;
+ PgStat_FunctionCallUsage fcusage;
+ 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;
+ }
+ }
}
+ pgstat_init_function_usage(&fcinfo, &fcusage);
+
+ /* fcinfo.isnull = false; */ /* handled by InitFunctionCallInfoData */
+ result = FunctionCallInvoke(&fcinfo);
+ *isNull = fcinfo.isnull;
+
+ pgstat_end_function_usage(&fcusage, true);
+
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.
*/
Tuplestorestate *
-ExecMakeTableFunctionResult(Expr *funcexpr,
+ExecMakeTableFunctionResult(ExprState *funcexpr,
ExprContext *econtext,
TupleDesc expectedDesc,
- TupleDesc *returnDesc)
+ bool randomAccess)
{
Tuplestorestate *tupstore = NULL;
TupleDesc tupdesc = NULL;
- Func *func;
- List *argList;
- FunctionCachePtr fcache;
+ Oid funcrettype;
+ bool returnsTuple;
+ bool returnsSet = false;
FunctionCallInfoData fcinfo;
+ PgStat_FunctionCallUsage fcusage;
ReturnSetInfo rsinfo;
- ExprDoneCond argDone;
+ HeapTupleData tmptup;
MemoryContext callerContext;
MemoryContext oldcontext;
- TupleTableSlot *slot;
+ bool direct_function_call;
bool first_time = true;
- bool returnsTuple = false;
- /* Extract data from function-call expression node */
- if (!funcexpr || !IsA(funcexpr, Expr) || funcexpr->opType != FUNC_EXPR)
- elog(ERROR, "ExecMakeTableFunctionResult: expression is not a function call");
- func = (Func *) funcexpr->oper;
- argList = funcexpr->args;
+ callerContext = CurrentMemoryContext;
- /*
- * get the fcache from the Func node. If it is NULL, then initialize it
- */
- fcache = func->func_fcache;
- if (fcache == NULL)
- {
- fcache = init_fcache(func->funcid, length(argList),
- econtext->ecxt_per_query_memory);
- func->func_fcache = fcache;
- }
+ funcrettype = exprType((Node *) funcexpr->expr);
+
+ returnsTuple = type_is_rowtype(funcrettype);
/*
- * 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?
+ * 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.
*/
- MemSet(&fcinfo, 0, sizeof(fcinfo));
- fcinfo.flinfo = &(fcache->func);
- argDone = ExecEvalFuncArgs(&fcinfo, argList, econtext);
- /* We don't allow sets in the arguments of the table function */
- if (argDone != ExprSingleResult)
- elog(ERROR, "Set-valued function called in context that cannot accept a set");
+ InitFunctionCallInfoData(fcinfo, NULL, 0, NULL, (Node *) &rsinfo);
+ rsinfo.type = T_ReturnSetInfo;
+ rsinfo.econtext = econtext;
+ rsinfo.expectedDesc = expectedDesc;
+ rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize | SFRM_Materialize_Preferred);
+ if (randomAccess)
+ rsinfo.allowedModes |= (int) SFRM_Materialize_Random;
+ rsinfo.returnMode = SFRM_ValuePerCall;
+ /* isDone is filled below */
+ rsinfo.setResult = NULL;
+ rsinfo.setDesc = NULL;
/*
- * If function is strict, and there are any NULL arguments, skip
- * calling the function and return NULL (actually an empty set).
+ * 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 (fcache->func.fn_strict)
+ if (funcexpr && IsA(funcexpr, FuncExprState) &&
+ IsA(funcexpr->expr, FuncExpr))
{
- int i;
+ FuncExprState *fcache = (FuncExprState *) funcexpr;
+ ExprDoneCond argDone;
+
+ /*
+ * This path is similar to ExecMakeFunctionResult.
+ */
+ direct_function_call = true;
- for (i = 0; i < fcinfo.nargs; i++)
+ /*
+ * Initialize function cache if first time through
+ */
+ if (fcache->func.fn_oid == InvalidOid)
{
- if (fcinfo.argnull[i])
+ FuncExpr *func = (FuncExpr *) fcache->xprstate.expr;
+
+ init_fcache(func->funcid, fcache,
+ econtext->ecxt_per_query_memory, false);
+ }
+ 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?
+ */
+ fcinfo.flinfo = &(fcache->func);
+ argDone = ExecEvalFuncArgs(&fcinfo, fcache->args, econtext);
+ /* We don't allow sets in the arguments of the table function */
+ if (argDone != ExprSingleResult)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ /*
+ * If function is strict, and there are any NULL arguments, skip
+ * calling the function and act like it returned NULL (or an empty
+ * set, in the returns-set case).
+ */
+ if (fcache->func.fn_strict)
+ {
+ int i;
+
+ for (i = 0; i < fcinfo.nargs; i++)
{
- *returnDesc = NULL;
- return NULL;
+ if (fcinfo.argnull[i])
+ goto no_function_result;
}
}
}
+ else
+ {
+ /* Treat funcexpr as a generic expression */
+ direct_function_call = false;
+ }
/*
- * Prepare a resultinfo node for communication. We always do this even
- * if not expecting a set result, so that we can pass expectedDesc.
- */
- fcinfo.resultinfo = (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;
-
- /*
- * Switch to short-lived context for calling the function.
+ * 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.
+ * Loop to handle the ValuePerCall protocol (which is also the same
+ * behavior needed in the generic ExecEvalExpr path).
*/
for (;;)
{
Datum result;
- HeapTuple tuple;
+
+ CHECK_FOR_INTERRUPTS();
/*
- * reset per-tuple memory context before each call of the function.
- * 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);
- /* Call the function one time */
- fcinfo.isnull = false;
- rsinfo.isDone = ExprSingleResult;
- result = FunctionCallInvoke(&fcinfo);
+ /* Call the function or expression one time */
+ if (direct_function_call)
+ {
+ pgstat_init_function_usage(&fcinfo, &fcusage);
+
+ fcinfo.isnull = false;
+ rsinfo.isDone = ExprSingleResult;
+ result = FunctionCallInvoke(&fcinfo);
+
+ pgstat_end_function_usage(&fcusage,
+ rsinfo.isDone != ExprMultipleResult);
+ }
+ else
+ {
+ result = ExecEvalExpr(funcexpr, econtext,
+ &fcinfo.isnull, &rsinfo.isDone);
+ }
/* Which protocol does function want to use? */
if (rsinfo.returnMode == SFRM_ValuePerCall)
{
/*
* 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;
+
+ /*
+ * 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)
{
- Oid funcrettype = funcexpr->typeOid;
-
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 || !IsA(slot, TupleTableSlot) ||
- !slot->ttc_tupleDescriptor)
- elog(ERROR, "ExecMakeTableFunctionResult: Invalid result from function returning tuple");
- tupdesc = CreateTupleDescCopy(slot->ttc_tupleDescriptor);
- returnsTuple = true;
+ HeapTupleHeader td;
+
+ td = DatumGetHeapTupleHeader(result);
+ tupdesc = lookup_rowtype_tupdesc_copy(HeapTupleHeaderGetTypeId(td),
+ HeapTupleHeaderGetTypMod(td));
}
else
{
"column",
funcrettype,
-1,
- 0,
- false);
+ 0);
}
- tupstore = tuplestore_begin_heap(true, /* randomAccess */
- SortMem);
+ tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
MemoryContextSwitchTo(oldcontext);
rsinfo.setResult = tupstore;
rsinfo.setDesc = tupdesc;
*/
if (returnsTuple)
{
- slot = (TupleTableSlot *) DatumGetPointer(result);
- if (fcinfo.isnull || !slot || !IsA(slot, TupleTableSlot) ||
- TupIsNull(slot))
- elog(ERROR, "ExecMakeTableFunctionResult: Invalid result from function returning tuple");
- 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;
+
+ oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+ tuplestore_puttuple(tupstore, &tmptup);
}
else
{
- char nullflag;
-
- nullflag = fcinfo.isnull ? 'n' : ' ';
- tuple = heap_formtuple(tupdesc, &result, &nullflag);
+ oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+ tuplestore_putvalues(tupstore, tupdesc, &result, &fcinfo.isnull);
}
-
- oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
- tuplestore_puttuple(tupstore, tuple);
MemoryContextSwitchTo(oldcontext);
/*
{
/* check we're on the same page as the function author */
if (!first_time || rsinfo.isDone != ExprSingleResult)
- elog(ERROR, "ExecMakeTableFunctionResult: Materialize-mode protocol not followed");
+ ereport(ERROR,
+ (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
+ errmsg("table-function protocol for materialize mode was not followed")));
/* Done evaluating the set result */
break;
}
else
- elog(ERROR, "ExecMakeTableFunctionResult: unknown returnMode %d",
- (int) rsinfo.returnMode);
+ ereport(ERROR,
+ (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
+ errmsg("unrecognized table-function returnMode: %d",
+ (int) rsinfo.returnMode)));
first_time = false;
}
- /* If we have a locally-created tupstore, close it up */
- if (tupstore)
+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);
- tuplestore_donestoring(tupstore);
+ tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
+ rsinfo.setResult = tupstore;
+ if (!returnsSet)
+ {
+ int natts = expectedDesc->natts;
+ Datum *nulldatums;
+ bool *nullflags;
+
+ MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ nulldatums = (Datum *) palloc0(natts * sizeof(Datum));
+ nullflags = (bool *) palloc(natts * sizeof(bool));
+ memset(nullflags, true, natts * sizeof(bool));
+ MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+ tuplestore_putvalues(tupstore, expectedDesc, nulldatums, nullflags);
+ }
+ }
+
+ /*
+ * If function provided a tupdesc, cross-check it. We only really
+ * need to do this for functions returning RECORD, but might as well
+ * do it always.
+ */
+ if (rsinfo.setDesc)
+ {
+ tupledesc_match(expectedDesc, rsinfo.setDesc);
+
+ /*
+ * If it is a dynamically-allocated TupleDesc, free it: it is
+ * typically allocated in a per-query context, so we must avoid
+ * leaking it across multiple usages.
+ */
+ if (rsinfo.setDesc->tdrefcount == -1)
+ FreeTupleDesc(rsinfo.setDesc);
}
MemoryContextSwitchTo(callerContext);
- /* The returned pointers are those in rsinfo */
- *returnDesc = rsinfo.setDesc;
+ /* All done, pass back the tuplestore */
return rsinfo.setResult;
}
/* ----------------------------------------------------------------
- * ExecEvalOper
* ExecEvalFunc
- * ExecEvalDistinct
+ * ExecEvalOper
*
* Evaluate the functional result of a list of arguments by calling the
* function manager.
*/
/* ----------------------------------------------------------------
- * ExecEvalOper
+ * ExecEvalFunc
* ----------------------------------------------------------------
*/
static Datum
-ExecEvalOper(Expr *opClause,
+ExecEvalFunc(FuncExprState *fcache,
ExprContext *econtext,
bool *isNull,
ExprDoneCond *isDone)
{
- Oper *op;
- List *argList;
- FunctionCachePtr fcache;
+ /* This is called only the first time through */
+ FuncExpr *func = (FuncExpr *) fcache->xprstate.expr;
- /*
- * we extract the oid of the function associated with the op and then
- * pass the work onto ExecMakeFunctionResult which evaluates the
- * arguments and returns the result of calling the function on the
- * evaluated arguments.
- */
- op = (Oper *) opClause->oper;
- argList = opClause->args;
+ /* Initialize function lookup info */
+ init_fcache(func->funcid, fcache, econtext->ecxt_per_query_memory, true);
- /*
- * get the fcache from the Oper node. If it is NULL, then initialize
- * it
- */
- fcache = op->op_fcache;
- if (fcache == NULL)
- {
- fcache = init_fcache(op->opid, length(argList),
- econtext->ecxt_per_query_memory);
- op->op_fcache = fcache;
- }
+ /* Go directly to ExecMakeFunctionResult on subsequent uses */
+ fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResult;
- return ExecMakeFunctionResult(fcache, argList, econtext,
- isNull, isDone);
+ return ExecMakeFunctionResult(fcache, econtext, isNull, isDone);
}
/* ----------------------------------------------------------------
- * ExecEvalFunc
+ * ExecEvalOper
* ----------------------------------------------------------------
*/
-
static Datum
-ExecEvalFunc(Expr *funcClause,
+ExecEvalOper(FuncExprState *fcache,
ExprContext *econtext,
bool *isNull,
ExprDoneCond *isDone)
{
- Func *func;
- List *argList;
- FunctionCachePtr fcache;
+ /* This is called only the first time through */
+ OpExpr *op = (OpExpr *) fcache->xprstate.expr;
- /*
- * we extract the oid of the function associated with the func node
- * and then pass the work onto ExecMakeFunctionResult which evaluates
- * the arguments and returns the result of calling the function on the
- * evaluated arguments.
- *
- * this is nearly identical to the ExecEvalOper code.
- */
- func = (Func *) funcClause->oper;
- argList = funcClause->args;
+ /* Initialize function lookup info */
+ init_fcache(op->opfuncid, fcache, econtext->ecxt_per_query_memory, true);
- /*
- * get the fcache from the Func node. If it is NULL, then initialize
- * it
- */
- fcache = func->func_fcache;
- if (fcache == NULL)
- {
- fcache = init_fcache(func->funcid, length(argList),
- econtext->ecxt_per_query_memory);
- func->func_fcache = fcache;
- }
+ /* Go directly to ExecMakeFunctionResult on subsequent uses */
+ fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResult;
- return ExecMakeFunctionResult(fcache, argList, econtext,
- isNull, isDone);
+ return ExecMakeFunctionResult(fcache, econtext, isNull, isDone);
}
/* ----------------------------------------------------------------
* they are NULL; if either is NULL then the result is already
* known. If neither is NULL, then proceed to evaluate the
* function. Note that this is *always* derived from the equals
- * operator, but since we've already evaluated the arguments
+ * operator, but since we need special processing of the arguments
* we can not simply reuse ExecEvalOper() or ExecEvalFunc().
* ----------------------------------------------------------------
*/
static Datum
-ExecEvalDistinct(Expr *opClause,
+ExecEvalDistinct(FuncExprState *fcache,
ExprContext *econtext,
bool *isNull,
ExprDoneCond *isDone)
{
- bool result;
- FunctionCachePtr fcache;
+ Datum result;
FunctionCallInfoData fcinfo;
ExprDoneCond argDone;
- Oper *op;
List *argList;
- /*
- * we extract the oid of the function associated with the op and then
- * pass the work onto ExecMakeFunctionResult which evaluates the
- * arguments and returns the result of calling the function on the
- * evaluated arguments.
- */
- op = (Oper *) opClause->oper;
- argList = opClause->args;
+ /* Set default values for result flags: non-null, not a set result */
+ *isNull = false;
+ if (isDone)
+ *isDone = ExprSingleResult;
/*
- * get the fcache from the Oper node. If it is NULL, then initialize
- * it
+ * Initialize function cache if first time through
*/
- fcache = op->op_fcache;
- if (fcache == NULL)
+ if (fcache->func.fn_oid == InvalidOid)
{
- fcache = init_fcache(op->opid, length(argList),
- econtext->ecxt_per_query_memory);
- op->op_fcache = fcache;
+ DistinctExpr *op = (DistinctExpr *) fcache->xprstate.expr;
+
+ init_fcache(op->opfuncid, fcache,
+ econtext->ecxt_per_query_memory, true);
+ Assert(!fcache->func.fn_retset);
}
- Assert(fcache->func.fn_retset == FALSE);
+
+ /*
+ * extract info from 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")));
Assert(fcinfo.nargs == 2);
if (fcinfo.argnull[0] && fcinfo.argnull[1])
{
/* Both NULL? Then is not distinct... */
- result = FALSE;
+ result = BoolGetDatum(FALSE);
}
else if (fcinfo.argnull[0] || fcinfo.argnull[1])
{
- /* One is NULL? Then is distinct... */
- result = TRUE;
+ /* Only one is NULL? Then is distinct... */
+ result = BoolGetDatum(TRUE);
}
else
{
fcinfo.isnull = false;
result = FunctionCallInvoke(&fcinfo);
*isNull = fcinfo.isnull;
-
- result = (!DatumGetBool(result));
+ /* Must invert result of "=" */
+ result = BoolGetDatum(!DatumGetBool(result));
}
- return BoolGetDatum(result);
+ return result;
}
-/* ----------------------------------------------------------------
- * ExecEvalNot
- * ExecEvalOr
- * ExecEvalAnd
- *
- * Evaluate boolean expressions. Evaluation of 'or' is
- * short-circuited when the first true (or null) value is found.
+/*
+ * ExecEvalScalarArrayOp
*
- * The query planner reformulates clause expressions in the
- * qualification to conjunctive normal form. If we ever get
- * an AND to evaluate, we can be sure that it's not a top-level
- * clause in the qualification, but appears lower (as a function
- * argument, for example), or in the target list. Not that you
- * need to know this, mind you...
- * ----------------------------------------------------------------
+ * Evaluate "scalar op ANY/ALL (array)". The operator always yields boolean,
+ * and we combine the results across all array elements using OR and AND
+ * (for ANY and ALL respectively). Of course we short-circuit as soon as
+ * the result is known.
*/
static Datum
-ExecEvalNot(Expr *notclause, ExprContext *econtext, bool *isNull)
+ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate,
+ ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
{
- Node *clause;
- Datum expr_value;
-
- clause = lfirst(notclause->args);
+ ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) sstate->fxprstate.xprstate.expr;
+ bool useOr = opexpr->useOr;
+ ArrayType *arr;
+ int nitems;
+ Datum result;
+ bool resultnull;
+ FunctionCallInfoData fcinfo;
+ ExprDoneCond argDone;
+ int i;
+ int16 typlen;
+ bool typbyval;
+ char typalign;
+ char *s;
+ bits8 *bitmap;
+ int bitmask;
- expr_value = ExecEvalExpr(clause, econtext, isNull, NULL);
+ /* Set default values for result flags: non-null, not a set result */
+ *isNull = false;
+ if (isDone)
+ *isDone = ExprSingleResult;
/*
- * if the expression evaluates to null, then we just cascade the null
- * back to whoever called us.
+ * Initialize function cache if first time through
*/
- if (*isNull)
- return expr_value;
+ if (sstate->fxprstate.func.fn_oid == InvalidOid)
+ {
+ init_fcache(opexpr->opfuncid, &sstate->fxprstate,
+ econtext->ecxt_per_query_memory, true);
+ Assert(!sstate->fxprstate.func.fn_retset);
+ }
+
+ /* Need to prep callinfo structure */
+ 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")));
+ Assert(fcinfo.nargs == 2);
/*
- * evaluation of 'not' is simple.. expr is false, then return 'true'
- * and vice versa.
+ * 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.
*/
- return BoolGetDatum(!DatumGetBool(expr_value));
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalOr
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalOr(Expr *orExpr, ExprContext *econtext, bool *isNull)
-{
- List *clauses;
- List *clause;
- bool AnyNull;
- Datum clause_value;
+ if (fcinfo.argnull[1])
+ {
+ *isNull = true;
+ return (Datum) 0;
+ }
+ /* Else okay to fetch and detoast the array */
+ arr = DatumGetArrayTypeP(fcinfo.arg[1]);
- clauses = orExpr->args;
- AnyNull = false;
+ /*
+ * 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.
+ */
+ nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+ if (nitems <= 0)
+ return BoolGetDatum(!useOr);
/*
- * 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 the scalar is NULL, and the function is strict, return NULL; no
+ * point in iterating the loop.
*/
- foreach(clause, clauses)
+ if (fcinfo.argnull[0] && sstate->fxprstate.func.fn_strict)
{
- clause_value = ExecEvalExpr((Node *) lfirst(clause),
- econtext, isNull, NULL);
-
- /*
- * if we have a non-null true result, then return it.
- */
- if (*isNull)
- AnyNull = true; /* remember we got a null */
- else if (DatumGetBool(clause_value))
- return clause_value;
+ *isNull = true;
+ return (Datum) 0;
}
- /* AnyNull is true if at least one clause evaluated to NULL */
- *isNull = AnyNull;
+ /*
+ * 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))
+ {
+ get_typlenbyvalalign(ARR_ELEMTYPE(arr),
+ &sstate->typlen,
+ &sstate->typbyval,
+ &sstate->typalign);
+ sstate->element_type = ARR_ELEMTYPE(arr);
+ }
+ typlen = sstate->typlen;
+ typbyval = sstate->typbyval;
+ typalign = sstate->typalign;
+
+ result = BoolGetDatum(!useOr);
+ resultnull = false;
+
+ /* 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, 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_pointer(s, typlen, s);
+ s = (char *) att_align_nominal(s, typalign);
+ fcinfo.arg[1] = elt;
+ fcinfo.argnull[1] = false;
+ }
+
+ /* Call comparison function */
+ 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)
+ resultnull = true;
+ else if (useOr)
+ {
+ if (DatumGetBool(thisresult))
+ {
+ result = BoolGetDatum(true);
+ resultnull = false;
+ break; /* needn't look at any more elements */
+ }
+ }
+ else
+ {
+ if (!DatumGetBool(thisresult))
+ {
+ result = BoolGetDatum(false);
+ resultnull = false;
+ 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;
+ return result;
+}
+
+/* ----------------------------------------------------------------
+ * ExecEvalNot
+ * ExecEvalOr
+ * ExecEvalAnd
+ *
+ * Evaluate boolean expressions, with appropriate short-circuiting.
+ *
+ * The query planner reformulates clause expressions in the
+ * qualification to conjunctive normal form. If we ever get
+ * an AND to evaluate, we can be sure that it's not a top-level
+ * clause in the qualification, but appears lower (as a function
+ * argument, for example), or in the target list. Not that you
+ * need to know this, mind you...
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalNot(BoolExprState *notclause, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
+{
+ ExprState *clause = linitial(notclause->args);
+ Datum expr_value;
+
+ 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 (*isNull)
+ return expr_value;
+
+ /*
+ * evaluation of 'not' is simple.. expr is false, then return 'true' and
+ * vice versa.
+ */
+ return BoolGetDatum(!DatumGetBool(expr_value));
+}
+
+/* ----------------------------------------------------------------
+ * ExecEvalOr
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalOr(BoolExprState *orExpr, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
+{
+ List *clauses = orExpr->args;
+ ListCell *clause;
+ bool AnyNull;
+
+ 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.
+ */
+ foreach(clause, clauses)
+ {
+ 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.
+ */
+ if (*isNull)
+ AnyNull = true; /* remember we got a null */
+ else if (DatumGetBool(clause_value))
+ return clause_value;
+ }
+
+ /* AnyNull is true if at least one clause evaluated to NULL */
+ *isNull = AnyNull;
return BoolGetDatum(false);
}
* ----------------------------------------------------------------
*/
static Datum
-ExecEvalAnd(Expr *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((Node *) 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.
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
*
* ----------------------------------------------------------------
*/
static Datum
-ExecEvalCase(CaseExpr *caseExpr, ExprContext *econtext,
+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;
+
+ 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;
- clauses = caseExpr->args;
+ 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)
{
- CaseWhen *wclause = lfirst(clause);
+ CaseWhenState *wclause = lfirst(clause);
+ Datum clause_value;
clause_value = ExecEvalExpr(wclause->expr,
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,
}
}
+ econtext->caseValue_datum = save_datum;
+ econtext->caseValue_isNull = save_isNull;
+
if (caseExpr->defresult)
{
return ExecEvalExpr(caseExpr->defresult,
return (Datum) 0;
}
-/* ----------------------------------------------------------------
- * ExecEvalNullTest
+/*
+ * ExecEvalCaseTestExpr
*
- * Evaluate a NullTest node.
- * ----------------------------------------------------------------
+ * Return the value stored by CASE.
*/
static Datum
-ExecEvalNullTest(NullTest *ntest,
- ExprContext *econtext,
- bool *isNull,
- ExprDoneCond *isDone)
+ExecEvalCaseTestExpr(ExprState *exprstate,
+ ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
{
- Datum result;
-
- result = ExecEvalExpr(ntest->arg, econtext, isNull, isDone);
- 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, "ExecEvalNullTest: unexpected nulltesttype %d",
- (int) ntest->nulltesttype);
- return (Datum) 0; /* keep compiler quiet */
- }
+ if (isDone)
+ *isDone = ExprSingleResult;
+ *isNull = econtext->caseValue_isNull;
+ return econtext->caseValue_datum;
}
/* ----------------------------------------------------------------
- * ExecEvalBooleanTest
- *
- * Evaluate a BooleanTest node.
+ * ExecEvalArray - ARRAY[] expressions
* ----------------------------------------------------------------
*/
static Datum
-ExecEvalBooleanTest(BooleanTest *btest,
- ExprContext *econtext,
- bool *isNull,
- ExprDoneCond *isDone)
+ExecEvalArray(ArrayExprState *astate, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
{
- Datum result;
+ ArrayExpr *arrayExpr = (ArrayExpr *) astate->xprstate.expr;
+ ArrayType *result;
+ ListCell *element;
+ Oid element_type = arrayExpr->element_typeid;
+ int ndims = 0;
+ int dims[MAXDIM];
+ int lbs[MAXDIM];
- result = ExecEvalExpr(btest->arg, econtext, isNull, isDone);
- switch (btest->booltesttype)
+ /* Set default values for result flags: non-null, not a set result */
+ *isNull = false;
+ if (isDone)
+ *isDone = ExprSingleResult;
+
+ if (!arrayExpr->multidims)
{
- case IS_TRUE:
- if (*isNull)
+ /* Elements are presumably of scalar type */
+ int nelems;
+ Datum *dvalues;
+ bool *dnulls;
+ int i = 0;
+
+ ndims = 1;
+ nelems = list_length(astate->elements);
+
+ /* 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)
+ {
+ 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)
{
- *isNull = false;
- return BoolGetDatum(false);
+ haveempty = true;
+ continue;
}
- else if (DatumGetBool(result))
- return BoolGetDatum(true);
- else
- return BoolGetDatum(false);
- case IS_NOT_TRUE:
- if (*isNull)
+
+ 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)
{
- *isNull = false;
- return BoolGetDatum(true);
+ haveempty = true;
+ continue;
}
- else if (DatumGetBool(result))
- return BoolGetDatum(false);
- else
- return BoolGetDatum(true);
- case IS_FALSE:
- if (*isNull)
+
+ if (firstone)
{
- *isNull = false;
- return BoolGetDatum(false);
+ /* 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 if (DatumGetBool(result))
- return BoolGetDatum(false);
else
- return BoolGetDatum(true);
- case IS_NOT_FALSE:
- if (*isNull)
{
- *isNull = false;
- return BoolGetDatum(true);
+ /* 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")));
}
- else if (DatumGetBool(result))
- return BoolGetDatum(true);
- else
- return BoolGetDatum(false);
- case IS_UNKNOWN:
- if (*isNull)
- {
+
+ 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);
+ }
+
+ result = (ArrayType *) palloc(nbytes);
+ SET_VARSIZE(result, 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;
+ Datum value;
+ bool isnull;
+ ListCell *arg;
+ ListCell *narg;
+
+ 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));
+ }
+
+ if (list_length(values) > 0)
+ {
+ *isNull = false;
+ return PointerGetDatum(xmlconcat(values));
+ }
+ else
+ return (Datum) 0;
+ }
+ break;
+
+ case IS_XMLFOREST:
+ {
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+ forboth(arg, xmlExpr->named_args, narg, xexpr->arg_names)
+ {
+ ExprState *e = (ExprState *) lfirst(arg);
+ char *argname = strVal(lfirst(narg));
+
+ value = ExecEvalExpr(e, econtext, &isnull, NULL);
+ if (!isnull)
+ {
+ appendStringInfo(&buf, "<%s>%s</%s>",
+ argname,
+ map_sql_value_to_xml_value(value, exprType((Node *) e->expr)),
+ argname);
+ *isNull = false;
+ }
+ }
+
+ if (*isNull)
+ {
+ pfree(buf.data);
+ return (Datum) 0;
+ }
+ else
+ {
+ text *result;
+
+ result = cstring_to_text_with_len(buf.data, buf.len);
+ pfree(buf.data);
+
+ return PointerGetDatum(result);
+ }
+ }
+ break;
+
+ case IS_XMLELEMENT:
+ *isNull = false;
+ return PointerGetDatum(xmlelement(xmlExpr, econtext));
+ break;
+
+ case IS_XMLPARSE:
+ {
+ ExprState *e;
+ text *data;
+ bool preserve_whitespace;
+
+ /* arguments are known to be text, bool */
+ Assert(list_length(xmlExpr->args) == 2);
+
+ e = (ExprState *) linitial(xmlExpr->args);
+ value = ExecEvalExpr(e, econtext, &isnull, NULL);
+ if (isnull)
+ return (Datum) 0;
+ data = DatumGetTextP(value);
+
+ e = (ExprState *) lsecond(xmlExpr->args);
+ value = ExecEvalExpr(e, econtext, &isnull, NULL);
+ if (isnull) /* probably can't happen */
+ return (Datum) 0;
+ preserve_whitespace = DatumGetBool(value);
+
+ *isNull = false;
+
+ return PointerGetDatum(xmlparse(data,
+ xexpr->xmloption,
+ preserve_whitespace));
+ }
+ break;
+
+ case IS_XMLPI:
+ {
+ ExprState *e;
+ text *arg;
+
+ /* optional argument is known to be text */
+ Assert(list_length(xmlExpr->args) <= 1);
+
+ 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));
+ }
+ break;
+
+ case IS_XMLROOT:
+ {
+ 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;
+
+ case IS_XMLSERIALIZE:
+ {
+ ExprState *e;
+
+ /* argument type is known to be xml */
+ Assert(list_length(xmlExpr->args) == 1);
+
+ e = (ExprState *) linitial(xmlExpr->args);
+ value = ExecEvalExpr(e, econtext, &isnull, NULL);
+ if (isnull)
+ return (Datum) 0;
+
+ *isNull = false;
+
+ return PointerGetDatum(xmltotext_with_xmloption(DatumGetXmlP(value), xexpr->xmloption));
+ }
+ break;
+
+ case IS_DOCUMENT:
+ {
+ ExprState *e;
+
+ /* optional argument is known to be xml */
+ Assert(list_length(xmlExpr->args) == 1);
+
+ 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;
+ }
+
+ elog(ERROR, "unrecognized XML operation");
+ return (Datum) 0;
+}
+
+/* ----------------------------------------------------------------
+ * ExecEvalNullIf
+ *
+ * Note that this is *always* derived from the equals operator,
+ * but since we need special processing of the arguments
+ * we can not simply reuse ExecEvalOper() or ExecEvalFunc().
+ * ----------------------------------------------------------------
+ */
+static Datum
+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 (nullIfExpr->func.fn_oid == InvalidOid)
+ {
+ NullIfExpr *op = (NullIfExpr *) nullIfExpr->xprstate.expr;
+
+ init_fcache(op->opfuncid, nullIfExpr,
+ econtext->ecxt_per_query_memory, true);
+ Assert(!nullIfExpr->func.fn_retset);
+ }
+
+ /*
+ * extract info from nullIfExpr
+ */
+ argList = nullIfExpr->args;
+
+ /* Need to prep callinfo structure */
+ InitFunctionCallInfoData(fcinfo, &(nullIfExpr->func), 0, NULL, NULL);
+ argDone = ExecEvalFuncArgs(&fcinfo, argList, econtext);
+ if (argDone != ExprSingleResult)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("NULLIF does not support set arguments")));
+ Assert(fcinfo.nargs == 2);
+
+ /* if either argument is NULL they can't be equal */
+ if (!fcinfo.argnull[0] && !fcinfo.argnull[1])
+ {
+ fcinfo.isnull = false;
+ result = FunctionCallInvoke(&fcinfo);
+ /* if the arguments are equal return null */
+ if (!fcinfo.isnull && DatumGetBool(result))
+ {
+ *isNull = true;
+ return (Datum) 0;
+ }
+ }
+
+ /* else return first argument */
+ *isNull = fcinfo.argnull[0];
+ return fcinfo.arg[0];
+}
+
+/* ----------------------------------------------------------------
+ * ExecEvalNullTest
+ *
+ * Evaluate a NullTest node.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalNullTest(NullTestState *nstate,
+ ExprContext *econtext,
+ bool *isNull,
+ ExprDoneCond *isDone)
+{
+ NullTest *ntest = (NullTest *) nstate->xprstate.expr;
+ Datum result;
+
+ result = ExecEvalExpr(nstate->arg, econtext, isNull, isDone);
+
+ if (isDone && *isDone == ExprEndResult)
+ return result; /* nothing to check */
+
+ if (nstate->argisrow && !(*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))
+ {
+ /* null field disproves IS NOT NULL */
+ if (ntest->nulltesttype == IS_NOT_NULL)
+ return BoolGetDatum(false);
+ }
+ else
+ {
+ /* non-null field disproves IS NULL */
+ if (ntest->nulltesttype == IS_NULL)
+ return BoolGetDatum(false);
+ }
+ }
+
+ 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 */
+ }
+ }
+}
+
+/* ----------------------------------------------------------------
+ * ExecEvalBooleanTest
+ *
+ * Evaluate a BooleanTest node.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalBooleanTest(GenericExprState *bstate,
+ ExprContext *econtext,
+ bool *isNull,
+ ExprDoneCond *isDone)
+{
+ BooleanTest *btest = (BooleanTest *) bstate->xprstate.expr;
+ Datum result;
+
+ result = ExecEvalExpr(bstate->arg, econtext, isNull, isDone);
+
+ if (isDone && *isDone == ExprEndResult)
+ return result; /* nothing to check */
+
+ switch (btest->booltesttype)
+ {
+ case IS_TRUE:
+ if (*isNull)
+ {
+ *isNull = false;
+ return BoolGetDatum(false);
+ }
+ else if (DatumGetBool(result))
+ return BoolGetDatum(true);
+ else
+ return BoolGetDatum(false);
+ case IS_NOT_TRUE:
+ if (*isNull)
+ {
+ *isNull = false;
+ return BoolGetDatum(true);
+ }
+ else if (DatumGetBool(result))
+ return BoolGetDatum(false);
+ else
+ return BoolGetDatum(true);
+ case IS_FALSE:
+ if (*isNull)
+ {
+ *isNull = false;
+ return BoolGetDatum(false);
+ }
+ else if (DatumGetBool(result))
+ return BoolGetDatum(false);
+ else
+ return BoolGetDatum(true);
+ case IS_NOT_FALSE:
+ if (*isNull)
+ {
+ *isNull = false;
+ return BoolGetDatum(true);
+ }
+ else if (DatumGetBool(result))
+ return BoolGetDatum(true);
+ else
+ return BoolGetDatum(false);
+ case IS_UNKNOWN:
+ if (*isNull)
+ {
*isNull = false;
return BoolGetDatum(true);
}
else
return BoolGetDatum(true);
default:
- elog(ERROR, "ExecEvalBooleanTest: unexpected booltesttype %d",
+ elog(ERROR, "unrecognized booltesttype: %d",
(int) btest->booltesttype);
return (Datum) 0; /* keep compiler quiet */
}
}
-/*
- * ExecEvalConstraintTest
+/*
+ * ExecEvalCoerceToDomain
+ *
+ * Test the provided data against the domain constraint(s). If the data
+ * passes the constraint specifications, pass it through (return the
+ * datum) otherwise throw an error.
+ */
+static Datum
+ExecEvalCoerceToDomain(CoerceToDomainState *cstate, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
+{
+ CoerceToDomain *ctest = (CoerceToDomain *) cstate->xprstate.expr;
+ Datum result;
+ ListCell *l;
+
+ result = ExecEvalExpr(cstate->arg, econtext, isNull, isDone);
+
+ if (isDone && *isDone == ExprEndResult)
+ return result; /* nothing to check */
+
+ foreach(l, cstate->constraints)
+ {
+ DomainConstraintState *con = (DomainConstraintState *) lfirst(l);
+
+ switch (con->constrainttype)
+ {
+ case DOM_CONSTRAINT_NOTNULL:
+ if (*isNull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NOT_NULL_VIOLATION),
+ errmsg("domain %s does not allow null values",
+ format_type_be(ctest->resulttype))));
+ break;
+ case DOM_CONSTRAINT_CHECK:
+ {
+ Datum conResult;
+ bool conIsNull;
+ Datum save_datum;
+ bool save_isNull;
+
+ /*
+ * 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.
+ */
+ save_datum = econtext->domainValue_datum;
+ save_isNull = econtext->domainValue_isNull;
+
+ econtext->domainValue_datum = result;
+ econtext->domainValue_isNull = *isNull;
+
+ conResult = ExecEvalExpr(con->check_expr,
+ econtext, &conIsNull, NULL);
+
+ if (!conIsNull &&
+ !DatumGetBool(conResult))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("value for domain %s violates check constraint \"%s\"",
+ format_type_be(ctest->resulttype),
+ con->name)));
+ econtext->domainValue_datum = save_datum;
+ econtext->domainValue_isNull = save_isNull;
+
+ break;
+ }
+ default:
+ elog(ERROR, "unrecognized constraint type: %d",
+ (int) con->constrainttype);
+ break;
+ }
+ }
+
+ /* If all has gone well (constraints did not fail) return the datum */
+ return result;
+}
+
+/*
+ * ExecEvalCoerceToDomainValue
+ *
+ * Return the value stored by CoerceToDomain.
+ */
+static Datum
+ExecEvalCoerceToDomainValue(ExprState *exprstate,
+ ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
+{
+ if (isDone)
+ *isDone = ExprSingleResult;
+ *isNull = econtext->domainValue_isNull;
+ return econtext->domainValue_datum;
+}
+
+/* ----------------------------------------------------------------
+ * ExecEvalFieldSelect
+ *
+ * Evaluate a FieldSelect node.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalFieldSelect(FieldSelectState *fstate,
+ ExprContext *econtext,
+ bool *isNull,
+ ExprDoneCond *isDone)
+{
+ FieldSelect *fselect = (FieldSelect *) fstate->xprstate.expr;
+ AttrNumber fieldnum = fselect->fieldnum;
+ Datum result;
+ Datum tupDatum;
+ HeapTupleHeader tuple;
+ Oid tupType;
+ int32 tupTypmod;
+ TupleDesc tupDesc;
+ Form_pg_attribute attr;
+ HeapTupleData tmptup;
+
+ tupDatum = ExecEvalExpr(fstate->arg, econtext, isNull, isDone);
+
+ /* this test covers the isDone exception too: */
+ if (*isNull)
+ 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))));
+
+ /*
+ * 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;
+}
+
+/* ----------------------------------------------------------------
+ * ExecEvalFieldStore
*
- * Test the constraint against the data provided. If the data fits
- * within the constraint specifications, pass it through (return the
- * datum) otherwise throw an error.
+ * Evaluate a FieldStore node.
+ * ----------------------------------------------------------------
*/
static Datum
-ExecEvalConstraintTest(ConstraintTest *constraint, ExprContext *econtext,
- bool *isNull, ExprDoneCond *isDone)
+ExecEvalFieldStore(FieldStoreState *fstate,
+ ExprContext *econtext,
+ bool *isNull,
+ ExprDoneCond *isDone)
{
- Datum result;
+ 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));
+
+ 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;
- result = ExecEvalExpr(constraint->arg, econtext, isNull, isDone);
+ tuphdr = DatumGetHeapTupleHeader(tupDatum);
+ tmptup.t_len = HeapTupleHeaderGetDatumLength(tuphdr);
+ ItemPointerSetInvalid(&(tmptup.t_self));
+ tmptup.t_tableOid = InvalidOid;
+ tmptup.t_data = tuphdr;
- switch (constraint->testtype)
+ heap_deform_tuple(&tmptup, tupDesc, values, isnull);
+ }
+ else
{
- case CONSTR_TEST_NOTNULL:
- if (*isNull)
- elog(ERROR, "Domain %s does not allow NULL values",
- constraint->name);
- break;
- case CONSTR_TEST_CHECK:
- /* TODO: Add CHECK Constraints to domains */
- elog(ERROR, "Domain CHECK Constraints not yet implemented");
- break;
- default:
- elog(ERROR, "ExecEvalConstraintTest: Constraint type unknown");
- break;
+ /* Convert null input tuple into an all-nulls row */
+ memset(isnull, true, tupDesc->natts * sizeof(bool));
}
- /* If all has gone well (constraint did not fail) return the datum */
- return result;
+ /* Result is never null */
+ *isNull = false;
+
+ save_datum = econtext->caseValue_datum;
+ save_isNull = econtext->caseValue_isNull;
+
+ forboth(l1, fstate->newvals, l2, fstore->fieldnums)
+ {
+ ExprState *newval = (ExprState *) lfirst(l1);
+ AttrNumber fieldnum = lfirst_int(l2);
+
+ Assert(fieldnum > 0 && fieldnum <= tupDesc->natts);
+
+ /*
+ * 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);
+ }
+
+ econtext->caseValue_datum = save_datum;
+ econtext->caseValue_isNull = save_isNull;
+
+ tuple = heap_form_tuple(tupDesc, values, isnull);
+
+ pfree(values);
+ pfree(isnull);
+
+ return HeapTupleGetDatum(tuple);
}
/* ----------------------------------------------------------------
- * ExecEvalFieldSelect
+ * ExecEvalRelabelType
*
- * Evaluate a FieldSelect node.
+ * Evaluate a RelabelType node.
* ----------------------------------------------------------------
*/
static Datum
-ExecEvalFieldSelect(FieldSelect *fselect,
+ExecEvalRelabelType(GenericExprState *exprstate,
ExprContext *econtext,
- bool *isNull,
- ExprDoneCond *isDone)
+ bool *isNull, ExprDoneCond *isDone)
+{
+ return ExecEvalExpr(exprstate->arg, econtext, isNull, isDone);
+}
+
+/* ----------------------------------------------------------------
+ * ExecEvalCoerceViaIO
+ *
+ * Evaluate a CoerceViaIO node.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalCoerceViaIO(CoerceViaIOState *iostate,
+ ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
{
Datum result;
- TupleTableSlot *resSlot;
+ Datum inputval;
+ char *string;
+
+ inputval = ExecEvalExpr(iostate->arg, econtext, isNull, isDone);
+
+ if (isDone && *isDone == ExprEndResult)
+ return inputval; /* nothing to do */
- result = ExecEvalExpr(fselect->arg, econtext, isNull, isDone);
if (*isNull)
- return result;
- resSlot = (TupleTableSlot *) DatumGetPointer(result);
- Assert(resSlot != NULL && IsA(resSlot, TupleTableSlot));
- result = heap_getattr(resSlot->val,
- fselect->fieldnum,
- resSlot->ttc_tupleDescriptor,
- isNull);
+ string = NULL; /* output functions are not called on nulls */
+ else
+ string = OutputFunctionCall(&iostate->outfunc, inputval);
+
+ result = InputFunctionCall(&iostate->infunc,
+ string,
+ iostate->intypioparam,
+ -1);
+
+ /* The input function cannot change the null/not-null status */
return result;
}
/* ----------------------------------------------------------------
- * ExecEvalExpr
+ * ExecEvalArrayCoerceExpr
*
- * Recursively evaluate a targetlist or qualification expression.
+ * Evaluate an ArrayCoerceExpr node.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate,
+ ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
+{
+ ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) astate->xprstate.expr;
+ Datum result;
+ ArrayType *array;
+ FunctionCallInfoData locfcinfo;
+
+ result = ExecEvalExpr(astate->arg, econtext, isNull, isDone);
+
+ if (isDone && *isDone == ExprEndResult)
+ return result; /* nothing to do */
+ if (*isNull)
+ return result; /* nothing to do */
+
+ /*
+ * If it's binary-compatible, modify the element type in the array header,
+ * but otherwise leave the array as we received it.
+ */
+ if (!OidIsValid(acoerce->elemfuncid))
+ {
+ /* Detoast input array if necessary, and copy in any case */
+ array = DatumGetArrayTypePCopy(result);
+ ARR_ELEMTYPE(array) = astate->resultelemtype;
+ PG_RETURN_ARRAYTYPE_P(array);
+ }
+
+ /* Detoast input array if necessary, but don't make a useless copy */
+ array = DatumGetArrayTypeP(result);
+
+ /* Initialize function cache if first time through */
+ if (astate->elemfunc.fn_oid == InvalidOid)
+ {
+ AclResult aclresult;
+
+ /* Check permission to call function */
+ aclresult = pg_proc_aclcheck(acoerce->elemfuncid, GetUserId(),
+ ACL_EXECUTE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_PROC,
+ get_func_name(acoerce->elemfuncid));
+
+ /* Set up the primary fmgr lookup information */
+ fmgr_info_cxt(acoerce->elemfuncid, &(astate->elemfunc),
+ econtext->ecxt_per_query_memory);
+
+ /* Initialize additional info */
+ astate->elemfunc.fn_expr = (Node *) acoerce;
+ }
+
+ /*
+ * Use array_map to apply the function to each array element.
+ *
+ * We pass on the desttypmod and isExplicit flags whether or not the
+ * function wants them.
+ */
+ InitFunctionCallInfoData(locfcinfo, &(astate->elemfunc), 3,
+ NULL, NULL);
+ locfcinfo.arg[0] = PointerGetDatum(array);
+ locfcinfo.arg[1] = Int32GetDatum(acoerce->resulttypmod);
+ locfcinfo.arg[2] = BoolGetDatum(acoerce->isExplicit);
+ locfcinfo.argnull[0] = false;
+ locfcinfo.argnull[1] = false;
+ locfcinfo.argnull[2] = false;
+
+ return array_map(&locfcinfo, ARR_ELEMTYPE(array), astate->resultelemtype,
+ astate->amstate);
+}
+
+/* ----------------------------------------------------------------
+ * ExecEvalCurrentOfExpr
*
- * Inputs:
- * expression: the expression tree to evaluate
- * econtext: evaluation context information
+ * The planner must convert CURRENT OF into a TidScan qualification.
+ * So, we have to be able to do ExecInitExpr on a CurrentOfExpr,
+ * but we shouldn't ever actually execute it.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
+{
+ elog(ERROR, "CURRENT OF cannot be executed");
+ return 0; /* keep compiler quiet */
+}
+
+
+/*
+ * ExecEvalExprSwitchContext
*
- * 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
+ * Same as ExecEvalExpr, but get into the right allocation context explicitly.
+ */
+Datum
+ExecEvalExprSwitchContext(ExprState *expression,
+ ExprContext *econtext,
+ bool *isNull,
+ ExprDoneCond *isDone)
+{
+ Datum retDatum;
+ MemoryContext oldContext;
+
+ oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ retDatum = ExecEvalExpr(expression, econtext, isNull, isDone);
+ MemoryContextSwitchTo(oldContext);
+ return retDatum;
+}
+
+
+/*
+ * ExecInitExpr: prepare an expression tree for execution
*
- * 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 elog()
- * error will be reported. 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.
+ * This function builds and returns an ExprState tree paralleling the given
+ * Expr node tree. The ExprState tree can then be handed to ExecEvalExpr
+ * for execution. Because the Expr tree itself is read-only as far as
+ * ExecInitExpr and ExecEvalExpr are concerned, several different executions
+ * of the same plan tree can occur concurrently.
*
- * 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().
+ * This must be called in a memory context that will last as long as repeated
+ * executions of the expression are needed. Typically the context will be
+ * the same as the per-query context of the associated ExprContext.
*
- * This routine is an inner loop routine and must be as fast as possible.
- * ----------------------------------------------------------------
+ * Any Aggref, WindowFunc, or SubPlan nodes found in the tree are added to the
+ * lists of such nodes held by the parent PlanState. Otherwise, we do very
+ * little initialization here other than building the state-node tree. Any
+ * nontrivial work associated with initializing runtime info for a node should
+ * happen during the first actual evaluation of that node. (This policy lets
+ * us avoid work if the node is never actually evaluated.)
+ *
+ * Note: there is no ExecEndExpr function; we assume that any resource
+ * cleanup needed will be handled by just releasing the memory context
+ * in which the state tree is built. Functions that require additional
+ * cleanup work can register a shutdown callback in the ExprContext.
+ *
+ * 'node' is the root of the expression tree to examine
+ * 'parent' is the PlanState node that owns the expression.
+ *
+ * 'parent' may be NULL if we are preparing an expression that is not
+ * associated with a plan tree. (If so, it can't have aggs or subplans.)
+ * This case should usually come through ExecPrepareExpr, not directly here.
*/
-Datum
-ExecEvalExpr(Node *expression,
- ExprContext *econtext,
- bool *isNull,
- ExprDoneCond *isDone)
+ExprState *
+ExecInitExpr(Expr *node, PlanState *parent)
{
- Datum retDatum;
+ ExprState *state;
+
+ 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:
+ 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;
+ int naggs;
+
+ aggstate->aggs = lcons(astate, aggstate->aggs);
+ naggs = ++aggstate->numaggs;
+
+ astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
+ parent);
+
+ /*
+ * 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.)
+ */
+ if (naggs != aggstate->numaggs)
+ ereport(ERROR,
+ (errcode(ERRCODE_GROUPING_ERROR),
+ errmsg("aggregate function calls cannot be nested")));
+ }
+ else
+ {
+ /* planner messed up */
+ elog(ERROR, "Aggref found in non-Agg plan node");
+ }
+ state = (ExprState *) astate;
+ }
+ break;
+ case T_WindowFunc:
+ {
+ WindowFunc *wfunc = (WindowFunc *) node;
+ WindowFuncExprState *wfstate = makeNode(WindowFuncExprState);
+
+ wfstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWindowFunc;
+ if (parent && IsA(parent, WindowAggState))
+ {
+ WindowAggState *winstate = (WindowAggState *) parent;
+ int nfuncs;
+
+ winstate->funcs = lcons(wfstate, winstate->funcs);
+ nfuncs = ++winstate->numfuncs;
+ if (wfunc->winagg)
+ winstate->numaggs++;
+
+ wfstate->args = (List *) ExecInitExpr((Expr *) wfunc->args,
+ parent);
+
+ /*
+ * Complain if the windowfunc's arguments contain any
+ * windowfuncs; nested window functions are semantically
+ * nonsensical. (This should have been caught earlier,
+ * but we defend against it here anyway.)
+ */
+ if (nfuncs != winstate->numfuncs)
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("window function calls cannot be nested")));
+ }
+ else
+ {
+ /* planner messed up */
+ elog(ERROR, "WindowFunc found in non-WindowAgg plan node");
+ }
+ state = (ExprState *) wfstate;
+ }
+ break;
+ case T_ArrayRef:
+ {
+ ArrayRef *aref = (ArrayRef *) node;
+ ArrayRefExprState *astate = makeNode(ArrayRefExprState);
+
+ astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArrayRef;
+ astate->refupperindexpr = (List *)
+ ExecInitExpr((Expr *) aref->refupperindexpr, parent);
+ astate->reflowerindexpr = (List *)
+ ExecInitExpr((Expr *) aref->reflowerindexpr, parent);
+ astate->refexpr = ExecInitExpr(aref->refexpr, parent);
+ astate->refassgnexpr = ExecInitExpr(aref->refassgnexpr,
+ parent);
+ /* do one-time catalog lookups for type info */
+ astate->refattrlength = get_typlen(aref->refarraytype);
+ get_typlenbyvalalign(aref->refelemtype,
+ &astate->refelemlength,
+ &astate->refelembyval,
+ &astate->refelemalign);
+ state = (ExprState *) astate;
+ }
+ break;
+ case T_FuncExpr:
+ {
+ 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 */
+ state = (ExprState *) fstate;
+ }
+ break;
+ case T_OpExpr:
+ {
+ 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 */
+ state = (ExprState *) fstate;
+ }
+ break;
+ case T_DistinctExpr:
+ {
+ 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 */
+ state = (ExprState *) fstate;
+ }
+ break;
+ case T_ScalarArrayOpExpr:
+ {
+ 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 */
+ sstate->element_type = InvalidOid; /* ditto */
+ state = (ExprState *) sstate;
+ }
+ break;
+ case T_BoolExpr:
+ {
+ 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;
+ }
+ break;
+ case T_SubPlan:
+ {
+ SubPlan *subplan = (SubPlan *) node;
+ SubPlanState *sstate;
+
+ if (!parent)
+ elog(ERROR, "SubPlan found with no parent plan");
+
+ sstate = ExecInitSubPlan(subplan, parent);
+
+ /* Add SubPlanState nodes to parent->subPlan */
+ parent->subPlan = lappend(parent->subPlan, sstate);
+
+ state = (ExprState *) sstate;
+ }
+ break;
+ case T_AlternativeSubPlan:
+ {
+ AlternativeSubPlan *asplan = (AlternativeSubPlan *) node;
+ AlternativeSubPlanState *asstate;
+
+ if (!parent)
+ elog(ERROR, "AlternativeSubPlan found with no parent plan");
+
+ asstate = ExecInitAlternativeSubPlan(asplan, parent);
+
+ state = (ExprState *) asstate;
+ }
+ break;
+ case T_FieldSelect:
+ {
+ FieldSelect *fselect = (FieldSelect *) node;
+ FieldSelectState *fstate = makeNode(FieldSelectState);
+
+ 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:
+ {
+ 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_CoerceViaIO:
+ {
+ CoerceViaIO *iocoerce = (CoerceViaIO *) node;
+ CoerceViaIOState *iostate = makeNode(CoerceViaIOState);
+ Oid iofunc;
+ bool typisvarlena;
+
+ iostate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoerceViaIO;
+ iostate->arg = ExecInitExpr(iocoerce->arg, parent);
+ /* lookup the result type's input function */
+ getTypeInputInfo(iocoerce->resulttype, &iofunc,
+ &iostate->intypioparam);
+ fmgr_info(iofunc, &iostate->infunc);
+ /* lookup the input type's output function */
+ getTypeOutputInfo(exprType((Node *) iocoerce->arg),
+ &iofunc, &typisvarlena);
+ fmgr_info(iofunc, &iostate->outfunc);
+ state = (ExprState *) iostate;
+ }
+ break;
+ case T_ArrayCoerceExpr:
+ {
+ ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
+ ArrayCoerceExprState *astate = makeNode(ArrayCoerceExprState);
+
+ astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArrayCoerceExpr;
+ astate->arg = ExecInitExpr(acoerce->arg, parent);
+ astate->resultelemtype = get_element_type(acoerce->resulttype);
+ if (astate->resultelemtype == InvalidOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("target type is not an array")));
+ /* Arrays over domains aren't supported yet */
+ Assert(getBaseType(astate->resultelemtype) ==
+ astate->resultelemtype);
+ astate->elemfunc.fn_oid = InvalidOid; /* not initialized */
+ astate->amstate = (ArrayMapState *) palloc0(sizeof(ArrayMapState));
+ state = (ExprState *) astate;
+ }
+ 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);
+ List *outlist = NIL;
+ ListCell *l;
+
+ cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCase;
+ cstate->arg = ExecInitExpr(caseexpr->arg, parent);
+ foreach(l, caseexpr->args)
+ {
+ 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);
+ outlist = lappend(outlist, wstate);
+ }
+ cstate->args = outlist;
+ cstate->defresult = ExecInitExpr(caseexpr->defresult, parent);
+ state = (ExprState *) cstate;
+ }
+ break;
+ case T_ArrayExpr:
+ {
+ ArrayExpr *arrayexpr = (ArrayExpr *) node;
+ ArrayExprState *astate = makeNode(ArrayExprState);
+ List *outlist = NIL;
+ ListCell *l;
- /* Set default values for result flags: non-null, not a set result */
- *isNull = false;
- if (isDone)
- *isDone = ExprSingleResult;
+ astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArray;
+ foreach(l, arrayexpr->elements)
+ {
+ Expr *e = (Expr *) lfirst(l);
+ ExprState *estate;
- /* Is this still necessary? Doubtful... */
- if (expression == NULL)
- {
- *isNull = true;
- return (Datum) 0;
- }
+ estate = ExecInitExpr(e, parent);
+ outlist = lappend(outlist, estate);
+ }
+ astate->elements = outlist;
+ /* do one-time catalog lookup for type info */
+ get_typlenbyvalalign(arrayexpr->element_typeid,
+ &astate->elemlength,
+ &astate->elembyval,
+ &astate->elemalign);
+ 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;
- /*
- * here we dispatch the work to the appropriate type of function given
- * the type of our expression.
- */
- switch (nodeTag(expression))
- {
- case T_Var:
- retDatum = ExecEvalVar((Var *) expression, econtext, isNull);
+ 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, -1);
+ }
+ estate = ExecInitExpr(e, parent);
+ outlist = lappend(outlist, estate);
+ i++;
+ }
+ rstate->args = outlist;
+ state = (ExprState *) rstate;
+ }
break;
- case T_Const:
+ case T_RowCompareExpr:
{
- Const *con = (Const *) expression;
+ 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;
- retDatum = con->constvalue;
- *isNull = con->constisnull;
- break;
+ 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;
+ Oid proc;
+
+ get_op_opfamily_properties(opno, opfamily,
+ &strategy,
+ &lefttype,
+ &righttype);
+ 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;
}
- case T_Param:
- retDatum = ExecEvalParam((Param *) expression, econtext, isNull);
break;
- case T_Aggref:
- retDatum = ExecEvalAggref((Aggref *) expression, econtext, isNull);
+ case T_CoalesceExpr:
+ {
+ CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
+ CoalesceExprState *cstate = makeNode(CoalesceExprState);
+ List *outlist = NIL;
+ ListCell *l;
+
+ cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoalesce;
+ foreach(l, coalesceexpr->args)
+ {
+ Expr *e = (Expr *) lfirst(l);
+ ExprState *estate;
+
+ estate = ExecInitExpr(e, parent);
+ outlist = lappend(outlist, estate);
+ }
+ cstate->args = outlist;
+ state = (ExprState *) cstate;
+ }
break;
- case T_ArrayRef:
- retDatum = ExecEvalArrayRef((ArrayRef *) expression,
- econtext,
- isNull,
- isDone);
+ 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_Expr:
+ case T_XmlExpr:
{
- Expr *expr = (Expr *) expression;
+ XmlExpr *xexpr = (XmlExpr *) node;
+ XmlExprState *xstate = makeNode(XmlExprState);
+ List *outlist;
+ ListCell *arg;
+
+ xstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalXml;
+ outlist = NIL;
+ foreach(arg, xexpr->named_args)
+ {
+ Expr *e = (Expr *) lfirst(arg);
+ ExprState *estate;
+
+ estate = ExecInitExpr(e, parent);
+ outlist = lappend(outlist, estate);
+ }
+ xstate->named_args = outlist;
- switch (expr->opType)
+ outlist = NIL;
+ foreach(arg, xexpr->args)
{
- case OP_EXPR:
- retDatum = ExecEvalOper(expr, econtext,
- isNull, isDone);
- break;
- case FUNC_EXPR:
- retDatum = ExecEvalFunc(expr, econtext,
- isNull, isDone);
- break;
- case OR_EXPR:
- retDatum = ExecEvalOr(expr, econtext, isNull);
- break;
- case AND_EXPR:
- retDatum = ExecEvalAnd(expr, econtext, isNull);
- break;
- case NOT_EXPR:
- retDatum = ExecEvalNot(expr, econtext, isNull);
- break;
- case DISTINCT_EXPR:
- retDatum = ExecEvalDistinct(expr, econtext,
- isNull, isDone);
- break;
- case SUBPLAN_EXPR:
- retDatum = ExecSubPlan((SubPlan *) expr->oper,
- expr->args, econtext,
- isNull);
- break;
- default:
- elog(ERROR, "ExecEvalExpr: unknown expression type %d",
- expr->opType);
- retDatum = 0; /* keep compiler quiet */
- break;
+ Expr *e = (Expr *) lfirst(arg);
+ ExprState *estate;
+
+ estate = ExecInitExpr(e, parent);
+ outlist = lappend(outlist, estate);
}
- break;
+ xstate->args = outlist;
+
+ state = (ExprState *) xstate;
}
- case T_FieldSelect:
- retDatum = ExecEvalFieldSelect((FieldSelect *) expression,
- econtext,
- isNull,
- isDone);
- break;
- case T_RelabelType:
- retDatum = ExecEvalExpr(((RelabelType *) expression)->arg,
- econtext,
- isNull,
- isDone);
break;
- case T_CaseExpr:
- retDatum = ExecEvalCase((CaseExpr *) expression,
- econtext,
- isNull,
- isDone);
+ 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 */
+ state = (ExprState *) fstate;
+ }
break;
case T_NullTest:
- retDatum = ExecEvalNullTest((NullTest *) expression,
- econtext,
- isNull,
- isDone);
+ {
+ NullTest *ntest = (NullTest *) node;
+ NullTestState *nstate = makeNode(NullTestState);
+
+ 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:
- retDatum = ExecEvalBooleanTest((BooleanTest *) expression,
- econtext,
- isNull,
- isDone);
+ {
+ BooleanTest *btest = (BooleanTest *) node;
+ GenericExprState *gstate = makeNode(GenericExprState);
+
+ gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalBooleanTest;
+ gstate->arg = ExecInitExpr(btest->arg, parent);
+ state = (ExprState *) gstate;
+ }
+ break;
+ case T_CoerceToDomain:
+ {
+ 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;
+ }
+ break;
+ case T_CurrentOfExpr:
+ state = (ExprState *) makeNode(ExprState);
+ state->evalfunc = ExecEvalCurrentOfExpr;
break;
- case T_ConstraintTest:
- retDatum = ExecEvalConstraintTest((ConstraintTest *) expression,
- econtext,
- isNull,
- isDone);
+ case T_TargetEntry:
+ {
+ 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:
+ {
+ List *outlist = NIL;
+ ListCell *l;
+ foreach(l, (List *) node)
+ {
+ outlist = lappend(outlist,
+ ExecInitExpr((Expr *) lfirst(l),
+ parent));
+ }
+ /* Don't fall through to the "common" code below */
+ return (ExprState *) outlist;
+ }
default:
- elog(ERROR, "ExecEvalExpr: unknown expression type %d",
- nodeTag(expression));
- retDatum = 0; /* keep compiler quiet */
+ elog(ERROR, "unrecognized node type: %d",
+ (int) nodeTag(node));
+ state = NULL; /* keep compiler quiet */
break;
}
- return retDatum;
-} /* ExecEvalExpr() */
+ /* Common code for all state-node types */
+ state->expr = node;
+ return state;
+}
/*
- * Same as above, but get into the right allocation context explicitly.
+ * ExecPrepareExpr --- initialize for expression execution outside a normal
+ * Plan tree context.
+ *
+ * This differs from ExecInitExpr in that we don't assume the caller is
+ * already running in the EState's per-query context. Also, we run the
+ * passed expression tree through expression_planner() to prepare it for
+ * execution. (In ordinary Plan trees the regular planning process will have
+ * made the appropriate transformations on expressions, but for standalone
+ * expressions this won't have happened.)
*/
-Datum
-ExecEvalExprSwitchContext(Node *expression,
- ExprContext *econtext,
- bool *isNull,
- ExprDoneCond *isDone)
+ExprState *
+ExecPrepareExpr(Expr *node, EState *estate)
{
- Datum retDatum;
- MemoryContext oldContext;
+ ExprState *result;
+ MemoryContext oldcontext;
- oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
- retDatum = ExecEvalExpr(expression, econtext, isNull, isDone);
- MemoryContextSwitchTo(oldContext);
- return retDatum;
+ oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ node = expression_planner(node);
+
+ result = ExecInitExpr(node, NULL);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ return result;
}
{
bool result;
MemoryContext oldContext;
- List *qlist;
+ ListCell *l;
/*
* debugging stuff
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)
{
- Node *clause = (Node *) lfirst(qlist);
+ ExprState *clause = (ExprState *) lfirst(l);
Datum expr_value;
bool isNull;
int
ExecTargetListLength(List *targetlist)
{
- int len = 0;
- List *tl;
-
- foreach(tl, targetlist)
- {
- TargetEntry *curTle = (TargetEntry *) lfirst(tl);
-
- if (curTle->resdom != NULL)
- len++;
- else
- len += curTle->fjoin->fj_nNodes;
- }
- return len;
+ /* This used to be more complex, but fjoins are dead */
+ return list_length(targetlist);
}
/*
ExecCleanTargetListLength(List *targetlist)
{
int len = 0;
- List *tl;
+ ListCell *tl;
foreach(tl, targetlist)
{
TargetEntry *curTle = (TargetEntry *) lfirst(tl);
- if (curTle->resdom != NULL)
- {
- if (!curTle->resdom->resjunk)
- len++;
- }
- else
- len += curTle->fjoin->fj_nNodes;
+ Assert(IsA(curTle, TargetEntry));
+ if (!curTle->resjunk)
+ len++;
}
return len;
}
-/* ----------------------------------------------------------------
- * ExecTargetList
+/*
+ * ExecTargetList
+ * Evaluates a targetlist with respect to the given
+ * expression context. Returns TRUE if we were able to create
+ * a result, FALSE if we have exhausted a set-valued expression.
*
- * Evaluates a targetlist with respect to the current
- * expression context and return a tuple.
+ * 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.
- * ----------------------------------------------------------------
+ * We assume that *isDone has been initialized to ExprSingleResult by caller.
*/
-static HeapTuple
+static bool
ExecTargetList(List *targetlist,
- int nodomains,
- TupleDesc targettype,
- Datum *values,
ExprContext *econtext,
+ Datum *values,
+ bool *isnull,
+ ExprDoneCond *itemIsDone,
ExprDoneCond *isDone)
{
MemoryContext oldContext;
-
-#define NPREALLOCDOMAINS 64
- char nullsArray[NPREALLOCDOMAINS];
- bool fjIsNullArray[NPREALLOCDOMAINS];
- ExprDoneCond itemIsDoneArray[NPREALLOCDOMAINS];
- char *nulls;
- bool *fjIsNull;
- ExprDoneCond *itemIsDone;
- List *tl;
- TargetEntry *tle;
- AttrNumber resind;
- HeapTuple newTuple;
- 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;
-
- /*
- * allocate an array of char's to hold the "null" information only if
- * we have a really large targetlist. otherwise we use the stack.
- *
- * We also allocate a bool array that is used to hold fjoin result state,
- * and another array that holds the isDone status for each targetlist
- * item. The isDone status is needed so that we can iterate,
- * generating multiple tuples, when one or more tlist items return
- * sets. (We expect the caller to call us again if we return:
- *
- * isDone = ExprMultipleResult.)
- */
- if (nodomains > NPREALLOCDOMAINS)
- {
- nulls = (char *) palloc(nodomains * sizeof(char));
- fjIsNull = (bool *) palloc(nodomains * sizeof(bool));
- itemIsDone = (ExprDoneCond *) palloc(nodomains * sizeof(ExprDoneCond));
- }
- else
- {
- nulls = nullsArray;
- fjIsNull = fjIsNullArray;
- itemIsDone = itemIsDoneArray;
- }
-
/*
* evaluate all the expressions in the target list
*/
-
- if (isDone)
- *isDone = ExprSingleResult; /* until proven otherwise */
-
haveDoneSets = false; /* any exhausted set exprs in tlist? */
foreach(tl, targetlist)
{
- tle = lfirst(tl);
-
- if (tle->resdom != NULL)
- {
- resind = tle->resdom->resno - 1;
+ GenericExprState *gstate = (GenericExprState *) lfirst(tl);
+ TargetEntry *tle = (TargetEntry *) gstate->xprstate.expr;
+ AttrNumber resind = tle->resno - 1;
- values[resind] = ExecEvalExpr(tle->expr,
- econtext,
- &isNull,
- &itemIsDone[resind]);
- nulls[resind] = isNull ? 'n' : ' ';
+ values[resind] = ExecEvalExpr(gstate->arg,
+ econtext,
+ &isnull[resind],
+ &itemIsDone[resind]);
- if (itemIsDone[resind] != ExprSingleResult)
- {
- /* We have a set-valued expression in the tlist */
- if (isDone == NULL)
- elog(ERROR, "Set-valued function called in context that cannot accept a set");
- if (itemIsDone[resind] == ExprMultipleResult)
- {
- /* we have undone sets in the tlist, set flag */
- *isDone = ExprMultipleResult;
- }
- else
- {
- /* we have done sets in the tlist, set flag for that */
- haveDoneSets = true;
- }
- }
- }
- else
+ if (itemIsDone[resind] != ExprSingleResult)
{
-#ifdef SETS_FIXED
- int curNode;
- Resdom *fjRes;
- List *fjTlist = (List *) tle->expr;
- Fjoin *fjNode = tle->fjoin;
- int nNodes = fjNode->fj_nNodes;
- DatumPtr results = fjNode->fj_results;
-
- ExecEvalFjoin(tle, econtext, fjIsNull, isDone);
-
- /*
- * XXX this is wrong, but since fjoin code is completely
- * broken anyway, I'm not going to worry about it now --- tgl
- * 8/23/00
- */
- if (isDone && *isDone == ExprEndResult)
+ /* We have a set-valued expression in the tlist */
+ if (isDone == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (itemIsDone[resind] == ExprMultipleResult)
{
- MemoryContextSwitchTo(oldContext);
- newTuple = NULL;
- goto exit;
+ /* we have undone sets in the tlist, set flag */
+ *isDone = ExprMultipleResult;
}
-
- /*
- * get the result from the inner node
- */
- fjRes = (Resdom *) fjNode->fj_innerNode;
- resind = fjRes->resno - 1;
- values[resind] = results[0];
- nulls[resind] = fjIsNull[0] ? 'n' : ' ';
-
- /*
- * Get results from all of the outer nodes
- */
- for (curNode = 1;
- curNode < nNodes;
- curNode++, fjTlist = lnext(fjTlist))
+ else
{
- Node *outernode = lfirst(fjTlist);
-
- fjRes = (Resdom *) outernode->iterexpr;
- resind = fjRes->resno - 1;
- values[resind] = results[curNode];
- nulls[resind] = fjIsNull[curNode] ? 'n' : ' ';
+ /* we have done sets in the tlist, set flag for that */
+ haveDoneSets = true;
}
-#else
- elog(ERROR, "ExecTargetList: fjoin nodes not currently supported");
-#endif
}
}
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);
- newTuple = NULL;
- goto exit;
+ 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)
{
- tle = lfirst(tl);
+ GenericExprState *gstate = (GenericExprState *) lfirst(tl);
+ TargetEntry *tle = (TargetEntry *) gstate->xprstate.expr;
+ AttrNumber resind = tle->resno - 1;
- if (tle->resdom != NULL)
+ if (itemIsDone[resind] == ExprEndResult)
{
- resind = tle->resdom->resno - 1;
+ values[resind] = ExecEvalExpr(gstate->arg,
+ econtext,
+ &isnull[resind],
+ &itemIsDone[resind]);
if (itemIsDone[resind] == ExprEndResult)
{
- values[resind] = ExecEvalExpr(tle->expr,
- econtext,
- &isNull,
- &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.
- */
- *isDone = ExprEndResult;
- break;
- }
+ /*
+ * Oh dear, this item is returning an empty set. Guess
+ * we can't make a tuple after all.
+ */
+ *isDone = ExprEndResult;
+ break;
}
}
}
/*
- * 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?
*/
if (*isDone == ExprEndResult)
{
foreach(tl, targetlist)
{
- tle = lfirst(tl);
+ GenericExprState *gstate = (GenericExprState *) lfirst(tl);
+ TargetEntry *tle = (TargetEntry *) gstate->xprstate.expr;
+ AttrNumber resind = tle->resno - 1;
- if (tle->resdom != NULL)
+ while (itemIsDone[resind] == ExprMultipleResult)
{
- resind = tle->resdom->resno - 1;
-
- while (itemIsDone[resind] == ExprMultipleResult)
- {
- (void) ExecEvalExpr(tle->expr,
- econtext,
- &isNull,
- &itemIsDone[resind]);
- }
+ values[resind] = ExecEvalExpr(gstate->arg,
+ econtext,
+ &isnull[resind],
+ &itemIsDone[resind]);
}
}
MemoryContextSwitchTo(oldContext);
- newTuple = NULL;
- goto exit;
+ return false;
}
}
}
- /*
- * form the new result tuple (in the caller's memory context!)
- */
+ /* Report success */
MemoryContextSwitchTo(oldContext);
- newTuple = (HeapTuple) heap_formtuple(targettype, values, nulls);
-
-exit:
-
- /*
- * free the status arrays if we palloc'd them
- */
- if (nodomains > NPREALLOCDOMAINS)
- {
- pfree(nulls);
- pfree(fjIsNull);
- pfree(itemIsDone);
- }
-
- return newTuple;
+ return true;
}
-/* ----------------------------------------------------------------
- * 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;
- List *targetlist;
- int len;
- TupleDesc tupType;
- Datum *tupValue;
ExprContext *econtext;
- HeapTuple newTuple;
+ int numSimpleVars;
/*
* sanity checks
*/
- if (projInfo == NULL)
- return (TupleTableSlot *) NULL;
+ Assert(projInfo != NULL);
/*
* get the projection info we want
*/
slot = projInfo->pi_slot;
- targetlist = projInfo->pi_targetlist;
- len = projInfo->pi_len;
- tupType = slot->ttc_tupleDescriptor;
-
- tupValue = projInfo->pi_tupValue;
econtext = projInfo->pi_exprContext;
+ /* Assume single result row until proven otherwise */
+ if (isDone)
+ *isDone = ExprSingleResult;
+
+ /*
+ * 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.)
+ */
+ ExecClearTuple(slot);
+
+ /*
+ * Force extraction of all input values that we'll need. The
+ * Var-extraction loops below depend on this, and we are also prefetching
+ * all attributes that will be referenced in the generic expressions.
+ */
+ 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 simple Vars to result by direct extraction of fields from source
+ * slots ... a mite ugly, but fast ...
+ */
+ numSimpleVars = projInfo->pi_numSimpleVars;
+ if (numSimpleVars > 0)
+ {
+ Datum *values = slot->tts_values;
+ bool *isnull = slot->tts_isnull;
+ int *varSlotOffsets = projInfo->pi_varSlotOffsets;
+ int *varNumbers = projInfo->pi_varNumbers;
+ int i;
+
+ if (projInfo->pi_directMap)
+ {
+ /* especially simple case where vars go to output in order */
+ for (i = 0; i < numSimpleVars; i++)
+ {
+ char *slotptr = ((char *) econtext) + varSlotOffsets[i];
+ TupleTableSlot *varSlot = *((TupleTableSlot **) slotptr);
+ int varNumber = varNumbers[i] - 1;
+
+ values[i] = varSlot->tts_values[varNumber];
+ isnull[i] = varSlot->tts_isnull[varNumber];
+ }
+ }
+ else
+ {
+ /* we have to pay attention to varOutputCols[] */
+ int *varOutputCols = projInfo->pi_varOutputCols;
+
+ for (i = 0; i < numSimpleVars; i++)
+ {
+ char *slotptr = ((char *) econtext) + varSlotOffsets[i];
+ TupleTableSlot *varSlot = *((TupleTableSlot **) slotptr);
+ int varNumber = varNumbers[i] - 1;
+ int varOutputCol = varOutputCols[i] - 1;
+
+ values[varOutputCol] = varSlot->tts_values[varNumber];
+ isnull[varOutputCol] = varSlot->tts_isnull[varNumber];
+ }
+ }
+ }
+
/*
- * form a new result tuple (if possible --- result can be NULL)
+ * If there are any generic expressions, evaluate them. It's possible
+ * that there are set-returning functions in such expressions; if so
+ * and we have reached the end of the set, we return the result slot,
+ * which we already marked empty.
*/
- newTuple = ExecTargetList(targetlist,
- len,
- tupType,
- tupValue,
- econtext,
- isDone);
+ if (projInfo->pi_targetlist)
+ {
+ if (!ExecTargetList(projInfo->pi_targetlist,
+ econtext,
+ slot->tts_values,
+ slot->tts_isnull,
+ projInfo->pi_itemIsDone,
+ isDone))
+ return slot; /* no more result rows, return empty slot */
+ }
/*
- * store the tuple in the projection slot and return the slot.
+ * Successfully formed a result row. 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);
+ return ExecStoreVirtualTuple(slot);
}