Skip to content

Commit b09fc7c

Browse files
committed
Ensure that whole-row junk Vars are always of composite type.
The EvalPlanQual machinery assumes that whole-row Vars generated for the outputs of non-table RTEs will be of composite types. However, for the case where the RTE is a function call returning a scalar type, we were doing the wrong thing, as a result of sharing code with a parser case where the function's scalar output is wanted. (Or at least, that's what that case has done historically; it does seem a bit inconsistent.) To fix, extend makeWholeRowVar's API so that it can support both use-cases. This fixes Belinda Cussen's report of crashes during concurrent execution of UPDATEs involving joins to the result of UNNEST() --- in READ COMMITTED mode, we'd run the EvalPlanQual machinery after a conflicting row update commits, and it was expecting to get a HeapTuple not a scalar datum from the "wholerowN" variable referencing the function RTE. Back-patch to 9.0 where the current EvalPlanQual implementation appeared. In 9.1 and up, this patch also fixes failure to attach the correct collation to the Var generated for a scalar-result case. An example: regression=# select upper(x.*) from textcat('ab', 'cd') x; ERROR: could not determine which collation to use for upper() function
1 parent 87b0dcf commit b09fc7c

File tree

4 files changed

+35
-25
lines changed

4 files changed

+35
-25
lines changed

src/backend/nodes/makefuncs.c

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,17 @@ makeVar(Index varno,
101101
* with error cases, but it's not worth changing now.) The vartype indicates
102102
* a rowtype; either a named composite type, or RECORD. This function
103103
* encapsulates the logic for determining the correct rowtype OID to use.
104+
*
105+
* If allowScalar is true, then for the case where the RTE is a function
106+
* returning a non-composite result type, we produce a normal Var referencing
107+
* the function's result directly, instead of the single-column composite
108+
* value that the whole-row notation might otherwise suggest.
104109
*/
105110
Var *
106111
makeWholeRowVar(RangeTblEntry *rte,
107112
Index varno,
108-
Index varlevelsup)
113+
Index varlevelsup,
114+
bool allowScalar)
109115
{
110116
Var *result;
111117
Oid toid;
@@ -135,37 +141,32 @@ makeWholeRowVar(RangeTblEntry *rte,
135141
-1,
136142
varlevelsup);
137143
}
138-
else
144+
else if (allowScalar)
139145
{
140-
/*
141-
* func returns scalar; instead of making a whole-row Var,
142-
* just reference the function's scalar output. (XXX this
143-
* seems a tad inconsistent, especially if "f.*" was
144-
* explicitly written ...)
145-
*/
146+
/* func returns scalar; just return its output as-is */
146147
result = makeVar(varno,
147148
1,
148149
toid,
149150
-1,
150151
varlevelsup);
151152
}
152-
break;
153-
case RTE_VALUES:
154-
toid = RECORDOID;
155-
/* returns composite; same as relation case */
156-
result = makeVar(varno,
157-
InvalidAttrNumber,
158-
toid,
159-
-1,
160-
varlevelsup);
153+
else
154+
{
155+
/* func returns scalar, but we want a composite result */
156+
result = makeVar(varno,
157+
InvalidAttrNumber,
158+
RECORDOID,
159+
-1,
160+
varlevelsup);
161+
}
161162
break;
162163
default:
163164

164165
/*
165-
* RTE is a join or subselect. We represent this as a whole-row
166-
* Var of RECORD type. (Note that in most cases the Var will be
167-
* expanded to a RowExpr during planning, but that is not our
168-
* concern here.)
166+
* RTE is a join, subselect, or VALUES. We represent this as a
167+
* whole-row Var of RECORD type. (Note that in most cases the Var
168+
* will be expanded to a RowExpr during planning, but that is not
169+
* our concern here.)
169170
*/
170171
result = makeVar(varno,
171172
InvalidAttrNumber,

src/backend/optimizer/prep/preptlist.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
160160
/* Not a table, so we need the whole row as a junk var */
161161
var = makeWholeRowVar(rt_fetch(rc->rti, range_table),
162162
rc->rti,
163-
0);
163+
0,
164+
false);
164165
snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId);
165166
tle = makeTargetEntry((Expr *) var,
166167
list_length(tlist) + 1,

src/backend/parser/parse_expr.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2029,8 +2029,15 @@ transformWholeRowRef(ParseState *pstate, RangeTblEntry *rte, int location)
20292029
/* Find the RTE's rangetable location */
20302030
vnum = RTERangeTablePosn(pstate, rte, &sublevels_up);
20312031

2032-
/* Build the appropriate referencing node */
2033-
result = makeWholeRowVar(rte, vnum, sublevels_up);
2032+
/*
2033+
* Build the appropriate referencing node. Note that if the RTE is a
2034+
* function returning scalar, we create just a plain reference to the
2035+
* function value, not a composite containing a single column. This is
2036+
* pretty inconsistent at first sight, but it's what we've done
2037+
* historically. One argument for it is that "rel" and "rel.*" mean the
2038+
* same thing for composite relations, so why not for scalar functions...
2039+
*/
2040+
result = makeWholeRowVar(rte, vnum, sublevels_up, true);
20342041

20352042
/* location is not filled in by makeWholeRowVar */
20362043
result->location = location;

src/include/nodes/makefuncs.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ extern Var *makeVar(Index varno,
3131

3232
extern Var *makeWholeRowVar(RangeTblEntry *rte,
3333
Index varno,
34-
Index varlevelsup);
34+
Index varlevelsup,
35+
bool allowScalar);
3536

3637
extern TargetEntry *makeTargetEntry(Expr *expr,
3738
AttrNumber resno,

0 commit comments

Comments
 (0)