* execQual.c
* Routines to evaluate qualification and targetlist expressions
*
- * Portions Copyright (c) 1996-2004, 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.168 2004/08/29 05:06:42 momjian 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 "executor/execdebug.h"
-#include "executor/functions.h"
#include "executor/nodeSubplan.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
-#include "optimizer/planmain.h"
-#include "parser/parse_expr.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
+#include "pgstat.h"
#include "utils/acl.h"
-#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
+#include "utils/memutils.h"
#include "utils/typcache.h"
+#include "utils/xml.h"
/* static function decls */
static Datum ExecEvalAggref(AggrefExprState *aggref,
ExprContext *econtext,
bool *isNull, 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);
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 ExecEvalCaseTestExpr(ExprState *exprstate,
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(GenericExprState *nstate,
+static Datum ExecEvalNullTest(NullTestState *nstate,
ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalBooleanTest(GenericExprState *bstate,
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 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
isDone));
/*
- * If refexpr yields NULL, and it's a fetch, then result is NULL. In
- * the assignment case, we'll cons up something below.
+ * 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)
{
econtext,
&eisnull,
NULL));
- /* If any index expr yields NULL, result is NULL or source array */
+ /* If any index expr yields NULL, result is NULL or error */
if (eisnull)
{
- if (!isAssignment)
- {
- *isNull = true;
- return (Datum) NULL;
- }
- 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;
}
}
econtext,
&eisnull,
NULL));
-
- /*
- * If any index expr yields NULL, result is NULL or source
- * array
- */
+ /* If any index expr yields NULL, result is NULL or error */
if (eisnull)
{
- if (!isAssignment)
- {
- *isNull = true;
- return (Datum) NULL;
- }
- 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 */
*
* 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.
+ * 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,
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 (eisnull)
- return PointerGetDatum(array_source);
+ if (astate->refattrlength > 0) /* fixed-length array? */
+ if (eisnull || *isNull)
+ return PointerGetDatum(array_source);
/*
- * For an assignment, if all the subscripts and the input
- * expression are non-null but the original array is null, then
- * substitute an empty (zero-dimensional) array and proceed with
- * the assignment. This only works for varlena arrays, though; for
- * fixed-length array types we punt and return the null input
- * array.
+ * 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 (astate->refattrlength > 0) /* fixed-length array? */
- return PointerGetDatum(array_source);
-
- array_source = construct_md_array(NULL, 0, NULL, NULL,
- arrayRef->refelemtype,
- astate->refelemlength,
- astate->refelembyval,
- astate->refelemalign);
+ array_source = construct_empty_array(arrayRef->refelemtype);
*isNull = false;
}
resultArray = array_set(array_source, i,
upper.indx,
sourceData,
+ eisnull,
astate->refattrlength,
astate->refelemlength,
astate->refelembyval,
- astate->refelemalign,
- isNull);
+ astate->refelemalign);
else
resultArray = array_set_slice(array_source, i,
upper.indx, lower.indx,
- (ArrayType *) DatumGetPointer(sourceData),
+ (ArrayType *) DatumGetPointer(sourceData),
+ eisnull,
astate->refattrlength,
astate->refelemlength,
astate->refelembyval,
- astate->refelemalign,
- isNull);
+ astate->refelemalign);
return PointerGetDatum(resultArray);
}
astate->refattrlength,
astate->refelemlength,
astate->refelembyval,
- astate->refelemalign,
- isNull);
+ astate->refelemalign);
return PointerGetDatum(resultArray);
}
}
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
ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone)
{
Var *variable = (Var *) exprstate->expr;
- Datum result;
TupleTableSlot *slot;
AttrNumber attnum;
- HeapTuple heapTuple;
- TupleDesc tuple_type;
if (isDone)
*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 be treated as ordinary variables (since we no longer have
- * access to the original tuple).
+ * 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;
break;
}
- /*
- * extract tuple information from the slot
- */
- heapTuple = slot->val;
- tuple_type = slot->ttc_tupleDescriptor;
-
- /*
- * Some checks that are only applied for user attribute numbers (bogus
- * system attnums will be caught inside heap_getattr).
- */
- if (attnum > 0)
+ if (attnum != InvalidAttrNumber)
{
/*
- * This assert checks that the attnum is valid.
- */
- Assert(attnum <= tuple_type->natts &&
- tuple_type->attrs[attnum - 1] != NULL);
-
- /*
- * If the attribute's column has been dropped, we force a NULL
- * result. This case should not happen in normal use, but it could
- * happen if we are executing a plan cached before the column was
- * dropped.
+ * Scalar variable case.
+ *
+ * If it's a user attribute, check validity (bogus system attnums will
+ * be caught inside slot_getattr). What we have to check for here is
+ * the possibility of an attribute having been changed in type since
+ * the plan tree was created. Ideally the plan would get invalidated
+ * and not re-used, but until that day arrives, we need defenses.
+ * Fortunately it's sufficient to check once on the first time
+ * through.
+ *
+ * Note: we allow a reference to a dropped attribute. slot_getattr
+ * will force a NULL result in such cases.
+ *
+ * Note: ideally we'd check typmod as well as typid, but that seems
+ * impractical at the moment: in many cases the tupdesc will have been
+ * generated by ExecTypeFromTL(), and that can't guarantee to generate
+ * an accurate typmod in all cases, because some expression node types
+ * don't carry typmod.
*/
- if (tuple_type->attrs[attnum - 1]->attisdropped)
+ if (attnum > 0)
{
- *isNull = true;
- return (Datum) 0;
+ TupleDesc slot_tupdesc = slot->tts_tupleDescriptor;
+ Form_pg_attribute attr;
+
+ if (attnum > slot_tupdesc->natts) /* should never happen */
+ elog(ERROR, "attribute number %d exceeds number of columns %d",
+ attnum, slot_tupdesc->natts);
+
+ attr = slot_tupdesc->attrs[attnum - 1];
+
+ /* can't check type if dropped, since atttypid is probably 0 */
+ if (!attr->attisdropped)
+ {
+ if (variable->vartype != attr->atttypid)
+ ereport(ERROR,
+ (errmsg("attribute %d has wrong type", attnum),
+ errdetail("Table has type %s, but query expects %s.",
+ format_type_be(attr->atttypid),
+ format_type_be(variable->vartype))));
+ }
}
+ /* Skip the checking on future executions of node */
+ exprstate->evalfunc = ExecEvalScalarVar;
+
+ /* Fetch the value from the slot */
+ return slot_getattr(slot, attnum, isNull);
+ }
+ else
+ {
/*
- * This assert checks that the datatype the plan expects to get
- * (as told by our "variable" argument) is in fact the datatype of
- * the attribute being fetched (as seen in the current context,
- * identified by our "econtext" argument). Otherwise crashes are
- * likely.
+ * Whole-row variable.
*
- * 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);
+ 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);
}
+}
+
+/* ----------------------------------------------------------------
+ * 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;
- result = heap_getattr(heapTuple, /* tuple containing attribute */
- attnum, /* attribute number of desired
- * attribute */
- tuple_type, /* tuple descriptor of tuple */
- isNull); /* return: is attribute null? */
+ if (isDone)
+ *isDone = ExprSingleResult;
- return result;
+ /* Get the input slot and attribute number we want */
+ switch (variable->varno)
+ {
+ case INNER: /* get the tuple from the inner node */
+ slot = econtext->ecxt_innertuple;
+ break;
+
+ case OUTER: /* get the tuple from the outer node */
+ slot = econtext->ecxt_outertuple;
+ break;
+
+ default: /* get the tuple from the relation being
+ * scanned */
+ slot = econtext->ecxt_scantuple;
+ break;
+ }
+
+ attnum = variable->varattno;
+
+ /* Fetch the value from the slot */
+ return slot_getattr(slot, attnum, isNull);
+}
+
+/* ----------------------------------------------------------------
+ * ExecEvalWholeRowVar
+ *
+ * Returns a Datum for a whole-row variable.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
+{
+ Var *variable = (Var *) exprstate->expr;
+ TupleTableSlot *slot = econtext->ecxt_scantuple;
+ HeapTuple tuple;
+ TupleDesc tupleDesc;
+ HeapTupleHeader dtuple;
+
+ if (isDone)
+ *isDone = ExprSingleResult;
+ *isNull = false;
+
+ tuple = ExecFetchSlotTuple(slot);
+ tupleDesc = slot->tts_tupleDescriptor;
+
+ /*
+ * 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.
+ */
+ dtuple = (HeapTupleHeader) palloc(tuple->t_len);
+ memcpy((char *) dtuple, (char *) tuple->t_data, tuple->t_len);
+
+ HeapTupleHeaderSetDatumLength(dtuple, tuple->t_len);
+
+ /*
+ * If the Var identifies a named composite type, label the tuple with that
+ * type; otherwise use what is in the tupleDesc.
+ */
+ if (variable->vartype != RECORDOID)
+ {
+ HeapTupleHeaderSetTypeId(dtuple, variable->vartype);
+ HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod);
+ }
+ else
+ {
+ HeapTupleHeaderSetTypeId(dtuple, tupleDesc->tdtypeid);
+ HeapTupleHeaderSetTypMod(dtuple, tupleDesc->tdtypmod);
+ }
+
+ 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);
}
/* ----------------------------------------------------------------
* 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
bool *isNull, ExprDoneCond *isDone)
{
Param *expression = (Param *) exprstate->expr;
- int thisParamKind = expression->paramkind;
- AttrNumber thisParamId = expression->paramid;
+ int thisParamId = expression->paramid;
if (isDone)
*isDone = ExprSingleResult;
- if (thisParamKind == PARAM_EXEC)
+ if (expression->paramkind == PARAM_EXEC)
{
/*
- * PARAM_EXEC params (internal executor parameters) are stored in
- * the ecxt_param_exec_vals array, and can be accessed by array
- * index.
+ * PARAM_EXEC params (internal executor parameters) are stored in the
+ * ecxt_param_exec_vals array, and can be accessed by array index.
*/
ParamExecData *prm;
else
{
/*
- * All other parameter types must be sought in
- * ecxt_param_list_info.
+ * PARAM_EXTERN parameters must be sought in ecxt_param_list_info.
*/
- ParamListInfo paramInfo;
+ 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];
- paramInfo = lookupParam(econtext->ecxt_param_list_info,
- thisParamKind,
- expression->paramname,
- thisParamId,
- false);
- Assert(paramInfo->ptype == expression->paramtype);
- *isNull = paramInfo->isnull;
- return paramInfo->value;
+ 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 */
}
}
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.
+ * 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));
attrno,
tupDesc,
isNull);
+
+ ReleaseTupleDesc(tupDesc);
+
return result;
}
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.
+ * 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));
attrno,
tupDesc,
isNull);
+
+ ReleaseTupleDesc(tupDesc);
+
return result;
}
/*
* 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 (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(foid));
- /* Safety check (should never fail, as parser should check sooner) */
+ /*
+ * 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)
- elog(ERROR, "too many arguments");
+ 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 info */
+ /* 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;
fcache->shutdown_reg = false;
}
+/*
+ * get_cached_rowtype: utility function to lookup a rowtype tupdesc
+ *
+ * type_id, typmod: identity of the rowtype
+ * cache_field: where to cache the TupleDesc pointer in expression state node
+ * (field must be initialized to NULL)
+ * econtext: expression context we are executing in
+ *
+ * NOTE: because the shutdown callback will be called during plan rescan,
+ * must be prepared to re-do this during any node execution; cannot call
+ * just once during expression initialization
+ */
+static TupleDesc
+get_cached_rowtype(Oid type_id, int32 typmod,
+ TupleDesc *cache_field, ExprContext *econtext)
+{
+ TupleDesc tupDesc = *cache_field;
+
+ /* Do lookup if no cached value or if requested type changed */
+ if (tupDesc == NULL ||
+ type_id != tupDesc->tdtypeid ||
+ typmod != tupDesc->tdtypmod)
+ {
+ tupDesc = lookup_rowtype_tupdesc(type_id, typmod);
+
+ if (*cache_field)
+ {
+ /* Release old tupdesc; but callback is already registered */
+ ReleaseTupleDesc(*cache_field);
+ }
+ else
+ {
+ /* Need to register shutdown callback to release tupdesc */
+ RegisterExprContextCallback(econtext,
+ ShutdownTupleDescRef,
+ PointerGetDatum(cache_field));
+ }
+ *cache_field = tupDesc;
+ }
+ return tupDesc;
+}
+
+/*
+ * Callback function to release a tupdesc refcount at expression tree shutdown
+ */
+static void
+ShutdownTupleDescRef(Datum arg)
+{
+ TupleDesc *cache_field = (TupleDesc *) DatumGetPointer(arg);
+
+ if (*cache_field)
+ ReleaseTupleDesc(*cache_field);
+ *cache_field = NULL;
+}
+
/*
* Evaluate arguments for a function.
*/
if (thisArgIsDone != ExprSingleResult)
{
/*
- * We allow only one argument to have a set value; we'd need
- * much more complexity to keep track of multiple set
- * arguments (cf. ExecTargetList) and it doesn't seem worth
- * it.
+ * We allow only one argument to have a set value; we'd need much
+ * more complexity to keep track of multiple set arguments (cf.
+ * ExecTargetList) and it doesn't seem worth it.
*/
if (argIsDone != ExprSingleResult)
ereport(ERROR,
}
/*
- * ExecMakeFunctionResult
+ * ExecPrepareTuplestoreResult
*
- * Evaluate the arguments to a function and then the function itself.
+ * 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.
*/
-Datum
-ExecMakeFunctionResult(FuncExprState *fcache,
- ExprContext *econtext,
- bool *isNull,
+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.
+ */
+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();
/*
- * arguments is a list of expressions to evaluate before passing to
- * the function manager. We skip the evaluation if it was already
- * done in the previous call (ie, we are continuing the evaluation of
- * a set-valued function). Otherwise, collect the current argument
- * values into fcinfo.
+ * If 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 */
- 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. */
}
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)
{
/*
- * We need to return a set result. Complain if caller not ready
- * to accept one.
+ * We need to return a set result. Complain if caller not ready to
+ * accept one.
*/
if (isDone == NULL)
ereport(ERROR,
errmsg("set-valued function called in context that cannot accept a set")));
/*
- * This loop handles the situation where we have both a set
- * argument and a set-valued function. Once we have exhausted the
- * function's value(s) for a particular argument value, we have to
- * get the next argument value and start the function over again.
- * We might have to do it more than once, if the function produces
- * an empty result set for a particular input value.
+ * 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
+ * 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;
- /* 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.
- */
- *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.
+ * 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 (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 */
if (isDone)
*isDone = ExprSingleResult;
- MemSet(&fcinfo, 0, sizeof(fcinfo));
- fcinfo.flinfo = &(fcache->func);
-
/* inlined, simplified version of ExecEvalFuncArgs */
i = 0;
foreach(arg, fcache->args)
{
ExprState *argstate = (ExprState *) lfirst(arg);
- ExprDoneCond thisArgIsDone;
fcinfo.arg[i] = ExecEvalExpr(argstate,
econtext,
&fcinfo.argnull[i],
- &thisArgIsDone);
-
- if (thisArgIsDone != ExprSingleResult)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("set-valued function called in context that cannot accept a set")));
+ NULL);
i++;
}
- fcinfo.nargs = 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 function is strict, and there are any NULL arguments, skip calling
+ * the function and return NULL.
*/
if (fcache->func.fn_strict)
{
}
}
}
- /* fcinfo.isnull = false; */ /* handled by MemSet */
+
+ 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(ExprState *funcexpr,
ExprContext *econtext,
TupleDesc expectedDesc,
- TupleDesc *returnDesc)
+ bool randomAccess)
{
Tuplestorestate *tupstore = NULL;
TupleDesc tupdesc = NULL;
Oid funcrettype;
bool returnsTuple;
+ bool returnsSet = false;
FunctionCallInfoData fcinfo;
+ PgStat_FunctionCallUsage fcusage;
ReturnSetInfo rsinfo;
HeapTupleData tmptup;
MemoryContext callerContext;
bool direct_function_call;
bool first_time = true;
+ callerContext = CurrentMemoryContext;
+
+ funcrettype = exprType((Node *) funcexpr->expr);
+
+ returnsTuple = type_is_rowtype(funcrettype);
+
+ /*
+ * Prepare a resultinfo node for communication. We always do this even if
+ * not expecting a set result, so that we can pass expectedDesc. In the
+ * generic-expression case, the expression doesn't actually get to see the
+ * resultinfo, but set it up anyway because we use some of the fields as
+ * our own state variables.
+ */
+ InitFunctionCallInfoData(fcinfo, NULL, 0, NULL, (Node *) &rsinfo);
+ rsinfo.type = T_ReturnSetInfo;
+ rsinfo.econtext = econtext;
+ rsinfo.expectedDesc = expectedDesc;
+ rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize | 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;
+
/*
- * Normally the passed expression tree will be a FuncExprState, since
- * the grammar only allows a function call at the top level of a table
- * function reference. However, if the function doesn't return set
- * then the planner might have replaced the function call via
- * constant-folding or inlining. So if we see any other kind of
- * expression node, execute it via the general ExecEvalExpr() code;
- * the only difference is that we don't get a chance to pass a special
- * ReturnSetInfo to any functions buried in the expression.
+ * Normally the passed expression tree will be a FuncExprState, since the
+ * grammar only allows a function call at the top level of a table
+ * function reference. However, if the function doesn't return set then
+ * the planner might have replaced the function call via constant-folding
+ * or inlining. So if we see any other kind of expression node, execute
+ * it via the general ExecEvalExpr() code; the only difference is that we
+ * don't get a chance to pass a special ReturnSetInfo to any functions
+ * buried in the expression.
*/
if (funcexpr && IsA(funcexpr, FuncExprState) &&
IsA(funcexpr->expr, FuncExpr))
{
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;
/*
* Evaluate the function's argument list.
*
* Note: ideally, we'd do this in the per-tuple context, but then the
- * argument values would disappear when we reset the context in
- * the inner loop. So do it in caller context. Perhaps we should
- * make a separate context just to hold the evaluated arguments?
+ * argument values would disappear when we reset the context in the
+ * inner loop. So do it in caller context. Perhaps we should make a
+ * separate context just to hold the evaluated arguments?
*/
- MemSet(&fcinfo, 0, sizeof(fcinfo));
fcinfo.flinfo = &(fcache->func);
argDone = ExecEvalFuncArgs(&fcinfo, fcache->args, econtext);
/* We don't allow sets in the arguments of the table function */
/*
* If function is strict, and there are any NULL arguments, skip
- * calling the function and return NULL (actually an empty set).
+ * calling the function and act like it returned NULL (or an empty
+ * set, in the returns-set case).
*/
if (fcache->func.fn_strict)
{
for (i = 0; i < fcinfo.nargs; i++)
{
if (fcinfo.argnull[i])
- {
- *returnDesc = NULL;
- return NULL;
- }
+ goto no_function_result;
}
}
}
direct_function_call = false;
}
- funcrettype = exprType((Node *) funcexpr->expr);
-
- returnsTuple = (funcrettype == RECORDOID ||
- get_typtype(funcrettype) == 'c');
-
/*
- * Prepare a resultinfo node for communication. We always do this
- * even if not expecting a set result, so that we can pass
- * 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.
+ * Switch to short-lived context for calling the function or expression.
*/
- 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 or
- * expression.
- */
- callerContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
/*
* Loop to handle the ValuePerCall protocol (which is also the same
for (;;)
{
Datum result;
- HeapTuple tuple;
+
+ CHECK_FOR_INTERRUPTS();
/*
- * reset per-tuple memory context before each call of the function
- * or expression. This cleans up any local memory the function may
- * leak when called.
+ * reset per-tuple memory context before each call of the function or
+ * expression. This cleans up any local memory the function may leak
+ * when called.
*/
ResetExprContext(econtext);
/* 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
{
{
/*
* 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 useful with NULL rowtype values.
- * Currently we raise an error, but another alternative is to
- * just ignore the result and "continue" to get another row.
+ * 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 row cannot return null value")));
+ errmsg("function returning set of rows cannot return null value")));
+ }
/*
- * If first time through, build tupdesc and tuplestore for
- * result
+ * If first time through, build tupdesc and tuplestore for result
*/
if (first_time)
{
if (returnsTuple)
{
/*
- * Use the type info embedded in the rowtype Datum to
- * look up the needed tupdesc. Make a copy for the
- * query.
+ * Use the type info embedded in the rowtype Datum to look
+ * up the needed tupdesc. Make a copy for the query.
*/
HeapTupleHeader td;
td = DatumGetHeapTupleHeader(result);
- tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(td),
- HeapTupleHeaderGetTypMod(td));
- tupdesc = CreateTupleDescCopy(tupdesc);
+ tupdesc = lookup_rowtype_tupdesc_copy(HeapTupleHeaderGetTypeId(td),
+ HeapTupleHeaderGetTypMod(td));
}
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
{
- 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);
/*
first_time = false;
}
+no_function_result:
+
+ /*
+ * If we got nothing from the function (ie, an empty-set or NULL result),
+ * we have to create the tuplestore to return, and if it's a
+ * non-set-returning function then insert a single all-nulls row.
+ */
+ if (rsinfo.setResult == NULL)
+ {
+ MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(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;
}
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);
}
argList = fcache->args;
/* Need to prep callinfo structure */
- MemSet(&fcinfo, 0, sizeof(fcinfo));
- fcinfo.flinfo = &(fcache->func);
+ InitFunctionCallInfoData(fcinfo, &(fcache->func), 0, NULL, NULL);
argDone = ExecEvalFuncArgs(&fcinfo, argList, econtext);
if (argDone != ExprSingleResult)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("IS DISTINCT FROM does not support set arguments")));
+ errmsg("IS DISTINCT FROM does not support set arguments")));
Assert(fcinfo.nargs == 2);
if (fcinfo.argnull[0] && fcinfo.argnull[1])
bool typbyval;
char typalign;
char *s;
+ bits8 *bitmap;
+ int bitmask;
/* Set default values for result flags: non-null, not a set result */
*isNull = false;
if (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);
}
/* Need to prep callinfo structure */
- MemSet(&fcinfo, 0, sizeof(fcinfo));
- fcinfo.flinfo = &(sstate->fxprstate.func);
+ InitFunctionCallInfoData(fcinfo, &(sstate->fxprstate.func), 0, NULL, NULL);
argDone = ExecEvalFuncArgs(&fcinfo, sstate->fxprstate.args, econtext);
if (argDone != ExprSingleResult)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("op ANY/ALL (array) does not support set arguments")));
+ errmsg("op ANY/ALL (array) does not support set arguments")));
Assert(fcinfo.nargs == 2);
/*
- * If the array is NULL then we return NULL --- it's not very
- * meaningful to do anything else, even if the operator isn't strict.
+ * If the array is NULL then we return NULL --- it's not very meaningful
+ * to do anything else, even if the operator isn't strict.
*/
if (fcinfo.argnull[1])
{
/*
* If the array is empty, we return either FALSE or TRUE per the useOr
* flag. This is correct even if the scalar is NULL; since we would
- * evaluate the operator zero times, it matters not whether it would
- * want to return NULL.
+ * evaluate the operator zero times, it matters not whether it would want
+ * to return NULL.
*/
nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
if (nitems <= 0)
return BoolGetDatum(!useOr);
/*
- * If the scalar is NULL, and the function is strict, return NULL.
- * This is just to avoid having to test for strictness inside the
- * loop. (XXX but if arrays could have null elements, we'd need a
- * test anyway.)
+ * If the scalar is NULL, and the function is strict, return NULL; no
+ * point in iterating the loop.
*/
if (fcinfo.argnull[0] && sstate->fxprstate.func.fn_strict)
{
}
/*
- * We arrange to look up info about the element type only once per
- * series of calls, assuming the element type doesn't change
- * underneath us.
+ * We arrange to look up info about the element type only once per series
+ * of calls, assuming the element type doesn't change underneath us.
*/
if (sstate->element_type != ARR_ELEMTYPE(arr))
{
/* Loop over the array elements */
s = (char *) ARR_DATA_PTR(arr);
+ bitmap = ARR_NULLBITMAP(arr);
+ bitmask = 1;
+
for (i = 0; i < nitems; i++)
{
Datum elt;
Datum thisresult;
- /* Get array element */
- elt = fetch_att(s, typbyval, typlen);
-
- s = att_addlength(s, typlen, PointerGetDatum(s));
- s = (char *) att_align(s, typalign);
+ /* Get array element, checking for NULL */
+ if (bitmap && (*bitmap & bitmask) == 0)
+ {
+ fcinfo.arg[1] = (Datum) 0;
+ fcinfo.argnull[1] = true;
+ }
+ else
+ {
+ elt = fetch_att(s, typbyval, typlen);
+ s = att_addlength_pointer(s, typlen, s);
+ s = (char *) att_align_nominal(s, typalign);
+ fcinfo.arg[1] = elt;
+ fcinfo.argnull[1] = false;
+ }
/* Call comparison function */
- fcinfo.arg[1] = elt;
- fcinfo.argnull[1] = false;
- fcinfo.isnull = false;
- thisresult = FunctionCallInvoke(&fcinfo);
+ if (fcinfo.argnull[1] && sstate->fxprstate.func.fn_strict)
+ {
+ fcinfo.isnull = true;
+ thisresult = (Datum) 0;
+ }
+ else
+ {
+ fcinfo.isnull = false;
+ thisresult = FunctionCallInvoke(&fcinfo);
+ }
/* Combine results per OR or AND semantics */
if (fcinfo.isnull)
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;
expr_value = ExecEvalExpr(clause, econtext, isNull, NULL);
/*
- * if the expression evaluates to null, then we just cascade the null
- * back to whoever called us.
+ * if the expression evaluates to null, then we just cascade the null back
+ * to whoever called us.
*/
if (*isNull)
return expr_value;
/*
- * evaluation of 'not' is simple.. expr is false, then return 'true'
- * and vice versa.
+ * evaluation of 'not' is simple.. expr is false, then return 'true' and
+ * vice versa.
*/
return BoolGetDatum(!DatumGetBool(expr_value));
}
AnyNull = false;
/*
- * If any of the clauses is TRUE, the OR result is TRUE regardless of
- * the states of the rest of the clauses, so we can stop evaluating
- * and return TRUE immediately. If none are TRUE and one or more is
- * NULL, we return NULL; otherwise we return FALSE. This makes sense
- * when you interpret NULL as "don't know": if we have a TRUE then the
- * OR is TRUE even if we aren't sure about some of the other inputs.
- * If all the known inputs are FALSE, but we have one or more "don't
- * knows", then we have to report that we "don't know" what the OR's
- * result should be --- perhaps one of the "don't knows" would have
- * been TRUE if we'd known its value. Only when all the inputs are
- * known to be FALSE can we state confidently that the OR's result is
- * FALSE.
+ * If any of the clauses is TRUE, the OR result is TRUE regardless of the
+ * states of the rest of the clauses, so we can stop evaluating and return
+ * TRUE immediately. If none are TRUE and one or more is NULL, we return
+ * NULL; otherwise we return FALSE. This makes sense when you interpret
+ * NULL as "don't know": if we have a TRUE then the OR is TRUE even if we
+ * aren't sure about some of the other inputs. If all the known inputs are
+ * FALSE, but we have one or more "don't knows", then we have to report
+ * that we "don't know" what the OR's result should be --- perhaps one of
+ * the "don't knows" would have been TRUE if we'd known its value. Only
+ * when all the inputs are known to be FALSE can we state confidently that
+ * the OR's result is FALSE.
*/
foreach(clause, clauses)
{
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)
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
*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.
+ * 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;
{
econtext->caseValue_datum = ExecEvalExpr(caseExpr->arg,
econtext,
- &econtext->caseValue_isNull,
+ &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)
{
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)
{
/* ----------------------------------------------------------------
* ExecEvalArray - ARRAY[] expressions
- *
- * NOTE: currently, if any input value is NULL then we return a NULL array,
- * so the ARRAY[] construct can be considered strict. Eventually this will
- * change; when it does, be sure to fix contain_nonstrict_functions().
* ----------------------------------------------------------------
*/
static Datum
/* 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 NULL */
+ /* Shouldn't happen here, but if length is 0, return empty array */
if (nelems == 0)
- {
- *isNull = true;
- return (Datum) 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);
- bool eisnull;
- dvalues[i++] = ExecEvalExpr(e, econtext, &eisnull, NULL);
- if (eisnull)
- {
- *isNull = true;
- return (Datum) 0;
- }
+ 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, ndims, dims, lbs,
+ result = construct_md_array(dvalues, dnulls, ndims, dims, lbs,
element_type,
astate->elemlength,
astate->elembyval,
else
{
/* Must be nested array expressions */
- char *dat = NULL;
- Size ndatabytes = 0;
- int nbytes;
- int outer_nelems = list_length(astate->elements);
+ 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)
bool eisnull;
Datum arraydatum;
ArrayType *array;
- int elem_ndatabytes;
+ int this_ndims;
arraydatum = ExecEvalExpr(e, econtext, &eisnull, NULL);
+ /* temporarily ignore null subarrays */
if (eisnull)
{
- *isNull = true;
- return (Datum) 0;
+ haveempty = true;
+ continue;
}
array = DatumGetArrayTypeP(arraydatum);
(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.",
+ "included in ARRAY construct with element type %s.",
format_type_be(ARR_ELEMTYPE(array)),
format_type_be(element_type))));
+ this_ndims = ARR_NDIM(array);
+ /* temporarily ignore zero-dimensional subarrays */
+ if (this_ndims <= 0)
+ {
+ haveempty = true;
+ continue;
+ }
+
if (firstone)
{
/* Get sub-array details from first member */
- elem_ndims = ARR_NDIM(array);
+ 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)));
+ 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));
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),
elem_ndims * sizeof(int)) != 0)
ereport(ERROR,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
- errmsg("multidimensional arrays must have array "
- "expressions with matching dimensions")));
+ errmsg("multidimensional arrays must have array "
+ "expressions with matching dimensions")));
}
- elem_ndatabytes = ARR_SIZE(array) - ARR_OVERHEAD(elem_ndims);
- ndatabytes += elem_ndatabytes;
- if (dat == NULL)
- dat = (char *) palloc(ndatabytes);
- else
- dat = (char *) repalloc(dat, ndatabytes);
+ 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++;
+ }
- memcpy(dat + (ndatabytes - elem_ndatabytes),
- ARR_DATA_PTR(array),
- elem_ndatabytes);
+ /*
+ * 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 */
lbs[i] = elem_lbs[i - 1];
}
- nbytes = ndatabytes + ARR_OVERHEAD(ndims);
- result = (ArrayType *) palloc(nbytes);
+ if (havenulls)
+ {
+ dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nitems);
+ nbytes += dataoffset;
+ }
+ else
+ {
+ dataoffset = 0; /* marker for no null bitmap */
+ nbytes += ARR_OVERHEAD_NONULLS(ndims);
+ }
- result->size = nbytes;
+ result = (ArrayType *) palloc(nbytes);
+ SET_VARSIZE(result, nbytes);
result->ndim = ndims;
- result->flags = 0;
+ result->dataoffset = dataoffset;
result->elemtype = element_type;
memcpy(ARR_DIMS(result), dims, ndims * sizeof(int));
memcpy(ARR_LBOUND(result), lbs, ndims * sizeof(int));
- if (ndatabytes > 0)
- memcpy(ARR_DATA_PTR(result), dat, ndatabytes);
- if (dat != NULL)
- pfree(dat);
+ 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);
{
HeapTuple tuple;
Datum *values;
- char *nulls;
+ bool *isnull;
int natts;
ListCell *arg;
int i;
/* Allocate workspace */
natts = rstate->tupdesc->natts;
values = (Datum *) palloc0(natts * sizeof(Datum));
- nulls = (char *) palloc(natts * sizeof(char));
+ isnull = (bool *) palloc(natts * sizeof(bool));
/* preset to nulls in case rowtype has some later-added columns */
- memset(nulls, 'n', natts * sizeof(char));
+ memset(isnull, true, natts * sizeof(bool));
/* Evaluate field values */
i = 0;
foreach(arg, rstate->args)
{
ExprState *e = (ExprState *) lfirst(arg);
- bool eisnull;
- values[i] = ExecEvalExpr(e, econtext, &eisnull, NULL);
- nulls[i] = eisnull ? 'n' : ' ';
+ values[i] = ExecEvalExpr(e, econtext, &isnull[i], NULL);
i++;
}
- tuple = heap_formtuple(rstate->tupdesc, values, nulls);
+ tuple = heap_form_tuple(rstate->tupdesc, values, isnull);
pfree(values);
- pfree(nulls);
+ 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
* ----------------------------------------------------------------
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
*
{
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);
}
argList = nullIfExpr->args;
/* Need to prep callinfo structure */
- MemSet(&fcinfo, 0, sizeof(fcinfo));
- fcinfo.flinfo = &(nullIfExpr->func);
+ InitFunctionCallInfoData(fcinfo, &(nullIfExpr->func), 0, NULL, NULL);
argDone = ExecEvalFuncArgs(&fcinfo, argList, econtext);
if (argDone != ExprSingleResult)
ereport(ERROR,
* ----------------------------------------------------------------
*/
static Datum
-ExecEvalNullTest(GenericExprState *nstate,
+ExecEvalNullTest(NullTestState *nstate,
ExprContext *econtext,
bool *isNull,
ExprDoneCond *isDone)
if (isDone && *isDone == ExprEndResult)
return result; /* nothing to check */
- switch (ntest->nulltesttype)
+ if (nstate->argisrow && !(*isNull))
{
- case IS_NULL:
- if (*isNull)
+ HeapTupleHeader tuple;
+ Oid tupType;
+ int32 tupTypmod;
+ TupleDesc tupDesc;
+ HeapTupleData tmptup;
+ int att;
+
+ tuple = DatumGetHeapTupleHeader(result);
+
+ tupType = HeapTupleHeaderGetTypeId(tuple);
+ tupTypmod = HeapTupleHeaderGetTypMod(tuple);
+
+ /* Lookup tupdesc if first time through or if type changes */
+ tupDesc = get_cached_rowtype(tupType, tupTypmod,
+ &nstate->argdesc, econtext);
+
+ /*
+ * heap_attisnull needs a HeapTuple not a bare HeapTupleHeader.
+ */
+ tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
+ tmptup.t_data = tuple;
+
+ for (att = 1; att <= tupDesc->natts; att++)
+ {
+ /* ignore dropped columns */
+ if (tupDesc->attrs[att - 1]->attisdropped)
+ continue;
+ if (heap_attisnull(&tmptup, att))
{
- *isNull = false;
- return BoolGetDatum(true);
+ /* null field disproves IS NOT NULL */
+ if (ntest->nulltesttype == IS_NOT_NULL)
+ return BoolGetDatum(false);
}
else
- return BoolGetDatum(false);
- case IS_NOT_NULL:
- if (*isNull)
{
- *isNull = false;
- return BoolGetDatum(false);
+ /* non-null field disproves IS NULL */
+ if (ntest->nulltesttype == IS_NULL)
+ return BoolGetDatum(false);
}
- else
- return BoolGetDatum(true);
- default:
- elog(ERROR, "unrecognized nulltesttype: %d",
- (int) ntest->nulltesttype);
- return (Datum) 0; /* keep compiler quiet */
+ }
+
+ return BoolGetDatum(true);
+ }
+ else
+ {
+ /* Simple scalar-argument case, or a null rowtype datum */
+ switch (ntest->nulltesttype)
+ {
+ case IS_NULL:
+ if (*isNull)
+ {
+ *isNull = false;
+ return BoolGetDatum(true);
+ }
+ else
+ return BoolGetDatum(false);
+ case IS_NOT_NULL:
+ if (*isNull)
+ {
+ *isNull = false;
+ return BoolGetDatum(false);
+ }
+ else
+ return BoolGetDatum(true);
+ default:
+ elog(ERROR, "unrecognized nulltesttype: %d",
+ (int) ntest->nulltesttype);
+ return (Datum) 0; /* keep compiler quiet */
+ }
}
}
if (*isNull)
ereport(ERROR,
(errcode(ERRCODE_NOT_NULL_VIOLATION),
- errmsg("domain %s does not allow null values",
- format_type_be(ctest->resulttype))));
+ errmsg("domain %s does not allow null values",
+ format_type_be(ctest->resulttype))));
break;
case DOM_CONSTRAINT_CHECK:
{
* Set up value to be returned by CoerceToDomainValue
* nodes. We must save and restore prior setting of
* econtext's domainValue fields, in case this node is
- * itself within a check expression for another
- * domain.
+ * itself within a check expression for another domain.
*/
save_datum = econtext->domainValue_datum;
save_isNull = econtext->domainValue_isNull;
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);
tupTypmod = HeapTupleHeaderGetTypMod(tuple);
/* Lookup tupdesc if first time through or if type changes */
- tupDesc = fstate->argdesc;
- if (tupDesc == NULL ||
- tupType != tupDesc->tdtypeid ||
- tupTypmod != tupDesc->tdtypmod)
+ 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)
{
- MemoryContext oldcontext;
-
- tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
- /* Copy the tupdesc into query storage for safety */
- oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
- tupDesc = CreateTupleDescCopy(tupDesc);
- if (fstate->argdesc)
- FreeTupleDesc(fstate->argdesc);
- fstate->argdesc = tupDesc;
- MemoryContextSwitchTo(oldcontext);
+ *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.
+ * 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_data = tuple;
result = heap_getattr(&tmptup,
- fselect->fieldnum,
+ fieldnum,
tupDesc,
isNull);
return result;
Datum tupDatum;
TupleDesc tupDesc;
Datum *values;
- char *nulls;
+ bool *isnull;
Datum save_datum;
bool save_isNull;
ListCell *l1,
if (isDone && *isDone == ExprEndResult)
return tupDatum;
- /* Lookup tupdesc if first time through or if type changes */
- tupDesc = fstate->argdesc;
- if (tupDesc == NULL ||
- fstore->resulttype != tupDesc->tdtypeid)
- {
- MemoryContext oldcontext;
-
- tupDesc = lookup_rowtype_tupdesc(fstore->resulttype, -1);
- /* Copy the tupdesc into query storage for safety */
- oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
- tupDesc = CreateTupleDescCopy(tupDesc);
- if (fstate->argdesc)
- FreeTupleDesc(fstate->argdesc);
- fstate->argdesc = tupDesc;
- MemoryContextSwitchTo(oldcontext);
- }
+ /* 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));
- nulls = (char *) palloc(tupDesc->natts * sizeof(char));
+ isnull = (bool *) palloc(tupDesc->natts * sizeof(bool));
if (!*isNull)
{
/*
- * heap_deformtuple needs a HeapTuple not a bare HeapTupleHeader.
- * We set all the fields in the struct just in case.
+ * 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;
tmptup.t_tableOid = InvalidOid;
tmptup.t_data = tuphdr;
- heap_deformtuple(&tmptup, tupDesc, values, nulls);
+ heap_deform_tuple(&tmptup, tupDesc, values, isnull);
}
else
{
/* Convert null input tuple into an all-nulls row */
- memset(nulls, 'n', tupDesc->natts * sizeof(char));
+ memset(isnull, true, tupDesc->natts * sizeof(bool));
}
/* Result is never null */
{
ExprState *newval = (ExprState *) lfirst(l1);
AttrNumber fieldnum = lfirst_int(l2);
- bool eisnull;
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.
+ * 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 = (nulls[fieldnum - 1] == 'n');
+ econtext->caseValue_isNull = isnull[fieldnum - 1];
values[fieldnum - 1] = ExecEvalExpr(newval,
econtext,
- &eisnull,
+ &isnull[fieldnum - 1],
NULL);
- nulls[fieldnum - 1] = eisnull ? 'n' : ' ';
}
econtext->caseValue_datum = save_datum;
econtext->caseValue_isNull = save_isNull;
- tuple = heap_formtuple(tupDesc, values, nulls);
+ tuple = heap_form_tuple(tupDesc, values, isnull);
pfree(values);
- pfree(nulls);
+ pfree(isnull);
return HeapTupleGetDatum(tuple);
}
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
aggstate->aggs = lcons(astate, aggstate->aggs);
naggs = ++aggstate->numaggs;
- astate->target = ExecInitExpr(aggref->target, parent);
+ astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
+ parent);
/*
- * Complain if the aggregate's argument contains any
+ * Complain if the aggregate's arguments contain any
* aggregates; nested agg functions are semantically
- * nonsensical. (This should have been caught
- * earlier, but we defend against it here anyway.)
+ * nonsensical. (This should have been caught earlier,
+ * but we defend against it here anyway.)
*/
if (naggs != aggstate->numaggs)
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
- errmsg("aggregate function calls may not be nested")));
+ errmsg("aggregate function calls cannot be nested")));
}
else
{
/* 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->exprs = (List *)
- ExecInitExpr((Expr *) subplan->exprs, 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;
+ 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;
{
/* generic record, use runtime type assignment */
rstate->tupdesc = ExecTypeFromExprList(rowexpr->args);
- rstate->tupdesc = BlessTupleDesc(rstate->tupdesc);
+ 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(rowexpr->row_typeid, -1);
- rstate->tupdesc = CreateTupleDescCopy(rstate->tupdesc);
+ 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);
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.
+ * 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))));
+ 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.
+ * 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);
+ e = (Expr *) makeNullConst(INT4OID, -1);
}
estate = ExecInitExpr(e, parent);
outlist = lappend(outlist, estate);
state = (ExprState *) rstate;
}
break;
+ case T_RowCompareExpr:
+ {
+ RowCompareExpr *rcexpr = (RowCompareExpr *) node;
+ RowCompareExprState *rstate = makeNode(RowCompareExprState);
+ int nopers = list_length(rcexpr->opnos);
+ List *outlist;
+ ListCell *l;
+ ListCell *l2;
+ int i;
+
+ rstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRowCompare;
+ Assert(list_length(rcexpr->largs) == nopers);
+ outlist = NIL;
+ foreach(l, rcexpr->largs)
+ {
+ Expr *e = (Expr *) lfirst(l);
+ ExprState *estate;
+
+ estate = ExecInitExpr(e, parent);
+ outlist = lappend(outlist, estate);
+ }
+ rstate->largs = outlist;
+ Assert(list_length(rcexpr->rargs) == nopers);
+ outlist = NIL;
+ foreach(l, rcexpr->rargs)
+ {
+ Expr *e = (Expr *) lfirst(l);
+ ExprState *estate;
+
+ estate = ExecInitExpr(e, parent);
+ outlist = lappend(outlist, estate);
+ }
+ rstate->rargs = outlist;
+ Assert(list_length(rcexpr->opfamilies) == nopers);
+ rstate->funcs = (FmgrInfo *) palloc(nopers * sizeof(FmgrInfo));
+ i = 0;
+ forboth(l, rcexpr->opnos, l2, rcexpr->opfamilies)
+ {
+ Oid opno = lfirst_oid(l);
+ Oid opfamily = lfirst_oid(l2);
+ int strategy;
+ Oid lefttype;
+ Oid righttype;
+ 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;
+ }
+ break;
case T_CoalesceExpr:
{
CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
state = (ExprState *) cstate;
}
break;
+ case T_MinMaxExpr:
+ {
+ MinMaxExpr *minmaxexpr = (MinMaxExpr *) node;
+ MinMaxExprState *mstate = makeNode(MinMaxExprState);
+ List *outlist = NIL;
+ ListCell *l;
+ TypeCacheEntry *typentry;
+
+ mstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalMinMax;
+ foreach(l, minmaxexpr->args)
+ {
+ Expr *e = (Expr *) lfirst(l);
+ ExprState *estate;
+
+ estate = ExecInitExpr(e, parent);
+ outlist = lappend(outlist, estate);
+ }
+ mstate->args = outlist;
+ /* Look up the btree comparison function for the datatype */
+ typentry = lookup_type_cache(minmaxexpr->minmaxtype,
+ TYPECACHE_CMP_PROC);
+ if (!OidIsValid(typentry->cmp_proc))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify a comparison function for type %s",
+ format_type_be(minmaxexpr->minmaxtype))));
+
+ /*
+ * If we enforced permissions checks on index support
+ * functions, we'd need to make a check here. But the index
+ * support machinery doesn't do that, and neither does this
+ * code.
+ */
+ fmgr_info(typentry->cmp_proc, &(mstate->cfunc));
+ state = (ExprState *) mstate;
+ }
+ break;
+ case T_XmlExpr:
+ {
+ XmlExpr *xexpr = (XmlExpr *) node;
+ XmlExprState *xstate = makeNode(XmlExprState);
+ List *outlist;
+ ListCell *arg;
+
+ 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;
case T_NullTest:
{
NullTest *ntest = (NullTest *) node;
- GenericExprState *gstate = makeNode(GenericExprState);
+ NullTestState *nstate = makeNode(NullTestState);
- gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullTest;
- gstate->arg = ExecInitExpr(ntest->arg, parent);
- state = (ExprState *) gstate;
+ nstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullTest;
+ nstate->arg = ExecInitExpr(ntest->arg, parent);
+ nstate->argisrow = type_is_rowtype(exprType((Node *) ntest->arg));
+ nstate->argdesc = NULL;
+ state = (ExprState *) nstate;
}
break;
case T_BooleanTest:
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->exprs = (List *) ExecInitExpr((Expr *) node->exprs, parent);
- sstate->args = (List *) ExecInitExpr((Expr *) node->args, parent);
-
- sstate->xprstate.expr = (Expr *) node;
-
- return sstate;
-}
-
/*
* ExecPrepareExpr --- initialize for expression execution outside a normal
* Plan tree context.
*
* 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);
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;
TargetEntry *curTle = (TargetEntry *) lfirst(tl);
Assert(IsA(curTle, TargetEntry));
- if (!curTle->resdom->resjunk)
+ if (!curTle->resjunk)
len++;
}
return len;
}
-/* ----------------------------------------------------------------
- * ExecTargetList
- *
+/*
+ * ExecTargetList
* Evaluates a targetlist with respect to the given
- * expression context and returns a tuple.
+ * expression context. Returns TRUE if we were able to create
+ * a result, FALSE if we have exhausted a set-valued expression.
*
- * The caller must pass workspace for the values and nulls arrays
- * as well as the itemIsDone array. This convention saves palloc'ing
- * workspace on each call, and some callers may find it useful to examine
- * the values array directly.
+ * Results are stored into the passed values and isnull arrays.
+ * The caller must provide an itemIsDone array that persists across calls.
*
* As with ExecEvalExpr, the caller should pass isDone = NULL if not
* prepared to deal with sets of result tuples. Otherwise, a return
* of *isDone = ExprMultipleResult signifies a set element, and a return
* of *isDone = ExprEndResult signifies end of the set of tuple.
- * ----------------------------------------------------------------
+ * We assume that *isDone has been initialized to ExprSingleResult by caller.
*/
-static HeapTuple
+static bool
ExecTargetList(List *targetlist,
- TupleDesc targettype,
ExprContext *econtext,
Datum *values,
- char *nulls,
+ bool *isnull,
ExprDoneCond *itemIsDone,
ExprDoneCond *isDone)
{
MemoryContext oldContext;
ListCell *tl;
- bool isNull;
bool haveDoneSets;
- /*
- * 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.
- */
-
/*
* 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)
{
GenericExprState *gstate = (GenericExprState *) lfirst(tl);
TargetEntry *tle = (TargetEntry *) gstate->xprstate.expr;
- AttrNumber resind = tle->resdom->resno - 1;
+ AttrNumber resind = tle->resno - 1;
values[resind] = ExecEvalExpr(gstate->arg,
econtext,
- &isNull,
+ &isnull[resind],
&itemIsDone[resind]);
- nulls[resind] = isNull ? 'n' : ' ';
if (itemIsDone[resind] != ExprSingleResult)
{
if (*isDone == ExprSingleResult)
{
/*
- * all sets are done, so report that tlist expansion is
- * complete.
+ * all sets are done, so report that tlist expansion is complete.
*/
*isDone = ExprEndResult;
MemoryContextSwitchTo(oldContext);
- return NULL;
+ return false;
}
else
{
/*
- * We have some done and some undone sets. Restart the done
- * ones so that we can deliver a tuple (if possible).
+ * We have some done and some undone sets. Restart the done ones
+ * so that we can deliver a tuple (if possible).
*/
foreach(tl, targetlist)
{
GenericExprState *gstate = (GenericExprState *) lfirst(tl);
TargetEntry *tle = (TargetEntry *) gstate->xprstate.expr;
- AttrNumber resind = tle->resdom->resno - 1;
+ AttrNumber resind = tle->resno - 1;
if (itemIsDone[resind] == ExprEndResult)
{
values[resind] = ExecEvalExpr(gstate->arg,
econtext,
- &isNull,
+ &isnull[resind],
&itemIsDone[resind]);
- nulls[resind] = isNull ? 'n' : ' ';
if (itemIsDone[resind] == ExprEndResult)
{
/*
- * Oh dear, this item is returning an empty set.
- * Guess we can't make a tuple after all.
+ * Oh dear, this item is returning an empty set. Guess
+ * we can't make a tuple after all.
*/
*isDone = ExprEndResult;
break;
}
/*
- * 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?
*/
{
GenericExprState *gstate = (GenericExprState *) lfirst(tl);
TargetEntry *tle = (TargetEntry *) gstate->xprstate.expr;
- AttrNumber resind = tle->resdom->resno - 1;
+ AttrNumber resind = tle->resno - 1;
while (itemIsDone[resind] == ExprMultipleResult)
{
- (void) ExecEvalExpr(gstate->arg,
- econtext,
- &isNull,
- &itemIsDone[resind]);
+ values[resind] = ExecEvalExpr(gstate->arg,
+ econtext,
+ &isnull[resind],
+ &itemIsDone[resind]);
}
}
MemoryContextSwitchTo(oldContext);
- return NULL;
+ return false;
}
}
}
- /*
- * form the new result tuple (in the caller's memory context!)
- */
+ /* Report success */
MemoryContextSwitchTo(oldContext);
- return heap_formtuple(targettype, values, nulls);
+ 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;
- TupleDesc tupType;
- HeapTuple newTuple;
+ ExprContext *econtext;
+ int numSimpleVars;
/*
* sanity checks
*/
- if (projInfo == NULL)
- return NULL;
+ Assert(projInfo != NULL);
/*
* get the projection info we want
*/
slot = projInfo->pi_slot;
- tupType = slot->ttc_tupleDescriptor;
+ 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(projInfo->pi_targetlist,
- tupType,
- projInfo->pi_exprContext,
- projInfo->pi_tupValues,
- projInfo->pi_tupNulls,
- projInfo->pi_itemIsDone,
- 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);
}