1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-03 09:13:20 +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.  It's cheap for transformValuesClause
to determine the common typmod while transforming a multi-row VALUES, but
it'd be less cheap for expandRTE() and get_rte_attribute_type() to
re-determine that info every time they're asked --- possibly a lot less
cheap, if the VALUES has many rows.  Therefore, the best fix is to record
the common typmods explicitly in a list in the VALUES RTE, as we were
already doing for column collations.  This looks quite a bit like what
we're doing for CTE RTEs, so we can save a little bit of space and code by
unifying the representation for those two RTE types.  They both now share
coltypes/coltypmods/colcollations fields.  (At some point it might seem
desirable to populate those fields for all RTE types; but right now it
looks like constructing them for other RTE types would add more code and
cycles than it would save.)

The RTE change requires a catversion bump, so this fix is only usable
in HEAD.  If we fix this at all in the back branches, the patch will
need to look quite different.

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-08 11:40:02 -05:00
parent 2560d244b4
commit 0b78106cd4
12 changed files with 151 additions and 109 deletions

View File

@@ -633,10 +633,11 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
* RTE.
*/
List *exprsLists = NIL;
List *collations = NIL;
List *coltypes = NIL;
List *coltypmods = NIL;
List *colcollations = NIL;
int sublist_length = -1;
bool lateral = false;
int i;
Assert(selectStmt->intoClause == NULL);
@@ -703,11 +704,20 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
}
/*
* Although we don't really need collation info, let's just make sure
* we provide a correctly-sized list in the VALUES RTE.
* Construct column type/typmod/collation lists for the VALUES RTE.
* Every expression in each column has been coerced to the type/typmod
* of the corresponding target column or subfield, so it's sufficient
* to look at the exprType/exprTypmod of the first row. We don't care
* about the collation labeling, so just fill in InvalidOid for that.
*/
for (i = 0; i < sublist_length; i++)
collations = lappend_oid(collations, InvalidOid);
foreach(lc, (List *) linitial(exprsLists))
{
Node *val = (Node *) lfirst(lc);
coltypes = lappend_oid(coltypes, exprType(val));
coltypmods = lappend_int(coltypmods, exprTypmod(val));
colcollations = lappend_oid(colcollations, InvalidOid);
}
/*
* Ordinarily there can't be any current-level Vars in the expression
@@ -722,7 +732,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
/*
* Generate the VALUES RTE
*/
rte = addRangeTableEntryForValues(pstate, exprsLists, collations,
rte = addRangeTableEntryForValues(pstate, exprsLists,
coltypes, coltypmods, colcollations,
NULL, lateral, true);
rtr = makeNode(RangeTblRef);
/* assume new rte is at end */
@@ -1274,7 +1285,9 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
{
Query *qry = makeNode(Query);
List *exprsLists;
List *collations;
List *coltypes = NIL;
List *coltypmods = NIL;
List *colcollations = NIL;
List **colexprs = NULL;
int sublist_length = -1;
bool lateral = false;
@@ -1360,8 +1373,8 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
/*
* Now resolve the common types of the columns, and coerce everything to
* those types. Then identify the common collation, if any, of each
* column.
* those types. Then identify the common typmod and common collation, if
* any, of each column.
*
* We must do collation processing now because (1) assign_query_collations
* doesn't process rangetable entries, and (2) we need to label the VALUES
@@ -1372,11 +1385,12 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
*
* Note we modify the per-column expression lists in-place.
*/
collations = NIL;
for (i = 0; i < sublist_length; i++)
{
Oid coltype;
int32 coltypmod = -1;
Oid colcoll;
bool first = true;
coltype = select_common_type(pstate, colexprs[i], "VALUES", NULL);
@@ -1386,11 +1400,24 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
col = coerce_to_common_type(pstate, col, coltype, "VALUES");
lfirst(lc) = (void *) col;
if (first)
{
coltypmod = exprTypmod(col);
first = false;
}
else
{
/* As soon as we see a non-matching typmod, fall back to -1 */
if (coltypmod >= 0 && coltypmod != exprTypmod(col))
coltypmod = -1;
}
}
colcoll = select_common_collation(pstate, colexprs[i], true);
collations = lappend_oid(collations, colcoll);
coltypes = lappend_oid(coltypes, coltype);
coltypmods = lappend_int(coltypmods, coltypmod);
colcollations = lappend_oid(colcollations, colcoll);
}
/*
@@ -1432,7 +1459,8 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
/*
* Generate the VALUES RTE
*/
rte = addRangeTableEntryForValues(pstate, exprsLists, collations,
rte = addRangeTableEntryForValues(pstate, exprsLists,
coltypes, coltypmods, colcollations,
NULL, lateral, true);
addRTEtoQuery(pstate, rte, true, true, true);

View File

@@ -1635,7 +1635,9 @@ addRangeTableEntryForFunction(ParseState *pstate,
RangeTblEntry *
addRangeTableEntryForValues(ParseState *pstate,
List *exprs,
List *collations,
List *coltypes,
List *coltypmods,
List *colcollations,
Alias *alias,
bool lateral,
bool inFromCl)
@@ -1652,7 +1654,9 @@ addRangeTableEntryForValues(ParseState *pstate,
rte->relid = InvalidOid;
rte->subquery = NULL;
rte->values_lists = exprs;
rte->values_collations = collations;
rte->coltypes = coltypes;
rte->coltypmods = coltypmods;
rte->colcollations = colcollations;
rte->alias = alias;
eref = alias ? copyObject(alias) : makeAlias(refname, NIL);
@@ -1822,9 +1826,9 @@ addRangeTableEntryForCTE(ParseState *pstate,
parser_errposition(pstate, rv->location)));
}
rte->ctecoltypes = cte->ctecoltypes;
rte->ctecoltypmods = cte->ctecoltypmods;
rte->ctecolcollations = cte->ctecolcollations;
rte->coltypes = cte->ctecoltypes;
rte->coltypmods = cte->ctecoltypmods;
rte->colcollations = cte->ctecolcollations;
rte->alias = alias;
if (alias)
@@ -2153,46 +2157,6 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
}
}
break;
case RTE_VALUES:
{
/* Values RTE */
ListCell *aliasp_item = list_head(rte->eref->colnames);
ListCell *lcv;
ListCell *lcc;
varattno = 0;
forboth(lcv, (List *) linitial(rte->values_lists),
lcc, rte->values_collations)
{
Node *col = (Node *) lfirst(lcv);
Oid colcollation = lfirst_oid(lcc);
varattno++;
if (colnames)
{
/* Assume there is one alias per column */
char *label = strVal(lfirst(aliasp_item));
*colnames = lappend(*colnames,
makeString(pstrdup(label)));
aliasp_item = lnext(aliasp_item);
}
if (colvars)
{
Var *varnode;
varnode = makeVar(rtindex, varattno,
exprType(col),
exprTypmod(col),
colcollation,
sublevels_up);
varnode->location = location;
*colvars = lappend(*colvars, varnode);
}
}
}
break;
case RTE_JOIN:
{
/* Join RTE */
@@ -2262,17 +2226,19 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
}
}
break;
case RTE_VALUES:
case RTE_CTE:
{
/* Values or CTE RTE */
ListCell *aliasp_item = list_head(rte->eref->colnames);
ListCell *lct;
ListCell *lcm;
ListCell *lcc;
varattno = 0;
forthree(lct, rte->ctecoltypes,
lcm, rte->ctecoltypmods,
lcc, rte->ctecolcollations)
forthree(lct, rte->coltypes,
lcm, rte->coltypmods,
lcc, rte->colcollations)
{
Oid coltype = lfirst_oid(lct);
int32 coltypmod = lfirst_int(lcm);
@@ -2285,7 +2251,8 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
/* Assume there is one alias per output column */
char *label = strVal(lfirst(aliasp_item));
*colnames = lappend(*colnames, makeString(pstrdup(label)));
*colnames = lappend(*colnames,
makeString(pstrdup(label)));
aliasp_item = lnext(aliasp_item);
}
@@ -2296,6 +2263,8 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
varnode = makeVar(rtindex, varattno,
coltype, coltypmod, colcoll,
sublevels_up);
varnode->location = location;
*colvars = lappend(*colvars, varnode);
}
}
@@ -2654,22 +2623,6 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
rte->eref->aliasname)));
}
break;
case RTE_VALUES:
{
/* Values RTE --- get type info from first sublist */
/* collation is stored separately, though */
List *collist = (List *) linitial(rte->values_lists);
Node *col;
if (attnum < 1 || attnum > list_length(collist))
elog(ERROR, "values list %s does not have attribute %d",
rte->eref->aliasname, attnum);
col = (Node *) list_nth(collist, attnum - 1);
*vartype = exprType(col);
*vartypmod = exprTypmod(col);
*varcollid = list_nth_oid(rte->values_collations, attnum - 1);
}
break;
case RTE_JOIN:
{
/*
@@ -2685,13 +2638,14 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
*varcollid = exprCollation(aliasvar);
}
break;
case RTE_VALUES:
case RTE_CTE:
{
/* CTE RTE --- get type info from lists in the RTE */
Assert(attnum > 0 && attnum <= list_length(rte->ctecoltypes));
*vartype = list_nth_oid(rte->ctecoltypes, attnum - 1);
*vartypmod = list_nth_int(rte->ctecoltypmods, attnum - 1);
*varcollid = list_nth_oid(rte->ctecolcollations, attnum - 1);
/* VALUES or CTE RTE --- get type info from lists in the RTE */
Assert(attnum > 0 && attnum <= list_length(rte->coltypes));
*vartype = list_nth_oid(rte->coltypes, attnum - 1);
*vartypmod = list_nth_int(rte->coltypmods, attnum - 1);
*varcollid = list_nth_oid(rte->colcollations, attnum - 1);
}
break;
default: