Skip to content

Commit bf7ca15

Browse files
committed
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 398f70e 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 d685814, 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.
1 parent 1e0b436 commit bf7ca15

File tree

8 files changed

+340
-73
lines changed

8 files changed

+340
-73
lines changed

src/backend/executor/execQual.c

+77-38
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
#include "nodes/nodeFuncs.h"
5151
#include "optimizer/planner.h"
5252
#include "parser/parse_coerce.h"
53+
#include "parser/parsetree.h"
5354
#include "pgstat.h"
5455
#include "utils/acl.h"
5556
#include "utils/builtins.h"
@@ -712,6 +713,8 @@ ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext,
712713
{
713714
Var *variable = (Var *) wrvstate->xprstate.expr;
714715
TupleTableSlot *slot;
716+
TupleDesc output_tupdesc;
717+
MemoryContext oldcontext;
715718
bool needslow = false;
716719

717720
if (isDone)
@@ -787,8 +790,6 @@ ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext,
787790
/* If so, build the junkfilter in the query memory context */
788791
if (junk_filter_needed)
789792
{
790-
MemoryContext oldcontext;
791-
792793
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
793794
wrvstate->wrv_junkFilter =
794795
ExecInitJunkFilter(subplan->plan->targetlist,
@@ -860,10 +861,60 @@ ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext,
860861
needslow = true; /* need runtime check for null */
861862
}
862863

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

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

892942
if (isDone)
@@ -916,34 +966,16 @@ ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext,
916966
if (wrvstate->wrv_junkFilter != NULL)
917967
slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
918968

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

938974
/*
939-
* If the Var identifies a named composite type, label the datum with that
940-
* type; otherwise we'll use the slot's info.
975+
* Label the datum with the composite type info we identified before.
941976
*/
942-
if (variable->vartype != RECORDOID)
943-
{
944-
HeapTupleHeaderSetTypeId(dtuple, variable->vartype);
945-
HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod);
946-
}
977+
HeapTupleHeaderSetTypeId(dtuple, wrvstate->wrv_tupdesc->tdtypeid);
978+
HeapTupleHeaderSetTypMod(dtuple, wrvstate->wrv_tupdesc->tdtypmod);
947979

948980
return PointerGetDatum(dtuple);
949981
}
@@ -997,8 +1029,9 @@ ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext,
9971029
tuple = ExecFetchSlotTuple(slot);
9981030
tupleDesc = slot->tts_tupleDescriptor;
9991031

1032+
/* wrv_tupdesc is a good enough representation of the Var's rowtype */
10001033
Assert(variable->vartype != RECORDOID);
1001-
var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
1034+
var_tupdesc = wrvstate->wrv_tupdesc;
10021035

10031036
/* Check to see if any dropped attributes are non-null */
10041037
for (i = 0; i < var_tupdesc->natts; i++)
@@ -1025,12 +1058,10 @@ ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext,
10251058
dtuple = DatumGetHeapTupleHeader(ExecFetchSlotTupleDatum(slot));
10261059

10271060
/*
1028-
* Reset datum's type ID fields to match the Var.
1061+
* Label the datum with the composite type info we identified before.
10291062
*/
1030-
HeapTupleHeaderSetTypeId(dtuple, variable->vartype);
1031-
HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod);
1032-
1033-
ReleaseTupleDesc(var_tupdesc);
1063+
HeapTupleHeaderSetTypeId(dtuple, wrvstate->wrv_tupdesc->tdtypeid);
1064+
HeapTupleHeaderSetTypMod(dtuple, wrvstate->wrv_tupdesc->tdtypmod);
10341065

10351066
return PointerGetDatum(dtuple);
10361067
}
@@ -2850,8 +2881,14 @@ ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate,
28502881
cstate->initialized = false;
28512882
}
28522883

2853-
Assert(HeapTupleHeaderGetTypeId(tuple) == cstate->indesc->tdtypeid);
2854-
Assert(HeapTupleHeaderGetTypMod(tuple) == cstate->indesc->tdtypmod);
2884+
/*
2885+
* We used to be able to assert that incoming tuples are marked with
2886+
* exactly the rowtype of cstate->indesc. However, now that
2887+
* ExecEvalWholeRowVar might change the tuples' marking to plain RECORD
2888+
* due to inserting aliases, we can only make this weak test:
2889+
*/
2890+
Assert(HeapTupleHeaderGetTypeId(tuple) == cstate->indesc->tdtypeid ||
2891+
HeapTupleHeaderGetTypeId(tuple) == RECORDOID);
28552892

28562893
/* if first time through, initialize conversion map */
28572894
if (!cstate->initialized)
@@ -4375,6 +4412,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
43754412
WholeRowVarExprState *wstate = makeNode(WholeRowVarExprState);
43764413

43774414
wstate->parent = parent;
4415+
wstate->wrv_tupdesc = NULL;
43784416
wstate->wrv_junkFilter = NULL;
43794417
state = (ExprState *) wstate;
43804418
state->evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowVar;
@@ -4778,17 +4816,18 @@ ExecInitExpr(Expr *node, PlanState *parent)
47784816
/* Build tupdesc to describe result tuples */
47794817
if (rowexpr->row_typeid == RECORDOID)
47804818
{
4781-
/* generic record, use runtime type assignment */
4782-
rstate->tupdesc = ExecTypeFromExprList(rowexpr->args,
4783-
rowexpr->colnames);
4784-
BlessTupleDesc(rstate->tupdesc);
4785-
/* we won't need to redo this at runtime */
4819+
/* generic record, use types of given expressions */
4820+
rstate->tupdesc = ExecTypeFromExprList(rowexpr->args);
47864821
}
47874822
else
47884823
{
47894824
/* it's been cast to a named type, use that */
47904825
rstate->tupdesc = lookup_rowtype_tupdesc_copy(rowexpr->row_typeid, -1);
47914826
}
4827+
/* In either case, adopt RowExpr's column aliases */
4828+
ExecTypeSetColNames(rstate->tupdesc, rowexpr->colnames);
4829+
/* Bless the tupdesc in case it's now of type RECORD */
4830+
BlessTupleDesc(rstate->tupdesc);
47924831
/* Set up evaluation, skipping any deleted columns */
47934832
Assert(list_length(rowexpr->args) <= rstate->tupdesc->natts);
47944833
attrs = rstate->tupdesc->attrs;

src/backend/executor/execTuples.c

+55-10
Original file line numberDiff line numberDiff line change
@@ -943,28 +943,25 @@ ExecTypeFromTLInternal(List *targetList, bool hasoid, bool skipjunk)
943943
/*
944944
* ExecTypeFromExprList - build a tuple descriptor from a list of Exprs
945945
*
946-
* Caller must also supply a list of field names (String nodes).
946+
* This is roughly like ExecTypeFromTL, but we work from bare expressions
947+
* not TargetEntrys. No names are attached to the tupledesc's columns.
947948
*/
948949
TupleDesc
949-
ExecTypeFromExprList(List *exprList, List *namesList)
950+
ExecTypeFromExprList(List *exprList)
950951
{
951952
TupleDesc typeInfo;
952-
ListCell *le;
953-
ListCell *ln;
953+
ListCell *lc;
954954
int cur_resno = 1;
955955

956-
Assert(list_length(exprList) == list_length(namesList));
957-
958956
typeInfo = CreateTemplateTupleDesc(list_length(exprList), false);
959957

960-
forboth(le, exprList, ln, namesList)
958+
foreach(lc, exprList)
961959
{
962-
Node *e = lfirst(le);
963-
char *n = strVal(lfirst(ln));
960+
Node *e = lfirst(lc);
964961

965962
TupleDescInitEntry(typeInfo,
966963
cur_resno,
967-
n,
964+
NULL,
968965
exprType(e),
969966
exprTypmod(e),
970967
0);
@@ -977,6 +974,54 @@ ExecTypeFromExprList(List *exprList, List *namesList)
977974
return typeInfo;
978975
}
979976

977+
/*
978+
* ExecTypeSetColNames - set column names in a TupleDesc
979+
*
980+
* Column names must be provided as an alias list (list of String nodes).
981+
*
982+
* For some callers, the supplied tupdesc has a named rowtype (not RECORD)
983+
* and it is moderately likely that the alias list matches the column names
984+
* already present in the tupdesc. If we do change any column names then
985+
* we must reset the tupdesc's type to anonymous RECORD; but we avoid doing
986+
* so if no names change.
987+
*/
988+
void
989+
ExecTypeSetColNames(TupleDesc typeInfo, List *namesList)
990+
{
991+
bool modified = false;
992+
int colno = 0;
993+
ListCell *lc;
994+
995+
foreach(lc, namesList)
996+
{
997+
char *cname = strVal(lfirst(lc));
998+
Form_pg_attribute attr;
999+
1000+
/* Guard against too-long names list */
1001+
if (colno >= typeInfo->natts)
1002+
break;
1003+
attr = typeInfo->attrs[colno++];
1004+
1005+
/* Ignore empty aliases (these must be for dropped columns) */
1006+
if (cname[0] == '\0')
1007+
continue;
1008+
1009+
/* Change tupdesc only if alias is actually different */
1010+
if (strcmp(cname, NameStr(attr->attname)) != 0)
1011+
{
1012+
namestrcpy(&(attr->attname), cname);
1013+
modified = true;
1014+
}
1015+
}
1016+
1017+
/* If we modified the tupdesc, it's now a new record type */
1018+
if (modified)
1019+
{
1020+
typeInfo->tdtypeid = RECORDOID;
1021+
typeInfo->tdtypmod = -1;
1022+
}
1023+
}
1024+
9801025
/*
9811026
* BlessTupleDesc - make a completed tuple descriptor useful for SRFs
9821027
*

src/backend/executor/nodeFunctionscan.c

-19
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
#include "executor/nodeFunctionscan.h"
2727
#include "funcapi.h"
2828
#include "nodes/nodeFuncs.h"
29-
#include "parser/parsetree.h"
3029
#include "utils/builtins.h"
3130
#include "utils/memutils.h"
3231

@@ -279,8 +278,6 @@ FunctionScanState *
279278
ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
280279
{
281280
FunctionScanState *scanstate;
282-
RangeTblEntry *rte = rt_fetch(node->scan.scanrelid,
283-
estate->es_range_table);
284281
int nfuncs = list_length(node->functions);
285282
TupleDesc scan_tupdesc;
286283
int i,
@@ -494,22 +491,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
494491
Assert(attno == natts);
495492
}
496493

497-
/*
498-
* Make sure the scan result tupdesc has the column names the query
499-
* expects. This affects the output of constructs like row_to_json which
500-
* read the column names from the passed-in tupdesc.
501-
*/
502-
i = 0;
503-
foreach(lc, rte->eref->colnames)
504-
{
505-
char *attname = strVal(lfirst(lc));
506-
507-
if (i >= scan_tupdesc->natts)
508-
break; /* shouldn't happen, but just in case */
509-
namestrcpy(&(scan_tupdesc->attrs[i]->attname), attname);
510-
i++;
511-
}
512-
513494
ExecAssignScanType(&scanstate->ss, scan_tupdesc);
514495

515496
/*

src/backend/executor/nodeValuesscan.c

+1-5
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525

2626
#include "executor/executor.h"
2727
#include "executor/nodeValuesscan.h"
28-
#include "parser/parsetree.h"
2928

3029

3130
static TupleTableSlot *ValuesNext(ValuesScanState *node);
@@ -189,8 +188,6 @@ ValuesScanState *
189188
ExecInitValuesScan(ValuesScan *node, EState *estate, int eflags)
190189
{
191190
ValuesScanState *scanstate;
192-
RangeTblEntry *rte = rt_fetch(node->scan.scanrelid,
193-
estate->es_range_table);
194191
TupleDesc tupdesc;
195192
ListCell *vtl;
196193
int i;
@@ -242,8 +239,7 @@ ExecInitValuesScan(ValuesScan *node, EState *estate, int eflags)
242239
/*
243240
* get info about values list
244241
*/
245-
tupdesc = ExecTypeFromExprList((List *) linitial(node->values_lists),
246-
rte->eref->colnames);
242+
tupdesc = ExecTypeFromExprList((List *) linitial(node->values_lists));
247243

248244
ExecAssignScanType(&scanstate->ss, tupdesc);
249245

src/include/executor/executor.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,8 @@ extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate,
268268
TupleDesc tupType);
269269
extern TupleDesc ExecTypeFromTL(List *targetList, bool hasoid);
270270
extern TupleDesc ExecCleanTypeFromTL(List *targetList, bool hasoid);
271-
extern TupleDesc ExecTypeFromExprList(List *exprList, List *namesList);
271+
extern TupleDesc ExecTypeFromExprList(List *exprList);
272+
extern void ExecTypeSetColNames(TupleDesc typeInfo, List *namesList);
272273
extern void UpdateChangedParamSet(PlanState *node, Bitmapset *newchg);
273274

274275
typedef struct TupOutputState

src/include/nodes/execnodes.h

+1
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,7 @@ typedef struct WholeRowVarExprState
578578
{
579579
ExprState xprstate;
580580
struct PlanState *parent; /* parent PlanState, or NULL if none */
581+
TupleDesc wrv_tupdesc; /* descriptor for resulting tuples */
581582
JunkFilter *wrv_junkFilter; /* JunkFilter to remove resjunk cols */
582583
} WholeRowVarExprState;
583584

0 commit comments

Comments
 (0)