Commit b9cff97f authored by Peter Eisentraut's avatar Peter Eisentraut

Don't allow CREATE TABLE AS to create a column with invalid collation

It is possible that an expression ends up with a collatable type but
without a collation.  CREATE TABLE AS could then create a table based
on that.  But such a column cannot be dumped with valid SQL syntax, so
we disallow creating such a column.

per test report from Noah Misch
parent 8d3b421f
...@@ -429,6 +429,7 @@ CheckAttributeNamesTypes(TupleDesc tupdesc, char relkind, ...@@ -429,6 +429,7 @@ CheckAttributeNamesTypes(TupleDesc tupdesc, char relkind,
{ {
CheckAttributeType(NameStr(tupdesc->attrs[i]->attname), CheckAttributeType(NameStr(tupdesc->attrs[i]->attname),
tupdesc->attrs[i]->atttypid, tupdesc->attrs[i]->atttypid,
tupdesc->attrs[i]->attcollation,
allow_system_table_mods); allow_system_table_mods);
} }
} }
...@@ -442,7 +443,7 @@ CheckAttributeNamesTypes(TupleDesc tupdesc, char relkind, ...@@ -442,7 +443,7 @@ CheckAttributeNamesTypes(TupleDesc tupdesc, char relkind,
* -------------------------------- * --------------------------------
*/ */
void void
CheckAttributeType(const char *attname, Oid atttypid, CheckAttributeType(const char *attname, Oid atttypid, Oid attcollation,
bool allow_system_table_mods) bool allow_system_table_mods)
{ {
char att_typtype = get_typtype(atttypid); char att_typtype = get_typtype(atttypid);
...@@ -493,12 +494,24 @@ CheckAttributeType(const char *attname, Oid atttypid, ...@@ -493,12 +494,24 @@ CheckAttributeType(const char *attname, Oid atttypid,
if (attr->attisdropped) if (attr->attisdropped)
continue; continue;
CheckAttributeType(NameStr(attr->attname), attr->atttypid, CheckAttributeType(NameStr(attr->attname), attr->atttypid, attr->attcollation,
allow_system_table_mods); allow_system_table_mods);
} }
relation_close(relation, AccessShareLock); relation_close(relation, AccessShareLock);
} }
/*
* This might not be strictly invalid per SQL standard, but it is
* pretty useless, and it cannot be dumped, so we must disallow
* it.
*/
if (type_is_collatable(atttypid) && !OidIsValid(attcollation))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("no collation was derived for column \"%s\" with collatable type %s",
attname, format_type_be(atttypid)),
errhint("Use the COLLATE clause to set the collation explicitly.")));
} }
/* /*
......
...@@ -352,6 +352,8 @@ ConstructTupleDescriptor(Relation heapRelation, ...@@ -352,6 +352,8 @@ ConstructTupleDescriptor(Relation heapRelation,
to->atthasdef = false; to->atthasdef = false;
to->attislocal = true; to->attislocal = true;
to->attinhcount = 0; to->attinhcount = 0;
to->attcollation = collationObjectId[i];
} }
else else
{ {
...@@ -388,6 +390,8 @@ ConstructTupleDescriptor(Relation heapRelation, ...@@ -388,6 +390,8 @@ ConstructTupleDescriptor(Relation heapRelation,
to->atttypmod = -1; to->atttypmod = -1;
to->attislocal = true; to->attislocal = true;
to->attcollation = collationObjectId[i];
ReleaseSysCache(tuple); ReleaseSysCache(tuple);
/* /*
...@@ -399,11 +403,9 @@ ConstructTupleDescriptor(Relation heapRelation, ...@@ -399,11 +403,9 @@ ConstructTupleDescriptor(Relation heapRelation,
* whether a table column is of a safe type (which is why we * whether a table column is of a safe type (which is why we
* needn't check for the non-expression case). * needn't check for the non-expression case).
*/ */
CheckAttributeType(NameStr(to->attname), to->atttypid, false); CheckAttributeType(NameStr(to->attname), to->atttypid, to->attcollation, false);
} }
to->attcollation = collationObjectId[i];
/* /*
* We do not yet have the correct relation OID for the index, so just * We do not yet have the correct relation OID for the index, so just
* set it invalid for now. InitializeAttributeOids() will fix it * set it invalid for now. InitializeAttributeOids() will fix it
......
...@@ -4206,7 +4206,7 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel, ...@@ -4206,7 +4206,7 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
typeOid = HeapTupleGetOid(typeTuple); typeOid = HeapTupleGetOid(typeTuple);
/* make sure datatype is legal for a column */ /* make sure datatype is legal for a column */
CheckAttributeType(colDef->colname, typeOid, false); CheckAttributeType(colDef->colname, typeOid, collOid, false);
/* construct new attribute's pg_attribute entry */ /* construct new attribute's pg_attribute entry */
attribute.attrelid = myrelid; attribute.attrelid = myrelid;
...@@ -6515,7 +6515,7 @@ ATPrepAlterColumnType(List **wqueue, ...@@ -6515,7 +6515,7 @@ ATPrepAlterColumnType(List **wqueue,
typenameTypeIdModColl(NULL, typeName, &targettype, &targettypmod, &targetcollid); typenameTypeIdModColl(NULL, typeName, &targettype, &targettypmod, &targetcollid);
/* make sure datatype is legal for a column */ /* make sure datatype is legal for a column */
CheckAttributeType(colName, targettype, false); CheckAttributeType(colName, targettype, targetcollid, false);
if (tab->relkind == RELKIND_RELATION) if (tab->relkind == RELKIND_RELATION)
{ {
......
...@@ -117,7 +117,7 @@ extern Form_pg_attribute SystemAttributeByName(const char *attname, ...@@ -117,7 +117,7 @@ extern Form_pg_attribute SystemAttributeByName(const char *attname,
extern void CheckAttributeNamesTypes(TupleDesc tupdesc, char relkind, extern void CheckAttributeNamesTypes(TupleDesc tupdesc, char relkind,
bool allow_system_table_mods); bool allow_system_table_mods);
extern void CheckAttributeType(const char *attname, Oid atttypid, extern void CheckAttributeType(const char *attname, Oid atttypid, Oid attcollation,
bool allow_system_table_mods); bool allow_system_table_mods);
#endif /* HEAP_H */ #endif /* HEAP_H */
...@@ -627,6 +627,9 @@ ERROR: collation mismatch between implicit collations "en_US.utf8" and "C" ...@@ -627,6 +627,9 @@ ERROR: collation mismatch between implicit collations "en_US.utf8" and "C"
LINE 1: SELECT a, b FROM collate_test1 EXCEPT SELECT a, b FROM colla... LINE 1: SELECT a, b FROM collate_test1 EXCEPT SELECT a, b FROM colla...
^ ^
HINT: You can override the collation by applying the COLLATE clause to one or both expressions. HINT: You can override the collation by applying the COLLATE clause to one or both expressions.
CREATE TABLE test_u AS SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test3; -- fail
ERROR: no collation was derived for column "b" with collatable type text
HINT: Use the COLLATE clause to set the collation explicitly.
-- casting -- casting
SELECT CAST('42' AS text COLLATE "C"); SELECT CAST('42' AS text COLLATE "C");
ERROR: COLLATE clause not allowed in cast target ERROR: COLLATE clause not allowed in cast target
......
...@@ -188,6 +188,8 @@ SELECT a, b COLLATE "C" FROM collate_test1 UNION SELECT a, b FROM collate_test3 ...@@ -188,6 +188,8 @@ SELECT a, b COLLATE "C" FROM collate_test1 UNION SELECT a, b FROM collate_test3
SELECT a, b FROM collate_test1 INTERSECT SELECT a, b FROM collate_test3 ORDER BY 2; -- fail SELECT a, b FROM collate_test1 INTERSECT SELECT a, b FROM collate_test3 ORDER BY 2; -- fail
SELECT a, b FROM collate_test1 EXCEPT SELECT a, b FROM collate_test3 ORDER BY 2; -- fail SELECT a, b FROM collate_test1 EXCEPT SELECT a, b FROM collate_test3 ORDER BY 2; -- fail
CREATE TABLE test_u AS SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test3; -- fail
-- casting -- casting
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment