Skip to content

Commit 2afe282

Browse files
committed
Fix reporting of column typmods for multi-row VALUES constructs.
expandRTE() and get_rte_attribute_type() reported the exprType() and exprTypmod() values of the expressions in the first row of the VALUES as being the column type/typmod returned by the VALUES RTE. That's fine for the data type, since we coerce all expressions in a column to have the same common type. But we don't coerce them to have a common typmod, so it was possible for rows after the first one to return values that violate the claimed column typmod. This leads to the incorrect result seen in bug #14448 from Hassan Mahmood, as well as some other corner-case misbehaviors. The desired behavior is the same as we use in other type-unification cases: report the common typmod if there is one, but otherwise return -1 indicating no particular constraint. We fixed this in HEAD by deriving the typmods during transformValuesClause and storing them in the RTE, but that's not a feasible solution in the back branches. Instead, just use a brute-force approach of determining the correct common typmod during expandRTE() and get_rte_attribute_type(). Simple testing says that that doesn't really cost much, at least not in common cases where expandRTE() is only used once per query. It turns out that get_rte_attribute_type() is typically never used at all on VALUES RTEs, so the inefficiency there is of no great concern. Report: https://postgr.es/m/20161205143037.4377.60754@wrigleys.postgresql.org Discussion: https://postgr.es/m/27429.1480968538@sss.pgh.pa.us
1 parent 8e403f2 commit 2afe282

File tree

3 files changed

+147
-4
lines changed

3 files changed

+147
-4
lines changed

src/backend/parser/parse_relation.c

Lines changed: 97 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
5050
int rtindex, int sublevels_up,
5151
int location, bool include_dropped,
5252
List **colnames, List **colvars);
53+
static int32 *getValuesTypmods(RangeTblEntry *rte);
5354
static int specialAttNum(const char *attname);
5455
static bool isQueryUsingTempRelation_walker(Node *node, void *context);
5556

@@ -1814,9 +1815,22 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
18141815
{
18151816
/* Values RTE */
18161817
ListCell *aliasp_item = list_head(rte->eref->colnames);
1818+
int32 *coltypmods;
18171819
ListCell *lcv;
18181820
ListCell *lcc;
18191821

1822+
/*
1823+
* It's okay to extract column types from the expressions in
1824+
* the first row, since all rows will have been coerced to the
1825+
* same types. Their typmods might not be the same though, so
1826+
* we potentially need to examine all rows to compute those.
1827+
* Column collations are pre-computed in values_collations.
1828+
*/
1829+
if (colvars)
1830+
coltypmods = getValuesTypmods(rte);
1831+
else
1832+
coltypmods = NULL;
1833+
18201834
varattno = 0;
18211835
forboth(lcv, (List *) linitial(rte->values_lists),
18221836
lcc, rte->values_collations)
@@ -1841,13 +1855,15 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
18411855

18421856
varnode = makeVar(rtindex, varattno,
18431857
exprType(col),
1844-
exprTypmod(col),
1858+
coltypmods[varattno - 1],
18451859
colcollation,
18461860
sublevels_up);
18471861
varnode->location = location;
18481862
*colvars = lappend(*colvars, varnode);
18491863
}
18501864
}
1865+
if (coltypmods)
1866+
pfree(coltypmods);
18511867
}
18521868
break;
18531869
case RTE_JOIN:
@@ -1953,6 +1969,8 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
19531969
varnode = makeVar(rtindex, varattno,
19541970
coltype, coltypmod, colcoll,
19551971
sublevels_up);
1972+
varnode->location = location;
1973+
19561974
*colvars = lappend(*colvars, varnode);
19571975
}
19581976
}
@@ -2043,6 +2061,74 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref,
20432061
}
20442062
}
20452063

2064+
/*
2065+
* getValuesTypmods -- expandRTE subroutine
2066+
*
2067+
* Identify per-column typmods for the given VALUES RTE. Returns a
2068+
* palloc'd array.
2069+
*/
2070+
static int32 *
2071+
getValuesTypmods(RangeTblEntry *rte)
2072+
{
2073+
int32 *coltypmods;
2074+
List *firstrow;
2075+
int ncolumns,
2076+
nvalid,
2077+
i;
2078+
ListCell *lc;
2079+
2080+
Assert(rte->values_lists != NIL);
2081+
firstrow = (List *) linitial(rte->values_lists);
2082+
ncolumns = list_length(firstrow);
2083+
coltypmods = (int32 *) palloc(ncolumns * sizeof(int32));
2084+
nvalid = 0;
2085+
2086+
/* Collect the typmods from the first VALUES row */
2087+
i = 0;
2088+
foreach(lc, firstrow)
2089+
{
2090+
Node *col = (Node *) lfirst(lc);
2091+
2092+
coltypmods[i] = exprTypmod(col);
2093+
if (coltypmods[i] >= 0)
2094+
nvalid++;
2095+
i++;
2096+
}
2097+
2098+
/*
2099+
* Scan remaining rows; as soon as we have a non-matching typmod for a
2100+
* column, reset that typmod to -1. We can bail out early if all typmods
2101+
* become -1.
2102+
*/
2103+
if (nvalid > 0)
2104+
{
2105+
for_each_cell(lc, lnext(list_head(rte->values_lists)))
2106+
{
2107+
List *thisrow = (List *) lfirst(lc);
2108+
ListCell *lc2;
2109+
2110+
Assert(list_length(thisrow) == ncolumns);
2111+
i = 0;
2112+
foreach(lc2, thisrow)
2113+
{
2114+
Node *col = (Node *) lfirst(lc2);
2115+
2116+
if (coltypmods[i] >= 0 && coltypmods[i] != exprTypmod(col))
2117+
{
2118+
coltypmods[i] = -1;
2119+
nvalid--;
2120+
}
2121+
i++;
2122+
}
2123+
2124+
if (nvalid <= 0)
2125+
break;
2126+
}
2127+
}
2128+
2129+
return coltypmods;
2130+
}
2131+
20462132
/*
20472133
* expandRelAttrs -
20482134
* Workhorse for "*" expansion: produce a list of targetentries
@@ -2256,18 +2342,25 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
22562342
break;
22572343
case RTE_VALUES:
22582344
{
2259-
/* Values RTE --- get type info from first sublist */
2260-
/* collation is stored separately, though */
2345+
/*
2346+
* Values RTE --- we can get type info from first sublist, but
2347+
* typmod may require scanning all sublists, and collation is
2348+
* stored separately. Using getValuesTypmods() is overkill,
2349+
* but this path is taken so seldom for VALUES that it's not
2350+
* worth writing extra code.
2351+
*/
22612352
List *collist = (List *) linitial(rte->values_lists);
22622353
Node *col;
2354+
int32 *coltypmods = getValuesTypmods(rte);
22632355

22642356
if (attnum < 1 || attnum > list_length(collist))
22652357
elog(ERROR, "values list %s does not have attribute %d",
22662358
rte->eref->aliasname, attnum);
22672359
col = (Node *) list_nth(collist, attnum - 1);
22682360
*vartype = exprType(col);
2269-
*vartypmod = exprTypmod(col);
2361+
*vartypmod = coltypmods[attnum - 1];
22702362
*varcollid = list_nth_oid(rte->values_collations, attnum - 1);
2363+
pfree(coltypmods);
22712364
}
22722365
break;
22732366
case RTE_JOIN:

src/test/regress/expected/create_view.out

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,43 @@ SELECT relname, relkind, reloptions FROM pg_class
288288
mysecview4 | v | {security_barrier=false}
289289
(4 rows)
290290

291+
-- This test checks that proper typmods are assigned in a multi-row VALUES
292+
CREATE VIEW tt1 AS
293+
SELECT * FROM (
294+
VALUES
295+
('abc'::varchar(3), '0123456789', 42, 'abcd'::varchar(4)),
296+
('0123456789', 'abc'::varchar(3), 42.12, 'abc'::varchar(4))
297+
) vv(a,b,c,d);
298+
\d+ tt1
299+
View "testviewschm2.tt1"
300+
Column | Type | Modifiers | Storage | Description
301+
--------+----------------------+-----------+----------+-------------
302+
a | character varying | | extended |
303+
b | character varying | | extended |
304+
c | numeric | | main |
305+
d | character varying(4) | | extended |
306+
View definition:
307+
SELECT vv.a,
308+
vv.b,
309+
vv.c,
310+
vv.d
311+
FROM ( VALUES ('abc'::character varying(3),'0123456789'::character varying,42,'abcd'::character varying(4)), ('0123456789'::character varying,'abc'::character varying(3),42.12,'abc'::character varying(4))) vv(a, b, c, d);
312+
313+
SELECT * FROM tt1;
314+
a | b | c | d
315+
------------+------------+-------+------
316+
abc | 0123456789 | 42 | abcd
317+
0123456789 | abc | 42.12 | abc
318+
(2 rows)
319+
320+
SELECT a::varchar(3) FROM tt1;
321+
a
322+
-----
323+
abc
324+
012
325+
(2 rows)
326+
327+
DROP VIEW tt1;
291328
-- Test view decompilation in the face of relation renaming conflicts
292329
CREATE TABLE tt1 (f1 int, f2 int, f3 text);
293330
CREATE TABLE tx1 (x1 int, x2 int, x3 text);

src/test/regress/sql/create_view.sql

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,19 @@ SELECT relname, relkind, reloptions FROM pg_class
224224
'mysecview3'::regclass, 'mysecview4'::regclass)
225225
ORDER BY relname;
226226

227+
-- This test checks that proper typmods are assigned in a multi-row VALUES
228+
229+
CREATE VIEW tt1 AS
230+
SELECT * FROM (
231+
VALUES
232+
('abc'::varchar(3), '0123456789', 42, 'abcd'::varchar(4)),
233+
('0123456789', 'abc'::varchar(3), 42.12, 'abc'::varchar(4))
234+
) vv(a,b,c,d);
235+
\d+ tt1
236+
SELECT * FROM tt1;
237+
SELECT a::varchar(3) FROM tt1;
238+
DROP VIEW tt1;
239+
227240
-- Test view decompilation in the face of relation renaming conflicts
228241

229242
CREATE TABLE tt1 (f1 int, f2 int, f3 text);

0 commit comments

Comments
 (0)