* execQual.c
* Routines to evaluate qualification and targetlist expressions
*
- * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.196 2006/10/06 17:13:59 petere Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.247 2009/06/04 18:33:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* instead of doing needless copying. -cim 5/31/91
*
* During expression evaluation, we check_stack_depth only in
- * ExecMakeFunctionResult rather than at every single node. This
- * is a compromise that trades off precision of the stack limit setting
- * to gain speed.
+ * 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 "funcapi.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
-#include "optimizer/planmain.h"
-#include "parser/parse_expr.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
+#include "pgstat.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/typcache.h"
+#include "utils/xml.h"
/* static function decls */
static Datum ExecEvalAggref(AggrefExprState *aggref,
ExprContext *econtext,
bool *isNull, 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 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 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 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);
/* ----------------------------------------------------------------
if (isAssignment)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
- errmsg("array subscript in assignment must not be NULL")));
+ errmsg("array subscript in assignment must not be null")));
*isNull = true;
return (Datum) NULL;
}
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.
+ *
+ * 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
*isDone = ExprSingleResult;
/*
- * Get the slot and attribute number 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
break;
}
-#ifdef USE_ASSERT_CHECKING
-
- /*
- * Some checks that are only applied for user attribute numbers (bogus
- * system attnums will be caught inside slot_getattr).
- */
- if (attnum > 0)
+ if (attnum != InvalidAttrNumber)
{
- TupleDesc tuple_type = slot->tts_tupleDescriptor;
-
/*
- * This assert checks that the attnum is valid.
+ * 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.
*/
- Assert(attnum <= tuple_type->natts);
+ if (attnum > 0)
+ {
+ TupleDesc slot_tupdesc = slot->tts_tupleDescriptor;
+ Form_pg_attribute attr;
+
+ if (attnum > slot_tupdesc->natts) /* should never happen */
+ elog(ERROR, "attribute number %d exceeds number of columns %d",
+ attnum, slot_tupdesc->natts);
+
+ attr = slot_tupdesc->attrs[attnum - 1];
+ /* can't check type if dropped, since atttypid is probably 0 */
+ if (!attr->attisdropped)
+ {
+ if (variable->vartype != attr->atttypid)
+ ereport(ERROR,
+ (errmsg("attribute %d has wrong type", attnum),
+ errdetail("Table has type %s, but query expects %s.",
+ format_type_be(attr->atttypid),
+ format_type_be(variable->vartype))));
+ }
+ }
+
+ /* Skip the checking on future executions of node */
+ exprstate->evalfunc = ExecEvalScalarVar;
+
+ /* Fetch the value from the slot */
+ return slot_getattr(slot, attnum, isNull);
+ }
+ else
+ {
/*
- * This assert checks that the datatype the plan expects to get (as
- * told by our "variable" argument) is in fact the datatype of the
- * attribute being fetched (as seen in the current context, identified
- * by our "econtext" argument). Otherwise crashes are likely.
+ * Whole-row variable.
*
- * Note that we can't check dropped columns, since their atttypid has
- * been zeroed.
+ * 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.
*/
- Assert(variable->vartype == tuple_type->attrs[attnum - 1]->atttypid ||
- tuple_type->attrs[attnum - 1]->attisdropped);
+ 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;
+
+ /*
+ * 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)));
+ }
+
+ 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);
}
-#endif /* USE_ASSERT_CHECKING */
+}
+/* ----------------------------------------------------------------
+ * ExecEvalScalarVar
+ *
+ * Returns a Datum for a scalar variable.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
+{
+ Var *variable = (Var *) exprstate->expr;
+ TupleTableSlot *slot;
+ AttrNumber attnum;
+
+ if (isDone)
+ *isDone = ExprSingleResult;
+
+ /* Get the input slot and attribute number we want */
+ switch (variable->varno)
+ {
+ case INNER: /* get the tuple from the inner node */
+ slot = econtext->ecxt_innertuple;
+ break;
+
+ case OUTER: /* get the tuple from the outer node */
+ slot = econtext->ecxt_outertuple;
+ break;
+
+ default: /* get the tuple from the relation being
+ * scanned */
+ slot = econtext->ecxt_scantuple;
+ break;
+ }
+
+ attnum = variable->varattno;
+
+ /* Fetch the value from the slot */
return slot_getattr(slot, attnum, isNull);
}
* ExecEvalWholeRowVar
*
* Returns a Datum for a whole-row variable.
- *
- * This could be folded into ExecEvalVar, but we make it a separate
- * routine so as not to slow down ExecEvalVar with tests for this
- * uncommon case.
* ----------------------------------------------------------------
*/
static Datum
bool *isNull, ExprDoneCond *isDone)
{
Var *variable = (Var *) exprstate->expr;
- TupleTableSlot *slot;
+ TupleTableSlot *slot = econtext->ecxt_scantuple;
HeapTuple tuple;
TupleDesc tupleDesc;
HeapTupleHeader dtuple;
*isDone = ExprSingleResult;
*isNull = false;
- Assert(variable->varattno == InvalidAttrNumber);
-
- /*
- * Whole-row Vars can only appear at the level of a relation scan, never
- * in a join.
- */
- Assert(variable->varno != INNER);
- Assert(variable->varno != OUTER);
- slot = econtext->ecxt_scantuple;
-
tuple = ExecFetchSlotTuple(slot);
tupleDesc = slot->tts_tupleDescriptor;
/*
* If the Var identifies a named composite type, label the tuple with that
* type; otherwise use what is in the tupleDesc.
- *
- * It's likely that the slot's tupleDesc is a record type; if so, make
- * sure it's been "blessed", so that the Datum can be interpreted later.
*/
if (variable->vartype != RECORDOID)
{
}
else
{
- if (tupleDesc->tdtypeid == RECORDOID &&
- tupleDesc->tdtypmod < 0)
- assign_record_type_typmod(tupleDesc);
HeapTupleHeaderSetTypeId(dtuple, tupleDesc->tdtypeid);
HeapTupleHeaderSetTypMod(dtuple, tupleDesc->tdtypmod);
}
return PointerGetDatum(dtuple);
}
+/* ----------------------------------------------------------------
+ * 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;
+
+ 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
*
* something like ($.name) and the expression context contains
* the current parameter bindings (name = "sam") (age = 34)...
* so our job is to find and return the appropriate datum ("sam").
- *
- * Q: if we have a parameter ($.foo) without a binding, i.e.
- * there is no (foo = xxx) in the parameter list info,
- * is this a fatal error or should this be a "not available"
- * (in which case we could return NULL)? -cim 10/13/89
* ----------------------------------------------------------------
*/
static Datum
/*
* init_fcache - initialize a FuncExprState node during first use
*/
-void
-init_fcache(Oid foid, FuncExprState *fcache, MemoryContext fcacheCxt)
+static void
+init_fcache(Oid foid, FuncExprState *fcache,
+ MemoryContext fcacheCxt, bool needDescForSets)
{
AclResult aclresult;
if (list_length(fcache->args) > FUNC_MAX_ARGS)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
- errmsg("cannot pass more than %d arguments to a function",
- FUNC_MAX_ARGS)));
+ 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;
- /* Initialize additional info */
+ /* 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;
- fcache->func.fn_expr = (Node *) fcache->xprstate.expr;
}
/*
{
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;
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
+static Datum
ExecMakeFunctionResult(FuncExprState *fcache,
ExprContext *econtext,
bool *isNull,
ExprDoneCond *isDone)
{
- List *arguments = fcache->args;
+ 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 = fcache->args;
if (!fcache->setArgsValid)
{
/* Need to prep callinfo structure */
- InitFunctionCallInfoData(fcinfo, &(fcache->func), 0, NULL, NULL);
- 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. */
}
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
- */
- 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.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.
+ * Now call the function, passing the evaluated parameter values.
*/
if (fcache->func.fn_retset || hasSetArg)
{
(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 = 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;
+
/*
* This loop handles the situation where we have both a set argument
* and a set-valued function. Once we have exhausted the function's
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 && *isDone == ExprMultipleResult)
+ if (*isDone != ExprEndResult)
{
- memcpy(&fcache->setArgs, &fcinfo, sizeof(fcinfo));
- fcache->setHasSetArg = hasSetArg;
- fcache->setArgsValid = true;
- /* Register cleanup callback if we didn't already */
- if (!fcache->shutdown_reg)
+ /*
+ * 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)
{
- RegisterExprContextCallback(econtext,
- ShutdownFuncExpr,
- PointerGetDatum(fcache));
- fcache->shutdown_reg = true;
+ 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.
- */
- if (hasSetArg)
- *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 (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;
ListCell *arg;
Datum result;
FunctionCallInfoData fcinfo;
+ PgStat_FunctionCallUsage fcusage;
int i;
/* Guard against stack overflow due to overly complex expressions */
}
}
}
+
+ 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. *returnDesc is set to the tupledesc actually returned by the
- * function, or NULL if it didn't provide one.
+ * object.
*/
Tuplestorestate *
ExecMakeTableFunctionResult(ExprState *funcexpr,
ExprContext *econtext,
TupleDesc expectedDesc,
- TupleDesc *returnDesc)
+ bool randomAccess)
{
Tuplestorestate *tupstore = NULL;
TupleDesc tupdesc = NULL;
bool returnsTuple;
bool returnsSet = false;
FunctionCallInfoData fcinfo;
+ PgStat_FunctionCallUsage fcusage;
ReturnSetInfo rsinfo;
HeapTupleData tmptup;
MemoryContext callerContext;
rsinfo.type = T_ReturnSetInfo;
rsinfo.econtext = econtext;
rsinfo.expectedDesc = expectedDesc;
- rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize);
+ 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;
{
FuncExpr *func = (FuncExpr *) fcache->xprstate.expr;
- init_fcache(func->funcid, fcache, econtext->ecxt_per_query_memory);
+ init_fcache(func->funcid, fcache,
+ econtext->ecxt_per_query_memory, false);
}
returnsSet = fcache->func.fn_retset;
for (;;)
{
Datum result;
- HeapTuple tuple;
CHECK_FOR_INTERRUPTS();
/* 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
{
-1,
0);
}
- tupstore = tuplestore_begin_heap(true, false, work_mem);
+ tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
MemoryContextSwitchTo(oldcontext);
rsinfo.setResult = tupstore;
rsinfo.setDesc = tupdesc;
*/
tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
tmptup.t_data = td;
- tuple = &tmptup;
+
+ oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+ tuplestore_puttuple(tupstore, &tmptup);
}
else
{
- tuple = heap_form_tuple(tupdesc, &result, &fcinfo.isnull);
+ 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);
/*
if (rsinfo.setResult == NULL)
{
MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
- tupstore = tuplestore_begin_heap(true, false, work_mem);
+ tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
rsinfo.setResult = tupstore;
if (!returnsSet)
{
int natts = expectedDesc->natts;
Datum *nulldatums;
bool *nullflags;
- HeapTuple tuple;
MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
nulldatums = (Datum *) palloc0(natts * sizeof(Datum));
nullflags = (bool *) palloc(natts * sizeof(bool));
memset(nullflags, true, natts * sizeof(bool));
- tuple = heap_form_tuple(expectedDesc, nulldatums, nullflags);
MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
- tuplestore_puttuple(tupstore, tuple);
+ 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;
}
FuncExpr *func = (FuncExpr *) fcache->xprstate.expr;
/* Initialize function lookup info */
- init_fcache(func->funcid, fcache, econtext->ecxt_per_query_memory);
+ init_fcache(func->funcid, fcache, econtext->ecxt_per_query_memory, true);
/* Go directly to ExecMakeFunctionResult on subsequent uses */
fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResult;
OpExpr *op = (OpExpr *) fcache->xprstate.expr;
/* Initialize function lookup info */
- init_fcache(op->opfuncid, fcache, econtext->ecxt_per_query_memory);
+ init_fcache(op->opfuncid, fcache, econtext->ecxt_per_query_memory, true);
/* Go directly to ExecMakeFunctionResult on subsequent uses */
fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResult;
{
DistinctExpr *op = (DistinctExpr *) fcache->xprstate.expr;
- init_fcache(op->opfuncid, fcache, econtext->ecxt_per_query_memory);
+ init_fcache(op->opfuncid, fcache,
+ econtext->ecxt_per_query_memory, true);
Assert(!fcache->func.fn_retset);
}
if (sstate->fxprstate.func.fn_oid == InvalidOid)
{
init_fcache(opexpr->opfuncid, &sstate->fxprstate,
- econtext->ecxt_per_query_memory);
+ econtext->ecxt_per_query_memory, true);
Assert(!sstate->fxprstate.func.fn_retset);
}
else
{
elt = fetch_att(s, typbyval, typlen);
- s = att_addlength(s, typlen, PointerGetDatum(s));
- s = (char *) att_align(s, typalign);
+ s = att_addlength_pointer(s, typlen, s);
+ s = (char *) att_align_nominal(s, typalign);
fcinfo.arg[1] = elt;
fcinfo.argnull[1] = false;
}
int *elem_lbs = NULL;
bool firstone = true;
bool havenulls = false;
+ bool haveempty = false;
char **subdata;
bits8 **subbitmaps;
int *subbytes;
bool eisnull;
Datum arraydatum;
ArrayType *array;
+ int this_ndims;
arraydatum = ExecEvalExpr(e, econtext, &eisnull, NULL);
- /* ignore null subarrays */
+ /* temporarily ignore null subarrays */
if (eisnull)
+ {
+ haveempty = true;
continue;
+ }
array = DatumGetArrayTypeP(arraydatum);
format_type_be(ARR_ELEMTYPE(array)),
format_type_be(element_type))));
+ this_ndims = ARR_NDIM(array);
+ /* temporarily ignore zero-dimensional subarrays */
+ if (this_ndims <= 0)
+ {
+ haveempty = true;
+ continue;
+ }
+
if (firstone)
{
/* Get sub-array details from first member */
- elem_ndims = ARR_NDIM(array);
+ elem_ndims = this_ndims;
ndims = elem_ndims + 1;
if (ndims <= 0 || ndims > MAXDIM)
ereport(ERROR,
else
{
/* Check other sub-arrays are compatible */
- if (elem_ndims != ARR_NDIM(array) ||
+ if (elem_ndims != this_ndims ||
memcmp(elem_dims, ARR_DIMS(array),
elem_ndims * sizeof(int)) != 0 ||
memcmp(elem_lbs, ARR_LBOUND(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(ARR_NDIM(array),
+ 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;
}
result = (ArrayType *) palloc(nbytes);
- result->size = nbytes;
+ SET_VARSIZE(result, nbytes);
result->ndim = ndims;
result->dataoffset = dataoffset;
result->elemtype = element_type;
*isDone = ExprSingleResult;
*isNull = true; /* until we get a result */
- InitFunctionCallInfoData(locfcinfo, &minmaxExpr->cfunc, 2, NULL, NULL);
- locfcinfo.argnull[0] = false;
- locfcinfo.argnull[1] = false;
+ 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;
- foreach(arg, minmaxExpr->args)
- {
- ExprState *e = (ExprState *) lfirst(arg);
- Datum value;
- bool valueIsNull;
- int32 cmpresult;
+ case IS_DOCUMENT:
+ {
+ ExprState *e;
- value = ExecEvalExpr(e, econtext, &valueIsNull, NULL);
- if (valueIsNull)
- continue; /* ignore NULL inputs */
+ /* optional argument is known to be xml */
+ Assert(list_length(xmlExpr->args) == 1);
- 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;
- }
+ 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;
}
- return result;
+ elog(ERROR, "unrecognized XML operation");
+ return (Datum) 0;
}
/* ----------------------------------------------------------------
{
NullIfExpr *op = (NullIfExpr *) nullIfExpr->xprstate.expr;
- init_fcache(op->opfuncid, nullIfExpr, econtext->ecxt_per_query_memory);
+ init_fcache(op->opfuncid, nullIfExpr,
+ econtext->ecxt_per_query_memory, true);
Assert(!nullIfExpr->func.fn_retset);
}
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);
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
tmptup.t_data = tuple;
result = heap_getattr(&tmptup,
- fselect->fieldnum,
+ fieldnum,
tupDesc,
isNull);
return result;
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;
+ Datum inputval;
+ char *string;
+
+ inputval = ExecEvalExpr(iostate->arg, econtext, isNull, isDone);
+
+ if (isDone && *isDone == ExprEndResult)
+ return inputval; /* nothing to do */
+
+ if (*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;
+}
+
+/* ----------------------------------------------------------------
+ * ExecEvalArrayCoerceExpr
+ *
+ * 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
+ *
+ * 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
* executions of the expression are needed. Typically the context will be
* the same as the per-query context of the associated ExprContext.
*
- * Any Aggref and 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.)
+ * 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
switch (nodeTag(node))
{
case T_Var:
- {
- Var *var = (Var *) node;
-
- state = (ExprState *) makeNode(ExprState);
- if (var->varattno != InvalidAttrNumber)
- state->evalfunc = ExecEvalVar;
- else
- state->evalfunc = ExecEvalWholeRowVar;
- }
+ state = (ExprState *) makeNode(ExprState);
+ state->evalfunc = ExecEvalVar;
break;
case T_Const:
state = (ExprState *) makeNode(ExprState);
if (naggs != aggstate->numaggs)
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
- errmsg("aggregate function calls may not be nested")));
+ errmsg("aggregate function calls cannot be nested")));
}
else
{
/* planner messed up */
- elog(ERROR, "aggref found in non-Agg plan node");
+ 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;
break;
case T_SubPlan:
{
- /* Keep this in sync with ExecInitExprInitPlan, below */
SubPlan *subplan = (SubPlan *) node;
- SubPlanState *sstate = makeNode(SubPlanState);
-
- sstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecSubPlan;
+ SubPlanState *sstate;
if (!parent)
elog(ERROR, "SubPlan found with no parent plan");
- /*
- * Here we just add the SubPlanState nodes to parent->subPlan.
- * The subplans will be initialized later.
- */
- parent->subPlan = lcons(sstate, parent->subPlan);
- sstate->sub_estate = NULL;
- sstate->planstate = NULL;
+ sstate = ExecInitSubPlan(subplan, parent);
- sstate->testexpr =
- ExecInitExpr((Expr *) subplan->testexpr, parent);
- sstate->args = (List *)
- ExecInitExpr((Expr *) subplan->args, 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;
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;
* don't really care what type of NULL it is, so
* always make an int4 NULL.
*/
- e = (Expr *) makeNullConst(INT4OID);
+ e = (Expr *) makeNullConst(INT4OID, -1);
}
estate = ExecInitExpr(e, parent);
outlist = lappend(outlist, estate);
outlist = lappend(outlist, estate);
}
rstate->rargs = outlist;
- Assert(list_length(rcexpr->opclasses) == nopers);
+ Assert(list_length(rcexpr->opfamilies) == nopers);
rstate->funcs = (FmgrInfo *) palloc(nopers * sizeof(FmgrInfo));
i = 0;
- forboth(l, rcexpr->opnos, l2, rcexpr->opclasses)
+ forboth(l, rcexpr->opnos, l2, rcexpr->opfamilies)
{
Oid opno = lfirst_oid(l);
- Oid opclass = lfirst_oid(l2);
+ Oid opfamily = lfirst_oid(l2);
int strategy;
- Oid subtype;
- bool recheck;
+ Oid lefttype;
+ Oid righttype;
Oid proc;
- get_op_opclass_properties(opno, opclass,
- &strategy, &subtype, &recheck);
- proc = get_opclass_proc(opclass, subtype, BTORDER_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
state = (ExprState *) mstate;
}
break;
+ case T_XmlExpr:
+ {
+ 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;
+
+ outlist = NIL;
+ foreach(arg, xexpr->args)
+ {
+ Expr *e = (Expr *) lfirst(arg);
+ ExprState *estate;
+
+ estate = ExecInitExpr(e, parent);
+ outlist = lappend(outlist, estate);
+ }
+ xstate->args = outlist;
+
+ state = (ExprState *) xstate;
+ }
+ break;
case T_NullIfExpr:
{
NullIfExpr *nullifexpr = (NullIfExpr *) node;
state = (ExprState *) cstate;
}
break;
+ case T_CurrentOfExpr:
+ state = (ExprState *) makeNode(ExprState);
+ state->evalfunc = ExecEvalCurrentOfExpr;
+ break;
case T_TargetEntry:
{
TargetEntry *tle = (TargetEntry *) node;
return state;
}
-/*
- * ExecInitExprInitPlan --- initialize a subplan expr that's being handled
- * as an InitPlan. This is identical to ExecInitExpr's handling of a regular
- * subplan expr, except we do NOT want to add the node to the parent's
- * subplan list.
- */
-SubPlanState *
-ExecInitExprInitPlan(SubPlan *node, PlanState *parent)
-{
- SubPlanState *sstate = makeNode(SubPlanState);
-
- if (!parent)
- elog(ERROR, "SubPlan found with no parent plan");
-
- /* The subplan's state will be initialized later */
- sstate->sub_estate = NULL;
- sstate->planstate = NULL;
-
- sstate->testexpr = ExecInitExpr((Expr *) node->testexpr, parent);
- sstate->args = (List *) ExecInitExpr((Expr *) node->args, parent);
-
- sstate->xprstate.expr = (Expr *) node;
-
- return sstate;
-}
-
/*
* ExecPrepareExpr --- initialize for expression execution outside a normal
* Plan tree context.
*
* This differs from ExecInitExpr in that we don't assume the caller is
- * already running in the EState's per-query context. Also, we apply
- * fix_opfuncids() to the passed expression tree to be sure it is ready
- * to run. (In ordinary Plan trees the planner will have fixed opfuncids,
- * but callers outside the executor will not have done this.)
+ * 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.)
*/
ExprState *
ExecPrepareExpr(Expr *node, EState *estate)
ExprState *result;
MemoryContext oldcontext;
- fix_opfuncids((Node *) node);
-
oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+ node = expression_planner(node);
+
result = ExecInitExpr(node, NULL);
MemoryContextSwitchTo(oldcontext);
* 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 bool
ExecTargetList(List *targetlist,
/*
* 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)
return true;
}
-/*
- * ExecVariableList
- * Evaluates a simple-Variable-list projection.
- *
- * Results are stored into the passed values and isnull arrays.
- */
-static void
-ExecVariableList(ProjectionInfo *projInfo,
- Datum *values,
- bool *isnull)
-{
- ExprContext *econtext = projInfo->pi_exprContext;
- int *varSlotOffsets = projInfo->pi_varSlotOffsets;
- int *varNumbers = projInfo->pi_varNumbers;
- int i;
-
- /*
- * Force extraction of all input values that we need.
- */
- if (projInfo->pi_lastInnerVar > 0)
- slot_getsomeattrs(econtext->ecxt_innertuple,
- projInfo->pi_lastInnerVar);
- if (projInfo->pi_lastOuterVar > 0)
- slot_getsomeattrs(econtext->ecxt_outertuple,
- projInfo->pi_lastOuterVar);
- if (projInfo->pi_lastScanVar > 0)
- slot_getsomeattrs(econtext->ecxt_scantuple,
- projInfo->pi_lastScanVar);
-
- /*
- * Assign to result by direct extraction of fields from source slots ... a
- * mite ugly, but fast ...
- */
- for (i = list_length(projInfo->pi_targetlist) - 1; i >= 0; i--)
- {
- char *slotptr = ((char *) econtext) + varSlotOffsets[i];
- TupleTableSlot *varSlot = *((TupleTableSlot **) slotptr);
- int varNumber = varNumbers[i] - 1;
-
- values[i] = varSlot->tts_values[varNumber];
- isnull[i] = varSlot->tts_isnull[varNumber];
- }
-}
-
/*
* ExecProject
*
ExecProject(ProjectionInfo *projInfo, ExprDoneCond *isDone)
{
TupleTableSlot *slot;
+ ExprContext *econtext;
+ int numSimpleVars;
/*
* sanity checks
* get the projection info we want
*/
slot = projInfo->pi_slot;
+ 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
ExecClearTuple(slot);
/*
- * form a new result tuple (if possible); if successful, mark the result
- * slot as containing a valid virtual tuple
+ * 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 ...
*/
- if (projInfo->pi_isVarList)
+ numSimpleVars = projInfo->pi_numSimpleVars;
+ if (numSimpleVars > 0)
{
- /* simple Var list: this always succeeds with one result row */
- if (isDone)
- *isDone = ExprSingleResult;
- ExecVariableList(projInfo,
- slot->tts_values,
- slot->tts_isnull);
- ExecStoreVirtualTuple(slot);
+ 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];
+ }
+ }
}
- else
+
+ /*
+ * 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.
+ */
+ if (projInfo->pi_targetlist)
{
- if (ExecTargetList(projInfo->pi_targetlist,
- projInfo->pi_exprContext,
- slot->tts_values,
- slot->tts_isnull,
- projInfo->pi_itemIsDone,
- isDone))
- ExecStoreVirtualTuple(slot);
+ 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 */
}
- return slot;
+ /*
+ * Successfully formed a result row. Mark the result slot as containing a
+ * valid virtual tuple.
+ */
+ return ExecStoreVirtualTuple(slot);
}