1
0
mirror of https://github.com/postgres/postgres.git synced 2025-05-05 09:19:17 +03:00

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
This commit is contained in:
Tom Lane 2016-12-09 12:01:14 -05:00
parent 79c89f1f4e
commit cf22c8cb89
3 changed files with 147 additions and 4 deletions

View File

@ -52,6 +52,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
int rtindex, int sublevels_up, int rtindex, int sublevels_up,
int location, bool include_dropped, int location, bool include_dropped,
List **colnames, List **colvars); List **colnames, List **colvars);
static int32 *getValuesTypmods(RangeTblEntry *rte);
static int specialAttNum(const char *attname); static int specialAttNum(const char *attname);
static bool isQueryUsingTempRelation_walker(Node *node, void *context); static bool isQueryUsingTempRelation_walker(Node *node, void *context);
@ -2157,9 +2158,22 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
{ {
/* Values RTE */ /* Values RTE */
ListCell *aliasp_item = list_head(rte->eref->colnames); ListCell *aliasp_item = list_head(rte->eref->colnames);
int32 *coltypmods;
ListCell *lcv; ListCell *lcv;
ListCell *lcc; ListCell *lcc;
/*
* It's okay to extract column types from the expressions in
* the first row, since all rows will have been coerced to the
* same types. Their typmods might not be the same though, so
* we potentially need to examine all rows to compute those.
* Column collations are pre-computed in values_collations.
*/
if (colvars)
coltypmods = getValuesTypmods(rte);
else
coltypmods = NULL;
varattno = 0; varattno = 0;
forboth(lcv, (List *) linitial(rte->values_lists), forboth(lcv, (List *) linitial(rte->values_lists),
lcc, rte->values_collations) lcc, rte->values_collations)
@ -2184,13 +2198,15 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
varnode = makeVar(rtindex, varattno, varnode = makeVar(rtindex, varattno,
exprType(col), exprType(col),
exprTypmod(col), coltypmods[varattno - 1],
colcollation, colcollation,
sublevels_up); sublevels_up);
varnode->location = location; varnode->location = location;
*colvars = lappend(*colvars, varnode); *colvars = lappend(*colvars, varnode);
} }
} }
if (coltypmods)
pfree(coltypmods);
} }
break; break;
case RTE_JOIN: case RTE_JOIN:
@ -2296,6 +2312,8 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
varnode = makeVar(rtindex, varattno, varnode = makeVar(rtindex, varattno,
coltype, coltypmod, colcoll, coltype, coltypmod, colcoll,
sublevels_up); sublevels_up);
varnode->location = location;
*colvars = lappend(*colvars, varnode); *colvars = lappend(*colvars, varnode);
} }
} }
@ -2412,6 +2430,74 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
} }
} }
/*
* getValuesTypmods -- expandRTE subroutine
*
* Identify per-column typmods for the given VALUES RTE. Returns a
* palloc'd array.
*/
static int32 *
getValuesTypmods(RangeTblEntry *rte)
{
int32 *coltypmods;
List *firstrow;
int ncolumns,
nvalid,
i;
ListCell *lc;
Assert(rte->values_lists != NIL);
firstrow = (List *) linitial(rte->values_lists);
ncolumns = list_length(firstrow);
coltypmods = (int32 *) palloc(ncolumns * sizeof(int32));
nvalid = 0;
/* Collect the typmods from the first VALUES row */
i = 0;
foreach(lc, firstrow)
{
Node *col = (Node *) lfirst(lc);
coltypmods[i] = exprTypmod(col);
if (coltypmods[i] >= 0)
nvalid++;
i++;
}
/*
* Scan remaining rows; as soon as we have a non-matching typmod for a
* column, reset that typmod to -1. We can bail out early if all typmods
* become -1.
*/
if (nvalid > 0)
{
for_each_cell(lc, lnext(list_head(rte->values_lists)))
{
List *thisrow = (List *) lfirst(lc);
ListCell *lc2;
Assert(list_length(thisrow) == ncolumns);
i = 0;
foreach(lc2, thisrow)
{
Node *col = (Node *) lfirst(lc2);
if (coltypmods[i] >= 0 && coltypmods[i] != exprTypmod(col))
{
coltypmods[i] = -1;
nvalid--;
}
i++;
}
if (nvalid <= 0)
break;
}
}
return coltypmods;
}
/* /*
* expandRelAttrs - * expandRelAttrs -
* Workhorse for "*" expansion: produce a list of targetentries * Workhorse for "*" expansion: produce a list of targetentries
@ -2656,18 +2742,25 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
break; break;
case RTE_VALUES: case RTE_VALUES:
{ {
/* Values RTE --- get type info from first sublist */ /*
/* collation is stored separately, though */ * Values RTE --- we can get type info from first sublist, but
* typmod may require scanning all sublists, and collation is
* stored separately. Using getValuesTypmods() is overkill,
* but this path is taken so seldom for VALUES that it's not
* worth writing extra code.
*/
List *collist = (List *) linitial(rte->values_lists); List *collist = (List *) linitial(rte->values_lists);
Node *col; Node *col;
int32 *coltypmods = getValuesTypmods(rte);
if (attnum < 1 || attnum > list_length(collist)) if (attnum < 1 || attnum > list_length(collist))
elog(ERROR, "values list %s does not have attribute %d", elog(ERROR, "values list %s does not have attribute %d",
rte->eref->aliasname, attnum); rte->eref->aliasname, attnum);
col = (Node *) list_nth(collist, attnum - 1); col = (Node *) list_nth(collist, attnum - 1);
*vartype = exprType(col); *vartype = exprType(col);
*vartypmod = exprTypmod(col); *vartypmod = coltypmods[attnum - 1];
*varcollid = list_nth_oid(rte->values_collations, attnum - 1); *varcollid = list_nth_oid(rte->values_collations, attnum - 1);
pfree(coltypmods);
} }
break; break;
case RTE_JOIN: case RTE_JOIN:

View File

@ -288,6 +288,43 @@ SELECT relname, relkind, reloptions FROM pg_class
mysecview4 | v | {security_barrier=false} mysecview4 | v | {security_barrier=false}
(4 rows) (4 rows)
-- This test checks that proper typmods are assigned in a multi-row VALUES
CREATE VIEW tt1 AS
SELECT * FROM (
VALUES
('abc'::varchar(3), '0123456789', 42, 'abcd'::varchar(4)),
('0123456789', 'abc'::varchar(3), 42.12, 'abc'::varchar(4))
) vv(a,b,c,d);
\d+ tt1
View "testviewschm2.tt1"
Column | Type | Modifiers | Storage | Description
--------+----------------------+-----------+----------+-------------
a | character varying | | extended |
b | character varying | | extended |
c | numeric | | main |
d | character varying(4) | | extended |
View definition:
SELECT vv.a,
vv.b,
vv.c,
vv.d
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);
SELECT * FROM tt1;
a | b | c | d
------------+------------+-------+------
abc | 0123456789 | 42 | abcd
0123456789 | abc | 42.12 | abc
(2 rows)
SELECT a::varchar(3) FROM tt1;
a
-----
abc
012
(2 rows)
DROP VIEW tt1;
-- Test view decompilation in the face of relation renaming conflicts -- Test view decompilation in the face of relation renaming conflicts
CREATE TABLE tt1 (f1 int, f2 int, f3 text); CREATE TABLE tt1 (f1 int, f2 int, f3 text);
CREATE TABLE tx1 (x1 int, x2 int, x3 text); CREATE TABLE tx1 (x1 int, x2 int, x3 text);

View File

@ -224,6 +224,19 @@ SELECT relname, relkind, reloptions FROM pg_class
'mysecview3'::regclass, 'mysecview4'::regclass) 'mysecview3'::regclass, 'mysecview4'::regclass)
ORDER BY relname; ORDER BY relname;
-- This test checks that proper typmods are assigned in a multi-row VALUES
CREATE VIEW tt1 AS
SELECT * FROM (
VALUES
('abc'::varchar(3), '0123456789', 42, 'abcd'::varchar(4)),
('0123456789', 'abc'::varchar(3), 42.12, 'abc'::varchar(4))
) vv(a,b,c,d);
\d+ tt1
SELECT * FROM tt1;
SELECT a::varchar(3) FROM tt1;
DROP VIEW tt1;
-- Test view decompilation in the face of relation renaming conflicts -- Test view decompilation in the face of relation renaming conflicts
CREATE TABLE tt1 (f1 int, f2 int, f3 text); CREATE TABLE tt1 (f1 int, f2 int, f3 text);