Skip to content

Commit 19ccaf9

Browse files
committed
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.
1 parent 0bb3185 commit 19ccaf9

File tree

6 files changed

+314
-31
lines changed

6 files changed

+314
-31
lines changed

src/backend/executor/execQual.c

Lines changed: 64 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
#include "nodes/nodeFuncs.h"
4949
#include "optimizer/planner.h"
5050
#include "parser/parse_coerce.h"
51+
#include "parser/parsetree.h"
5152
#include "pgstat.h"
5253
#include "utils/acl.h"
5354
#include "utils/builtins.h"
@@ -710,6 +711,8 @@ ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext,
710711
{
711712
Var *variable = (Var *) wrvstate->xprstate.expr;
712713
TupleTableSlot *slot;
714+
TupleDesc output_tupdesc;
715+
MemoryContext oldcontext;
713716
bool needslow = false;
714717

715718
if (isDone)
@@ -785,8 +788,6 @@ ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext,
785788
/* If so, build the junkfilter in the query memory context */
786789
if (junk_filter_needed)
787790
{
788-
MemoryContext oldcontext;
789-
790791
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
791792
wrvstate->wrv_junkFilter =
792793
ExecInitJunkFilter(subplan->plan->targetlist,
@@ -858,10 +859,61 @@ ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext,
858859
needslow = true; /* need runtime check for null */
859860
}
860861

862+
/*
863+
* Use the variable's declared rowtype as the descriptor for the
864+
* output values. In particular, we *must* absorb any attisdropped
865+
* markings.
866+
*/
867+
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
868+
output_tupdesc = CreateTupleDescCopy(var_tupdesc);
869+
MemoryContextSwitchTo(oldcontext);
870+
861871
ReleaseTupleDesc(var_tupdesc);
862872
}
873+
else
874+
{
875+
/*
876+
* In the RECORD case, we use the input slot's rowtype as the
877+
* descriptor for the output values, modulo possibly assigning new
878+
* column names below.
879+
*/
880+
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
881+
output_tupdesc = CreateTupleDescCopy(slot->tts_tupleDescriptor);
882+
MemoryContextSwitchTo(oldcontext);
883+
}
863884

864-
/* Skip the checking on future executions of node */
885+
/*
886+
* Construct a tuple descriptor for the composite values we'll produce,
887+
* and make sure its record type is "blessed". The main reason to do this
888+
* is to be sure that operations such as row_to_json() will see the
889+
* desired column names when they look up the descriptor from the type
890+
* information embedded in the composite values.
891+
*
892+
* We already got the correct physical datatype info above, but now we
893+
* should try to find the source RTE and adopt its column aliases, in case
894+
* they are different from the original rowtype's names. But to minimize
895+
* compatibility breakage, don't do this for Vars of named composite
896+
* types, only for Vars of type RECORD.
897+
*
898+
* If we can't locate the RTE, assume the column names we've got are OK.
899+
* (As of this writing, the only cases where we can't locate the RTE are
900+
* in execution of trigger WHEN clauses, and then the Var will have the
901+
* trigger's relation's rowtype, so its names are fine.)
902+
*/
903+
if (variable->vartype == RECORDOID &&
904+
econtext->ecxt_estate &&
905+
variable->varno <= list_length(econtext->ecxt_estate->es_range_table))
906+
{
907+
RangeTblEntry *rte = rt_fetch(variable->varno,
908+
econtext->ecxt_estate->es_range_table);
909+
910+
ExecTypeSetColNames(output_tupdesc, rte->eref->colnames);
911+
}
912+
913+
/* Bless the tupdesc if needed, and save it in the execution state */
914+
wrvstate->wrv_tupdesc = BlessTupleDesc(output_tupdesc);
915+
916+
/* Skip all the above on future executions of node */
865917
if (needslow)
866918
wrvstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowSlow;
867919
else
@@ -884,7 +936,6 @@ ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext,
884936
{
885937
Var *variable = (Var *) wrvstate->xprstate.expr;
886938
TupleTableSlot *slot;
887-
TupleDesc slot_tupdesc;
888939
HeapTupleHeader dtuple;
889940

890941
if (isDone)
@@ -914,34 +965,16 @@ ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext,
914965
if (wrvstate->wrv_junkFilter != NULL)
915966
slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
916967

917-
/*
918-
* If it's a RECORD Var, we'll use the slot's type ID info. It's likely
919-
* that the slot's type is also RECORD; if so, make sure it's been
920-
* "blessed", so that the Datum can be interpreted later. (Note: we must
921-
* do this here, not in ExecEvalWholeRowVar, because some plan trees may
922-
* return different slots at different times. We have to be ready to
923-
* bless additional slots during the run.)
924-
*/
925-
slot_tupdesc = slot->tts_tupleDescriptor;
926-
if (variable->vartype == RECORDOID &&
927-
slot_tupdesc->tdtypeid == RECORDOID &&
928-
slot_tupdesc->tdtypmod < 0)
929-
assign_record_type_typmod(slot_tupdesc);
930-
931968
/*
932969
* Copy the slot tuple and make sure any toasted fields get detoasted.
933970
*/
934971
dtuple = DatumGetHeapTupleHeader(ExecFetchSlotTupleDatum(slot));
935972

936973
/*
937-
* If the Var identifies a named composite type, label the datum with that
938-
* type; otherwise we'll use the slot's info.
974+
* Label the datum with the composite type info we identified before.
939975
*/
940-
if (variable->vartype != RECORDOID)
941-
{
942-
HeapTupleHeaderSetTypeId(dtuple, variable->vartype);
943-
HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod);
944-
}
976+
HeapTupleHeaderSetTypeId(dtuple, wrvstate->wrv_tupdesc->tdtypeid);
977+
HeapTupleHeaderSetTypMod(dtuple, wrvstate->wrv_tupdesc->tdtypmod);
945978

946979
return PointerGetDatum(dtuple);
947980
}
@@ -995,8 +1028,9 @@ ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext,
9951028
tuple = ExecFetchSlotTuple(slot);
9961029
tupleDesc = slot->tts_tupleDescriptor;
9971030

1031+
/* wrv_tupdesc is a good enough representation of the Var's rowtype */
9981032
Assert(variable->vartype != RECORDOID);
999-
var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
1033+
var_tupdesc = wrvstate->wrv_tupdesc;
10001034

10011035
/* Check to see if any dropped attributes are non-null */
10021036
for (i = 0; i < var_tupdesc->natts; i++)
@@ -1023,12 +1057,10 @@ ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext,
10231057
dtuple = DatumGetHeapTupleHeader(ExecFetchSlotTupleDatum(slot));
10241058

10251059
/*
1026-
* Reset datum's type ID fields to match the Var.
1060+
* Label the datum with the composite type info we identified before.
10271061
*/
1028-
HeapTupleHeaderSetTypeId(dtuple, variable->vartype);
1029-
HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod);
1030-
1031-
ReleaseTupleDesc(var_tupdesc);
1062+
HeapTupleHeaderSetTypeId(dtuple, wrvstate->wrv_tupdesc->tdtypeid);
1063+
HeapTupleHeaderSetTypMod(dtuple, wrvstate->wrv_tupdesc->tdtypmod);
10321064

10331065
return PointerGetDatum(dtuple);
10341066
}
@@ -4356,6 +4388,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
43564388
WholeRowVarExprState *wstate = makeNode(WholeRowVarExprState);
43574389

43584390
wstate->parent = parent;
4391+
wstate->wrv_tupdesc = NULL;
43594392
wstate->wrv_junkFilter = NULL;
43604393
state = (ExprState *) wstate;
43614394
state->evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowVar;

src/backend/executor/execTuples.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -983,6 +983,49 @@ ExecTypeFromExprList(List *exprList, List *namesList)
983983
return typeInfo;
984984
}
985985

986+
/*
987+
* ExecTypeSetColNames - set column names in a TupleDesc
988+
*
989+
* Column names must be provided as an alias list (list of String nodes).
990+
* We set names only in TupleDesc columns that lacked one before.
991+
*/
992+
void
993+
ExecTypeSetColNames(TupleDesc typeInfo, List *namesList)
994+
{
995+
bool modified = false;
996+
int colno = 0;
997+
ListCell *lc;
998+
999+
foreach(lc, namesList)
1000+
{
1001+
char *cname = strVal(lfirst(lc));
1002+
Form_pg_attribute attr;
1003+
1004+
/* Guard against too-long names list */
1005+
if (colno >= typeInfo->natts)
1006+
break;
1007+
attr = typeInfo->attrs[colno++];
1008+
1009+
/* Ignore empty aliases (these must be for dropped columns) */
1010+
if (cname[0] == '\0')
1011+
continue;
1012+
1013+
/* Change tupdesc only if we didn't have a name before */
1014+
if (NameStr(attr->attname)[0] == '\0')
1015+
{
1016+
namestrcpy(&(attr->attname), cname);
1017+
modified = true;
1018+
}
1019+
}
1020+
1021+
/* If we modified the tupdesc, it's now a new record type */
1022+
if (modified)
1023+
{
1024+
typeInfo->tdtypeid = RECORDOID;
1025+
typeInfo->tdtypmod = -1;
1026+
}
1027+
}
1028+
9861029
/*
9871030
* BlessTupleDesc - make a completed tuple descriptor useful for SRFs
9881031
*

src/include/executor/executor.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate,
263263
extern TupleDesc ExecTypeFromTL(List *targetList, bool hasoid);
264264
extern TupleDesc ExecCleanTypeFromTL(List *targetList, bool hasoid);
265265
extern TupleDesc ExecTypeFromExprList(List *exprList, List *namesList);
266+
extern void ExecTypeSetColNames(TupleDesc typeInfo, List *namesList);
266267
extern void UpdateChangedParamSet(PlanState *node, Bitmapset *newchg);
267268

268269
typedef struct TupOutputState

src/include/nodes/execnodes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,7 @@ typedef struct WholeRowVarExprState
569569
ExprState xprstate;
570570
struct PlanState *parent; /* parent PlanState, or NULL if none */
571571
JunkFilter *wrv_junkFilter; /* JunkFilter to remove resjunk cols */
572+
TupleDesc wrv_tupdesc; /* descriptor for resulting tuples */
572573
} WholeRowVarExprState;
573574

574575
/* ----------------

0 commit comments

Comments
 (0)