mirror of
https://github.com/postgres/postgres.git
synced 2025-06-30 21:42:05 +03:00
Ensure that whole-row Vars produce nonempty 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. However, that doesn't work terribly well: 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(). It seems unwise to change this behavior too much in stable branches, however, since users might not have noticed that they weren't getting the least-unintuitive choice of field names. Therefore, in the back branches, only change the results when the child plan has returned an actually-empty field name. (We assume that can't happen with a named rowtype, so this also dodges the issue of possibly producing RECORD-typed output from a Var with a named composite result type.) As in the sister patch for HEAD, we can get a better name to use from the Var's corresponding RTE. There is no need to touch the RowExpr code since it was already using a copy of the RTE's alias list for RECORD cases. Back-patch as far as 9.2. Before that we did not have row_to_json() so there were no core functions potentially affected by bogus field names. While 9.1 and earlier do have contrib's hstore(record) which is also affected, those versions don't seem to produce empty field names (at least not in the known problem cases), so we'll leave them alone.
This commit is contained in:
@ -48,6 +48,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"
|
||||
@ -710,6 +711,8 @@ ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext,
|
||||
{
|
||||
Var *variable = (Var *) wrvstate->xprstate.expr;
|
||||
TupleTableSlot *slot;
|
||||
TupleDesc output_tupdesc;
|
||||
MemoryContext oldcontext;
|
||||
bool needslow = false;
|
||||
|
||||
if (isDone)
|
||||
@ -785,8 +788,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,
|
||||
@ -858,10 +859,61 @@ 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. 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. But to minimize
|
||||
* compatibility breakage, don't do this for Vars of named composite
|
||||
* types, only for Vars of type RECORD.
|
||||
*
|
||||
* 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 (variable->vartype == RECORDOID &&
|
||||
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
|
||||
@ -884,7 +936,6 @@ ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext,
|
||||
{
|
||||
Var *variable = (Var *) wrvstate->xprstate.expr;
|
||||
TupleTableSlot *slot;
|
||||
TupleDesc slot_tupdesc;
|
||||
HeapTupleHeader dtuple;
|
||||
|
||||
if (isDone)
|
||||
@ -914,34 +965,16 @@ ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext,
|
||||
if (wrvstate->wrv_junkFilter != NULL)
|
||||
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);
|
||||
}
|
||||
@ -995,8 +1028,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++)
|
||||
@ -1023,12 +1057,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);
|
||||
}
|
||||
@ -4356,6 +4388,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;
|
||||
|
Reference in New Issue
Block a user