aboutsummaryrefslogtreecommitdiff
path: root/src/backend/executor/execQual.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2014-11-10 15:21:09 -0500
committerTom Lane <tgl@sss.pgh.pa.us>2014-11-10 15:21:09 -0500
commitbf7ca15875988a88e97302e012d7c4808bef3ea9 (patch)
treeb3fda5c24a98a2eeb816a87b557fc47ddb16996c /src/backend/executor/execQual.c
parent1e0b4365c22c9f8a1bc7a5f8339f770c767b402f (diff)
downloadpostgresql-bf7ca15875988a88e97302e012d7c4808bef3ea9.tar.gz
postgresql-bf7ca15875988a88e97302e012d7c4808bef3ea9.zip
Ensure that RowExprs and whole-row Vars produce the expected column names.
At one time it wasn't terribly important what column names were associated with the fields of a composite Datum, but since the introduction of operations like row_to_json(), it's important that looking up the rowtype ID embedded in the Datum returns the column names that users would expect. That did not work terribly well before this patch: you could get the column names of the underlying table, or column aliases from any level of the query, depending on minor details of the plan tree. You could even get totally empty field names, which is disastrous for cases like row_to_json(). To fix this for whole-row Vars, look to the RTE referenced by the Var, and make sure its column aliases are applied to the rowtype associated with the result Datums. This is a tad scary because we might have to return a transient RECORD type even though the Var is declared as having some named rowtype. In principle it should be all right because the record type will still be physically compatible with the named rowtype; but I had to weaken one Assert in ExecEvalConvertRowtype, and there might be third-party code containing similar assumptions. Similarly, RowExprs have to be willing to override the column names coming from a named composite result type and produce a RECORD when the column aliases visible at the site of the RowExpr differ from the underlying table's column names. In passing, revert the decision made in commit 398f70ec070fe601 to add an alias-list argument to ExecTypeFromExprList: better to provide that functionality in a separate function. This also reverts most of the code changes in d68581483564ec0f, which we don't need because we're no longer depending on the tupdesc found in the child plan node's result slot to be blessed. Back-patch to 9.4, but not earlier, since this solution changes the results in some cases that users might not have realized were buggy. We'll apply a more restricted form of this patch in older branches.
Diffstat (limited to 'src/backend/executor/execQual.c')
-rw-r--r--src/backend/executor/execQual.c115
1 files changed, 77 insertions, 38 deletions
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 7cfa63f37dc..88af73575c0 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -50,6 +50,7 @@
#include "nodes/nodeFuncs.h"
#include "optimizer/planner.h"
#include "parser/parse_coerce.h"
+#include "parser/parsetree.h"
#include "pgstat.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -712,6 +713,8 @@ ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext,
{
Var *variable = (Var *) wrvstate->xprstate.expr;
TupleTableSlot *slot;
+ TupleDesc output_tupdesc;
+ MemoryContext oldcontext;
bool needslow = false;
if (isDone)
@@ -787,8 +790,6 @@ ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext,
/* If so, build the junkfilter in the query memory context */
if (junk_filter_needed)
{
- MemoryContext oldcontext;
-
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
wrvstate->wrv_junkFilter =
ExecInitJunkFilter(subplan->plan->targetlist,
@@ -860,10 +861,60 @@ ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext,
needslow = true; /* need runtime check for null */
}
+ /*
+ * Use the variable's declared rowtype as the descriptor for the
+ * output values, modulo possibly assigning new column names below. In
+ * particular, we *must* absorb any attisdropped markings.
+ */
+ oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+ output_tupdesc = CreateTupleDescCopy(var_tupdesc);
+ MemoryContextSwitchTo(oldcontext);
+
ReleaseTupleDesc(var_tupdesc);
}
+ else
+ {
+ /*
+ * In the RECORD case, we use the input slot's rowtype as the
+ * descriptor for the output values, modulo possibly assigning new
+ * column names below.
+ */
+ oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+ output_tupdesc = CreateTupleDescCopy(slot->tts_tupleDescriptor);
+ MemoryContextSwitchTo(oldcontext);
+ }
- /* Skip the checking on future executions of node */
+ /*
+ * Construct a tuple descriptor for the composite values we'll produce,
+ * and make sure its record type is "blessed". The main reason to do this
+ * is to be sure that operations such as row_to_json() will see the
+ * desired column names when they look up the descriptor from the type
+ * information embedded in the composite values.
+ *
+ * We already got the correct physical datatype info above, but now we
+ * should try to find the source RTE and adopt its column aliases, in case
+ * they are different from the original rowtype's names. For example, in
+ * "SELECT foo(t) FROM tab t(x,y)", the first two columns in the composite
+ * output should be named "x" and "y" regardless of tab's column names.
+ *
+ * If we can't locate the RTE, assume the column names we've got are OK.
+ * (As of this writing, the only cases where we can't locate the RTE are
+ * in execution of trigger WHEN clauses, and then the Var will have the
+ * trigger's relation's rowtype, so its names are fine.)
+ */
+ if (econtext->ecxt_estate &&
+ variable->varno <= list_length(econtext->ecxt_estate->es_range_table))
+ {
+ RangeTblEntry *rte = rt_fetch(variable->varno,
+ econtext->ecxt_estate->es_range_table);
+
+ ExecTypeSetColNames(output_tupdesc, rte->eref->colnames);
+ }
+
+ /* Bless the tupdesc if needed, and save it in the execution state */
+ wrvstate->wrv_tupdesc = BlessTupleDesc(output_tupdesc);
+
+ /* Skip all the above on future executions of node */
if (needslow)
wrvstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowSlow;
else
@@ -886,7 +937,6 @@ ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext,
{
Var *variable = (Var *) wrvstate->xprstate.expr;
TupleTableSlot *slot;
- TupleDesc slot_tupdesc;
HeapTupleHeader dtuple;
if (isDone)
@@ -917,33 +967,15 @@ ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext,
slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
/*
- * If it's a RECORD Var, we'll use the slot's type ID info. It's likely
- * that the slot's type is also RECORD; if so, make sure it's been
- * "blessed", so that the Datum can be interpreted later. (Note: we must
- * do this here, not in ExecEvalWholeRowVar, because some plan trees may
- * return different slots at different times. We have to be ready to
- * bless additional slots during the run.)
- */
- slot_tupdesc = slot->tts_tupleDescriptor;
- if (variable->vartype == RECORDOID &&
- slot_tupdesc->tdtypeid == RECORDOID &&
- slot_tupdesc->tdtypmod < 0)
- assign_record_type_typmod(slot_tupdesc);
-
- /*
* Copy the slot tuple and make sure any toasted fields get detoasted.
*/
dtuple = DatumGetHeapTupleHeader(ExecFetchSlotTupleDatum(slot));
/*
- * If the Var identifies a named composite type, label the datum with that
- * type; otherwise we'll use the slot's info.
+ * Label the datum with the composite type info we identified before.
*/
- if (variable->vartype != RECORDOID)
- {
- HeapTupleHeaderSetTypeId(dtuple, variable->vartype);
- HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod);
- }
+ HeapTupleHeaderSetTypeId(dtuple, wrvstate->wrv_tupdesc->tdtypeid);
+ HeapTupleHeaderSetTypMod(dtuple, wrvstate->wrv_tupdesc->tdtypmod);
return PointerGetDatum(dtuple);
}
@@ -997,8 +1029,9 @@ ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext,
tuple = ExecFetchSlotTuple(slot);
tupleDesc = slot->tts_tupleDescriptor;
+ /* wrv_tupdesc is a good enough representation of the Var's rowtype */
Assert(variable->vartype != RECORDOID);
- var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
+ var_tupdesc = wrvstate->wrv_tupdesc;
/* Check to see if any dropped attributes are non-null */
for (i = 0; i < var_tupdesc->natts; i++)
@@ -1025,12 +1058,10 @@ ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext,
dtuple = DatumGetHeapTupleHeader(ExecFetchSlotTupleDatum(slot));
/*
- * Reset datum's type ID fields to match the Var.
+ * Label the datum with the composite type info we identified before.
*/
- HeapTupleHeaderSetTypeId(dtuple, variable->vartype);
- HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod);
-
- ReleaseTupleDesc(var_tupdesc);
+ HeapTupleHeaderSetTypeId(dtuple, wrvstate->wrv_tupdesc->tdtypeid);
+ HeapTupleHeaderSetTypMod(dtuple, wrvstate->wrv_tupdesc->tdtypmod);
return PointerGetDatum(dtuple);
}
@@ -2850,8 +2881,14 @@ ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate,
cstate->initialized = false;
}
- Assert(HeapTupleHeaderGetTypeId(tuple) == cstate->indesc->tdtypeid);
- Assert(HeapTupleHeaderGetTypMod(tuple) == cstate->indesc->tdtypmod);
+ /*
+ * We used to be able to assert that incoming tuples are marked with
+ * exactly the rowtype of cstate->indesc. However, now that
+ * ExecEvalWholeRowVar might change the tuples' marking to plain RECORD
+ * due to inserting aliases, we can only make this weak test:
+ */
+ Assert(HeapTupleHeaderGetTypeId(tuple) == cstate->indesc->tdtypeid ||
+ HeapTupleHeaderGetTypeId(tuple) == RECORDOID);
/* if first time through, initialize conversion map */
if (!cstate->initialized)
@@ -4375,6 +4412,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
WholeRowVarExprState *wstate = makeNode(WholeRowVarExprState);
wstate->parent = parent;
+ wstate->wrv_tupdesc = NULL;
wstate->wrv_junkFilter = NULL;
state = (ExprState *) wstate;
state->evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowVar;
@@ -4778,17 +4816,18 @@ ExecInitExpr(Expr *node, PlanState *parent)
/* Build tupdesc to describe result tuples */
if (rowexpr->row_typeid == RECORDOID)
{
- /* generic record, use runtime type assignment */
- rstate->tupdesc = ExecTypeFromExprList(rowexpr->args,
- rowexpr->colnames);
- BlessTupleDesc(rstate->tupdesc);
- /* we won't need to redo this at runtime */
+ /* generic record, use types of given expressions */
+ rstate->tupdesc = ExecTypeFromExprList(rowexpr->args);
}
else
{
/* it's been cast to a named type, use that */
rstate->tupdesc = lookup_rowtype_tupdesc_copy(rowexpr->row_typeid, -1);
}
+ /* In either case, adopt RowExpr's column aliases */
+ ExecTypeSetColNames(rstate->tupdesc, rowexpr->colnames);
+ /* Bless the tupdesc in case it's now of type RECORD */
+ BlessTupleDesc(rstate->tupdesc);
/* Set up evaluation, skipping any deleted columns */
Assert(list_length(rowexpr->args) <= rstate->tupdesc->natts);
attrs = rstate->tupdesc->attrs;