Fix handling of collations in multi-row VALUES constructs.

Per spec we ought to apply select_common_collation() across the expressions
in each column of the VALUES table.  The original coding was just taking
the first row and assuming it was representative.

This patch adds a field to struct RangeTblEntry to carry the resolved
collations, so initdb is forced for changes in stored rule representation.
This commit is contained in:
Tom Lane 2011-04-18 15:31:52 -04:00
parent 04db0fdbfa
commit 918854cc08
13 changed files with 131 additions and 53 deletions

View File

@ -1671,10 +1671,15 @@ find_expr_references_walker(Node *node,
/* /*
* Add whole-relation refs for each plain relation mentioned in the * Add whole-relation refs for each plain relation mentioned in the
* subquery's rtable, as well as refs for any datatypes and collations * subquery's rtable, as well as refs for any datatypes and collations
* used in a RECORD function's output. (Note: query_tree_walker takes * used in a RECORD function's output.
* care of recursing into RTE_FUNCTION RTEs, subqueries, etc, so no *
* need to do that here. But keep it from looking at join alias * Note: query_tree_walker takes care of recursing into RTE_FUNCTION
* lists.) * RTEs, subqueries, etc, so no need to do that here. But keep it
* from looking at join alias lists.
*
* Note: we don't need to worry about collations mentioned in
* RTE_VALUES or RTE_CTE RTEs, because those must just duplicate
* collations referenced in other parts of the Query.
*/ */
foreach(lc, query->rtable) foreach(lc, query->rtable)
{ {

View File

@ -1951,6 +1951,7 @@ _copyRangeTblEntry(RangeTblEntry *from)
COPY_NODE_FIELD(funccoltypmods); COPY_NODE_FIELD(funccoltypmods);
COPY_NODE_FIELD(funccolcollations); COPY_NODE_FIELD(funccolcollations);
COPY_NODE_FIELD(values_lists); COPY_NODE_FIELD(values_lists);
COPY_NODE_FIELD(values_collations);
COPY_STRING_FIELD(ctename); COPY_STRING_FIELD(ctename);
COPY_SCALAR_FIELD(ctelevelsup); COPY_SCALAR_FIELD(ctelevelsup);
COPY_SCALAR_FIELD(self_reference); COPY_SCALAR_FIELD(self_reference);

View File

@ -2310,6 +2310,7 @@ _equalRangeTblEntry(RangeTblEntry *a, RangeTblEntry *b)
COMPARE_NODE_FIELD(funccoltypmods); COMPARE_NODE_FIELD(funccoltypmods);
COMPARE_NODE_FIELD(funccolcollations); COMPARE_NODE_FIELD(funccolcollations);
COMPARE_NODE_FIELD(values_lists); COMPARE_NODE_FIELD(values_lists);
COMPARE_NODE_FIELD(values_collations);
COMPARE_STRING_FIELD(ctename); COMPARE_STRING_FIELD(ctename);
COMPARE_SCALAR_FIELD(ctelevelsup); COMPARE_SCALAR_FIELD(ctelevelsup);
COMPARE_SCALAR_FIELD(self_reference); COMPARE_SCALAR_FIELD(self_reference);

View File

@ -2324,6 +2324,7 @@ _outRangeTblEntry(StringInfo str, RangeTblEntry *node)
break; break;
case RTE_VALUES: case RTE_VALUES:
WRITE_NODE_FIELD(values_lists); WRITE_NODE_FIELD(values_lists);
WRITE_NODE_FIELD(values_collations);
break; break;
case RTE_CTE: case RTE_CTE:
WRITE_STRING_FIELD(ctename); WRITE_STRING_FIELD(ctename);

View File

@ -1203,6 +1203,7 @@ _readRangeTblEntry(void)
break; break;
case RTE_VALUES: case RTE_VALUES:
READ_NODE_FIELD(values_lists); READ_NODE_FIELD(values_lists);
READ_NODE_FIELD(values_collations);
break; break;
case RTE_CTE: case RTE_CTE:
READ_STRING_FIELD(ctename); READ_STRING_FIELD(ctename);

View File

@ -216,6 +216,7 @@ set_plan_references(PlannerGlobal *glob, Plan *plan,
newrte->funccoltypmods = NIL; newrte->funccoltypmods = NIL;
newrte->funccolcollations = NIL; newrte->funccolcollations = NIL;
newrte->values_lists = NIL; newrte->values_lists = NIL;
newrte->values_collations = NIL;
newrte->ctecoltypes = NIL; newrte->ctecoltypes = NIL;
newrte->ctecoltypmods = NIL; newrte->ctecoltypmods = NIL;
newrte->ctecolcollations = NIL; newrte->ctecolcollations = NIL;

View File

@ -536,7 +536,9 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
* RTE. * RTE.
*/ */
List *exprsLists = NIL; List *exprsLists = NIL;
List *collations = NIL;
int sublist_length = -1; int sublist_length = -1;
int i;
foreach(lc, selectStmt->valuesLists) foreach(lc, selectStmt->valuesLists)
{ {
@ -573,13 +575,26 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
* We must assign collations now because assign_query_collations * We must assign collations now because assign_query_collations
* doesn't process rangetable entries. We just assign all the * doesn't process rangetable entries. We just assign all the
* collations independently in each row, and don't worry about * collations independently in each row, and don't worry about
* whether they are consistent vertically either. * whether they are consistent vertically. The outer INSERT query
* isn't going to care about the collations of the VALUES columns,
* so it's not worth the effort to identify a common collation for
* each one here. (But note this does have one user-visible
* consequence: INSERT ... VALUES won't complain about conflicting
* explicit COLLATEs in a column, whereas the same VALUES
* construct in another context would complain.)
*/ */
assign_list_collations(pstate, sublist); assign_list_collations(pstate, sublist);
exprsLists = lappend(exprsLists, sublist); exprsLists = lappend(exprsLists, sublist);
} }
/*
* Although we don't really need collation info, let's just make sure
* we provide a correctly-sized list in the VALUES RTE.
*/
for (i = 0; i < sublist_length; i++)
collations = lappend_oid(collations, InvalidOid);
/* /*
* There mustn't have been any table references in the expressions, * There mustn't have been any table references in the expressions,
* else strange things would happen, like Cartesian products of those * else strange things would happen, like Cartesian products of those
@ -610,7 +625,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
/* /*
* Generate the VALUES RTE * Generate the VALUES RTE
*/ */
rte = addRangeTableEntryForValues(pstate, exprsLists, NULL, true); rte = addRangeTableEntryForValues(pstate, exprsLists, collations,
NULL, true);
rtr = makeNode(RangeTblRef); rtr = makeNode(RangeTblRef);
/* assume new rte is at end */ /* assume new rte is at end */
rtr->rtindex = list_length(pstate->p_rtable); rtr->rtindex = list_length(pstate->p_rtable);
@ -989,11 +1005,10 @@ static Query *
transformValuesClause(ParseState *pstate, SelectStmt *stmt) transformValuesClause(ParseState *pstate, SelectStmt *stmt)
{ {
Query *qry = makeNode(Query); Query *qry = makeNode(Query);
List *exprsLists = NIL; List *exprsLists;
List *collations;
List **colexprs = NULL; List **colexprs = NULL;
Oid *coltypes = NULL;
int sublist_length = -1; int sublist_length = -1;
List *newExprsLists;
RangeTblEntry *rte; RangeTblEntry *rte;
RangeTblRef *rtr; RangeTblRef *rtr;
ListCell *lc; ListCell *lc;
@ -1021,9 +1036,13 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
} }
/* /*
* For each row of VALUES, transform the raw expressions and gather type * For each row of VALUES, transform the raw expressions. This is also a
* information. This is also a handy place to reject DEFAULT nodes, which * handy place to reject DEFAULT nodes, which the grammar allows for
* the grammar allows for simplicity. * simplicity.
*
* Note that the intermediate representation we build is column-organized
* not row-organized. That simplifies the type and collation processing
* below.
*/ */
foreach(lc, stmt->valuesLists) foreach(lc, stmt->valuesLists)
{ {
@ -1041,9 +1060,8 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
{ {
/* Remember post-transformation length of first sublist */ /* Remember post-transformation length of first sublist */
sublist_length = list_length(sublist); sublist_length = list_length(sublist);
/* and allocate arrays for per-column info */ /* and allocate array for per-column lists */
colexprs = (List **) palloc0(sublist_length * sizeof(List *)); colexprs = (List **) palloc0(sublist_length * sizeof(List *));
coltypes = (Oid *) palloc0(sublist_length * sizeof(Oid));
} }
else if (sublist_length != list_length(sublist)) else if (sublist_length != list_length(sublist))
{ {
@ -1054,8 +1072,6 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
exprLocation((Node *) sublist)))); exprLocation((Node *) sublist))));
} }
exprsLists = lappend(exprsLists, sublist);
/* Check for DEFAULT and build per-column expression lists */ /* Check for DEFAULT and build per-column expression lists */
i = 0; i = 0;
foreach(lc2, sublist) foreach(lc2, sublist)
@ -1070,48 +1086,77 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
colexprs[i] = lappend(colexprs[i], col); colexprs[i] = lappend(colexprs[i], col);
i++; i++;
} }
/* Release sub-list's cells to save memory */
list_free(sublist);
} }
/* /*
* Now resolve the common types of the columns, and coerce everything to * Now resolve the common types of the columns, and coerce everything to
* those types. * those types. Then identify the 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
* RTE with column collations for use in the outer query. We don't
* consider conflict of implicit collations to be an error here; instead
* the column will just show InvalidOid as its collation, and you'll get
* a failure later if that results in failure to resolve a collation.
*
* Note we modify the per-column expression lists in-place.
*/ */
collations = NIL;
for (i = 0; i < sublist_length; i++) for (i = 0; i < sublist_length; i++)
{ {
coltypes[i] = select_common_type(pstate, colexprs[i], "VALUES", NULL); Oid coltype;
} Oid colcoll;
newExprsLists = NIL; coltype = select_common_type(pstate, colexprs[i], "VALUES", NULL);
foreach(lc, exprsLists)
{
List *sublist = (List *) lfirst(lc);
List *newsublist = NIL;
i = 0; foreach(lc, colexprs[i])
foreach(lc2, sublist)
{ {
Node *col = (Node *) lfirst(lc2); Node *col = (Node *) lfirst(lc);
col = coerce_to_common_type(pstate, col, coltypes[i], "VALUES"); col = coerce_to_common_type(pstate, col, coltype, "VALUES");
newsublist = lappend(newsublist, col); lfirst(lc) = (void *) col;
i++;
} }
/* colcoll = select_common_collation(pstate, colexprs[i], true);
* We must assign collations now because assign_query_collations
* doesn't process rangetable entries. We just assign all the
* collations independently in each row, and don't worry about whether
* they are consistent vertically either.
*/
assign_list_collations(pstate, newsublist);
newExprsLists = lappend(newExprsLists, newsublist); collations = lappend_oid(collations, colcoll);
}
/*
* Finally, rearrange the coerced expressions into row-organized lists.
*/
exprsLists = NIL;
foreach(lc, colexprs[0])
{
Node *col = (Node *) lfirst(lc);
List *sublist;
sublist = list_make1(col);
exprsLists = lappend(exprsLists, sublist);
}
list_free(colexprs[0]);
for (i = 1; i < sublist_length; i++)
{
forboth(lc, colexprs[i], lc2, exprsLists)
{
Node *col = (Node *) lfirst(lc);
List *sublist = lfirst(lc2);
/* sublist pointer in exprsLists won't need adjustment */
(void) lappend(sublist, col);
}
list_free(colexprs[i]);
} }
/* /*
* Generate the VALUES RTE * Generate the VALUES RTE
*/ */
rte = addRangeTableEntryForValues(pstate, newExprsLists, NULL, true); rte = addRangeTableEntryForValues(pstate, exprsLists, collations,
NULL, true);
rtr = makeNode(RangeTblRef); rtr = makeNode(RangeTblRef);
/* assume new rte is at end */ /* assume new rte is at end */
rtr->rtindex = list_length(pstate->p_rtable); rtr->rtindex = list_length(pstate->p_rtable);
@ -1164,7 +1209,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VALUES must not contain table references"), errmsg("VALUES must not contain table references"),
parser_errposition(pstate, parser_errposition(pstate,
locate_var_of_level((Node *) newExprsLists, 0)))); locate_var_of_level((Node *) exprsLists, 0))));
/* /*
* Another thing we can't currently support is NEW/OLD references in rules * Another thing we can't currently support is NEW/OLD references in rules
@ -1173,13 +1218,13 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
* This is a shame. FIXME * This is a shame. FIXME
*/ */
if (list_length(pstate->p_rtable) != 1 && if (list_length(pstate->p_rtable) != 1 &&
contain_vars_of_level((Node *) newExprsLists, 0)) contain_vars_of_level((Node *) exprsLists, 0))
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VALUES must not contain OLD or NEW references"), errmsg("VALUES must not contain OLD or NEW references"),
errhint("Use SELECT ... UNION ALL ... instead."), errhint("Use SELECT ... UNION ALL ... instead."),
parser_errposition(pstate, parser_errposition(pstate,
locate_var_of_level((Node *) newExprsLists, 0)))); locate_var_of_level((Node *) exprsLists, 0))));
qry->rtable = pstate->p_rtable; qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL); qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
@ -1191,13 +1236,13 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
(errcode(ERRCODE_GROUPING_ERROR), (errcode(ERRCODE_GROUPING_ERROR),
errmsg("cannot use aggregate function in VALUES"), errmsg("cannot use aggregate function in VALUES"),
parser_errposition(pstate, parser_errposition(pstate,
locate_agg_of_level((Node *) newExprsLists, 0)))); locate_agg_of_level((Node *) exprsLists, 0))));
if (pstate->p_hasWindowFuncs) if (pstate->p_hasWindowFuncs)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_WINDOWING_ERROR), (errcode(ERRCODE_WINDOWING_ERROR),
errmsg("cannot use window function in VALUES"), errmsg("cannot use window function in VALUES"),
parser_errposition(pstate, parser_errposition(pstate,
locate_windowfunc((Node *) newExprsLists)))); locate_windowfunc((Node *) exprsLists))));
assign_query_collations(pstate, qry); assign_query_collations(pstate, qry);

View File

@ -1220,6 +1220,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
RangeTblEntry * RangeTblEntry *
addRangeTableEntryForValues(ParseState *pstate, addRangeTableEntryForValues(ParseState *pstate,
List *exprs, List *exprs,
List *collations,
Alias *alias, Alias *alias,
bool inFromCl) bool inFromCl)
{ {
@ -1233,6 +1234,7 @@ addRangeTableEntryForValues(ParseState *pstate,
rte->relid = InvalidOid; rte->relid = InvalidOid;
rte->subquery = NULL; rte->subquery = NULL;
rte->values_lists = exprs; rte->values_lists = exprs;
rte->values_collations = collations;
rte->alias = alias; rte->alias = alias;
eref = alias ? copyObject(alias) : makeAlias(refname, NIL); eref = alias ? copyObject(alias) : makeAlias(refname, NIL);
@ -1657,7 +1659,9 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
ListCell *l3; ListCell *l3;
int attnum = 0; int attnum = 0;
forthree(l1, rte->funccoltypes, l2, rte->funccoltypmods, l3, rte->funccolcollations) forthree(l1, rte->funccoltypes,
l2, rte->funccoltypmods,
l3, rte->funccolcollations)
{ {
Oid attrtype = lfirst_oid(l1); Oid attrtype = lfirst_oid(l1);
int32 attrtypmod = lfirst_int(l2); int32 attrtypmod = lfirst_int(l2);
@ -1687,12 +1691,15 @@ 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);
ListCell *lc; ListCell *lcv;
ListCell *lcc;
varattno = 0; varattno = 0;
foreach(lc, (List *) linitial(rte->values_lists)) forboth(lcv, (List *) linitial(rte->values_lists),
lcc, rte->values_collations)
{ {
Node *col = (Node *) lfirst(lc); Node *col = (Node *) lfirst(lcv);
Oid colcollation = lfirst_oid(lcc);
varattno++; varattno++;
if (colnames) if (colnames)
@ -1712,7 +1719,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
varnode = makeVar(rtindex, varattno, varnode = makeVar(rtindex, varattno,
exprType(col), exprType(col),
exprTypmod(col), exprTypmod(col),
exprCollation(col), colcollation,
sublevels_up); sublevels_up);
varnode->location = location; varnode->location = location;
*colvars = lappend(*colvars, varnode); *colvars = lappend(*colvars, varnode);
@ -1789,7 +1796,9 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
ListCell *lcc; ListCell *lcc;
varattno = 0; varattno = 0;
forthree(lct, rte->ctecoltypes, lcm, rte->ctecoltypmods, lcc, rte->ctecolcollations) forthree(lct, rte->ctecoltypes,
lcm, rte->ctecoltypmods,
lcc, rte->ctecolcollations)
{ {
Oid coltype = lfirst_oid(lct); Oid coltype = lfirst_oid(lct);
int32 coltypmod = lfirst_int(lcm); int32 coltypmod = lfirst_int(lcm);
@ -2116,6 +2125,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
case RTE_VALUES: case RTE_VALUES:
{ {
/* Values RTE --- get type info from first sublist */ /* Values RTE --- get type info from first sublist */
/* collation is stored separately, though */
List *collist = (List *) linitial(rte->values_lists); List *collist = (List *) linitial(rte->values_lists);
Node *col; Node *col;
@ -2125,7 +2135,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber 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 = exprTypmod(col);
*varcollid = exprCollation(col); *varcollid = list_nth_oid(rte->values_collations, attnum - 1);
} }
break; break;
case RTE_JOIN: case RTE_JOIN:

View File

@ -53,6 +53,6 @@
*/ */
/* yyyymmddN */ /* yyyymmddN */
#define CATALOG_VERSION_NO 201104051 #define CATALOG_VERSION_NO 201104181
#endif #endif

View File

@ -725,7 +725,7 @@ typedef struct RangeTblEntry
* *
* If the function returns RECORD, funccoltypes lists the column types * If the function returns RECORD, funccoltypes lists the column types
* declared in the RTE's column type specification, funccoltypmods lists * declared in the RTE's column type specification, funccoltypmods lists
* their declared typmods, funccolcollations their collations. Otherwise, * their declared typmods, funccolcollations their collations. Otherwise,
* those fields are NIL. * those fields are NIL.
*/ */
Node *funcexpr; /* expression tree for func call */ Node *funcexpr; /* expression tree for func call */
@ -737,6 +737,7 @@ typedef struct RangeTblEntry
* Fields valid for a values RTE (else NIL): * Fields valid for a values RTE (else NIL):
*/ */
List *values_lists; /* list of expression lists */ List *values_lists; /* list of expression lists */
List *values_collations; /* OID list of column collation OIDs */
/* /*
* Fields valid for a CTE RTE (else NULL/zero): * Fields valid for a CTE RTE (else NULL/zero):

View File

@ -63,6 +63,7 @@ extern RangeTblEntry *addRangeTableEntryForFunction(ParseState *pstate,
bool inFromCl); bool inFromCl);
extern RangeTblEntry *addRangeTableEntryForValues(ParseState *pstate, extern RangeTblEntry *addRangeTableEntryForValues(ParseState *pstate,
List *exprs, List *exprs,
List *collations,
Alias *alias, Alias *alias,
bool inFromCl); bool inFromCl);
extern RangeTblEntry *addRangeTableEntryForJoin(ParseState *pstate, extern RangeTblEntry *addRangeTableEntryForJoin(ParseState *pstate,

View File

@ -460,6 +460,14 @@ ERROR: recursive query "foo" column 1 has collation "C" in non-recursive term b
LINE 2: (SELECT x FROM (VALUES('a' COLLATE "C"),('b')) t(x) LINE 2: (SELECT x FROM (VALUES('a' COLLATE "C"),('b')) t(x)
^ ^
HINT: Use the COLLATE clause to set the collation of the non-recursive term. HINT: Use the COLLATE clause to set the collation of the non-recursive term.
SELECT a, b, a < b as lt FROM
(VALUES ('a', 'B'), ('A', 'b' COLLATE "C")) v(a,b);
a | b | lt
---+---+----
a | B | f
A | b | t
(2 rows)
-- casting -- casting
SELECT CAST('42' AS text COLLATE "C"); SELECT CAST('42' AS text COLLATE "C");
ERROR: syntax error at or near "COLLATE" ERROR: syntax error at or near "COLLATE"

View File

@ -154,6 +154,9 @@ WITH RECURSIVE foo(x) AS
SELECT (x || 'c') COLLATE "POSIX" FROM foo WHERE length(x) < 10) SELECT (x || 'c') COLLATE "POSIX" FROM foo WHERE length(x) < 10)
SELECT * FROM foo; SELECT * FROM foo;
SELECT a, b, a < b as lt FROM
(VALUES ('a', 'B'), ('A', 'b' COLLATE "C")) v(a,b);
-- casting -- casting