Skip to content

Commit 07ab4ec

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 5a74ff3 commit 07ab4ec

File tree

6 files changed

+313
-31
lines changed

6 files changed

+313
-31
lines changed

src/backend/executor/execQual.c

Lines changed: 64 additions & 31 deletions
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,61 @@ 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. In particular, we *must* absorb any attisdropped
867+
* 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. But to minimize
897+
* compatibility breakage, don't do this for Vars of named composite
898+
* types, only for Vars of type RECORD.
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 (variable->vartype == RECORDOID &&
906+
econtext->ecxt_estate &&
907+
variable->varno <= list_length(econtext->ecxt_estate->es_range_table))
908+
{
909+
RangeTblEntry *rte = rt_fetch(variable->varno,
910+
econtext->ecxt_estate->es_range_table);
911+
912+
ExecTypeSetColNames(output_tupdesc, rte->eref->colnames);
913+
}
914+
915+
/* Bless the tupdesc if needed, and save it in the execution state */
916+
wrvstate->wrv_tupdesc = BlessTupleDesc(output_tupdesc);
917+
918+
/* Skip all the above on future executions of node */
867919
if (needslow)
868920
wrvstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowSlow;
869921
else
@@ -886,7 +938,6 @@ ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext,
886938
{
887939
Var *variable = (Var *) wrvstate->xprstate.expr;
888940
TupleTableSlot *slot;
889-
TupleDesc slot_tupdesc;
890941
HeapTupleHeader dtuple;
891942

892943
if (isDone)
@@ -916,34 +967,16 @@ ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext,
916967
if (wrvstate->wrv_junkFilter != NULL)
917968
slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
918969

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-
933970
/*
934971
* Copy the slot tuple and make sure any toasted fields get detoasted.
935972
*/
936973
dtuple = DatumGetHeapTupleHeader(ExecFetchSlotTupleDatum(slot));
937974

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

948981
return PointerGetDatum(dtuple);
949982
}
@@ -997,8 +1030,9 @@ ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext,
9971030
tuple = ExecFetchSlotTuple(slot);
9981031
tupleDesc = slot->tts_tupleDescriptor;
9991032

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

10031037
/* Check to see if any dropped attributes are non-null */
10041038
for (i = 0; i < var_tupdesc->natts; i++)
@@ -1025,12 +1059,10 @@ ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext,
10251059
dtuple = DatumGetHeapTupleHeader(ExecFetchSlotTupleDatum(slot));
10261060

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

10351067
return PointerGetDatum(dtuple);
10361068
}
@@ -4375,6 +4407,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
43754407
WholeRowVarExprState *wstate = makeNode(WholeRowVarExprState);
43764408

43774409
wstate->parent = parent;
4410+
wstate->wrv_tupdesc = NULL;
43784411
wstate->wrv_junkFilter = NULL;
43794412
state = (ExprState *) wstate;
43804413
state->evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowVar;

src/backend/executor/execTuples.c

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

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

src/include/executor/executor.h

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

269270
typedef struct TupOutputState

src/include/nodes/execnodes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,7 @@ typedef struct WholeRowVarExprState
574574
ExprState xprstate;
575575
struct PlanState *parent; /* parent PlanState, or NULL if none */
576576
JunkFilter *wrv_junkFilter; /* JunkFilter to remove resjunk cols */
577+
TupleDesc wrv_tupdesc; /* descriptor for resulting tuples */
577578
} WholeRowVarExprState;
578579

579580
/* ----------------

src/test/regress/expected/rowtypes.out

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,3 +474,163 @@ select (row('Jim', 'Beam')).text; -- error
474474
ERROR: could not identify column "text" in record data type
475475
LINE 1: select (row('Jim', 'Beam')).text;
476476
^
477+
--
478+
-- Test that composite values are seen to have the correct column names
479+
-- (bug #11210 and other reports)
480+
--
481+
select row_to_json(i) from int8_tbl i;
482+
row_to_json
483+
------------------------------------------------
484+
{"q1":123,"q2":456}
485+
{"q1":123,"q2":4567890123456789}
486+
{"q1":4567890123456789,"q2":123}
487+
{"q1":4567890123456789,"q2":4567890123456789}
488+
{"q1":4567890123456789,"q2":-4567890123456789}
489+
(5 rows)
490+
491+
select row_to_json(i) from int8_tbl i(x,y);
492+
row_to_json
493+
------------------------------------------------
494+
{"q1":123,"q2":456}
495+
{"q1":123,"q2":4567890123456789}
496+
{"q1":4567890123456789,"q2":123}
497+
{"q1":4567890123456789,"q2":4567890123456789}
498+
{"q1":4567890123456789,"q2":-4567890123456789}
499+
(5 rows)
500+
501+
create temp view vv1 as select * from int8_tbl;
502+
select row_to_json(i) from vv1 i;
503+
row_to_json
504+
------------------------------------------------
505+
{"q1":123,"q2":456}
506+
{"q1":123,"q2":4567890123456789}
507+
{"q1":4567890123456789,"q2":123}
508+
{"q1":4567890123456789,"q2":4567890123456789}
509+
{"q1":4567890123456789,"q2":-4567890123456789}
510+
(5 rows)
511+
512+
select row_to_json(i) from vv1 i(x,y);
513+
row_to_json
514+
------------------------------------------------
515+
{"q1":123,"q2":456}
516+
{"q1":123,"q2":4567890123456789}
517+
{"q1":4567890123456789,"q2":123}
518+
{"q1":4567890123456789,"q2":4567890123456789}
519+
{"q1":4567890123456789,"q2":-4567890123456789}
520+
(5 rows)
521+
522+
select row_to_json(ss) from
523+
(select q1, q2 from int8_tbl) as ss;
524+
row_to_json
525+
------------------------------------------------
526+
{"q1":123,"q2":456}
527+
{"q1":123,"q2":4567890123456789}
528+
{"q1":4567890123456789,"q2":123}
529+
{"q1":4567890123456789,"q2":4567890123456789}
530+
{"q1":4567890123456789,"q2":-4567890123456789}
531+
(5 rows)
532+
533+
select row_to_json(ss) from
534+
(select q1, q2 from int8_tbl offset 0) as ss;
535+
row_to_json
536+
------------------------------------------------
537+
{"q1":123,"q2":456}
538+
{"q1":123,"q2":4567890123456789}
539+
{"q1":4567890123456789,"q2":123}
540+
{"q1":4567890123456789,"q2":4567890123456789}
541+
{"q1":4567890123456789,"q2":-4567890123456789}
542+
(5 rows)
543+
544+
select row_to_json(ss) from
545+
(select q1 as a, q2 as b from int8_tbl) as ss;
546+
row_to_json
547+
----------------------------------------------
548+
{"a":123,"b":456}
549+
{"a":123,"b":4567890123456789}
550+
{"a":4567890123456789,"b":123}
551+
{"a":4567890123456789,"b":4567890123456789}
552+
{"a":4567890123456789,"b":-4567890123456789}
553+
(5 rows)
554+
555+
select row_to_json(ss) from
556+
(select q1 as a, q2 as b from int8_tbl offset 0) as ss;
557+
row_to_json
558+
------------------------------------------------
559+
{"q1":123,"q2":456}
560+
{"q1":123,"q2":4567890123456789}
561+
{"q1":4567890123456789,"q2":123}
562+
{"q1":4567890123456789,"q2":4567890123456789}
563+
{"q1":4567890123456789,"q2":-4567890123456789}
564+
(5 rows)
565+
566+
select row_to_json(ss) from
567+
(select q1 as a, q2 as b from int8_tbl) as ss(x,y);
568+
row_to_json
569+
----------------------------------------------
570+
{"x":123,"y":456}
571+
{"x":123,"y":4567890123456789}
572+
{"x":4567890123456789,"y":123}
573+
{"x":4567890123456789,"y":4567890123456789}
574+
{"x":4567890123456789,"y":-4567890123456789}
575+
(5 rows)
576+
577+
select row_to_json(ss) from
578+
(select q1 as a, q2 as b from int8_tbl offset 0) as ss(x,y);
579+
row_to_json
580+
------------------------------------------------
581+
{"q1":123,"q2":456}
582+
{"q1":123,"q2":4567890123456789}
583+
{"q1":4567890123456789,"q2":123}
584+
{"q1":4567890123456789,"q2":4567890123456789}
585+
{"q1":4567890123456789,"q2":-4567890123456789}
586+
(5 rows)
587+
588+
explain (costs off)
589+
select row_to_json(q) from
590+
(select thousand, tenthous from tenk1
591+
where thousand = 42 and tenthous < 2000 offset 0) q;
592+
QUERY PLAN
593+
-------------------------------------------------------------
594+
Subquery Scan on q
595+
-> Index Only Scan using tenk1_thous_tenthous on tenk1
596+
Index Cond: ((thousand = 42) AND (tenthous < 2000))
597+
(3 rows)
598+
599+
select row_to_json(q) from
600+
(select thousand, tenthous from tenk1
601+
where thousand = 42 and tenthous < 2000 offset 0) q;
602+
row_to_json
603+
---------------------------------
604+
{"thousand":42,"tenthous":42}
605+
{"thousand":42,"tenthous":1042}
606+
(2 rows)
607+
608+
select row_to_json(q) from
609+
(select thousand as x, tenthous as y from tenk1
610+
where thousand = 42 and tenthous < 2000 offset 0) q;
611+
row_to_json
612+
-------------------
613+
{"x":42,"y":42}
614+
{"x":42,"y":1042}
615+
(2 rows)
616+
617+
select row_to_json(q) from
618+
(select thousand as x, tenthous as y from tenk1
619+
where thousand = 42 and tenthous < 2000 offset 0) q(a,b);
620+
row_to_json
621+
-------------------
622+
{"a":42,"b":42}
623+
{"a":42,"b":1042}
624+
(2 rows)
625+
626+
create temp table tt1 as select * from int8_tbl limit 2;
627+
create temp table tt2 () inherits(tt1);
628+
insert into tt2 values(0,0);
629+
select row_to_json(r) from (select q2,q1 from tt1 offset 0) r;
630+
row_to_json
631+
----------------------------------
632+
{"q2":456,"q1":123}
633+
{"q2":4567890123456789,"q1":123}
634+
{"q2":0,"q1":0}
635+
(3 rows)
636+

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy