Currently, the following query SELECT q.b = row(2) FROM unnest(ARRAY[row(1, row(2))]) AS q(a int, b record);
would fail with ERROR: column "b" has pseudo-type record This is due to CheckAttributeNamesTypes() being used on a function coldeflist as if it was a real relation definition. But in the context of a query there seems to be no harm in allowing this, as other ways of manipulating anonymous rowtypes work well, e.g.: SELECT (ARRAY[ROW(1, ROW(2))])[1]; Elvis
>From 873ecd6b31abc28c787f398d78ba2511c6e712a2 Mon Sep 17 00:00:00 2001 From: Elvis Pranskevichus <el...@magic.io> Date: Thu, 6 Dec 2018 17:16:28 -0500 Subject: [PATCH] Allow anonymous rowtypes in function return column definition Currently, the following query SELECT q.b = row(2) FROM unnest(ARRAY[row(1, row(2))]) AS q(a int, b record); would fail with ERROR: column "b" has pseudo-type record This is due to CheckAttributeNamesTypes() being used on a function coldeflist as if it was a real relation definition. But in the context of a query there seems to be no harm in allowing this, as other ways of manipulating anonymous rowtypes work well, e.g.: SELECT (ARRAY[ROW(1, ROW(2))])[1]; --- src/backend/catalog/heap.c | 23 +++++++++++++++-------- src/backend/catalog/index.c | 2 +- src/backend/commands/tablecmds.c | 4 ++-- src/backend/parser/parse_relation.c | 2 +- src/include/catalog/heap.h | 6 ++++-- src/test/regress/expected/rowtypes.out | 7 +++++++ src/test/regress/sql/rowtypes.sql | 2 ++ 7 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 8c52a1543d..ab9cb600f1 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -412,7 +412,8 @@ heap_create(const char *relname, */ void CheckAttributeNamesTypes(TupleDesc tupdesc, char relkind, - bool allow_system_table_mods) + bool allow_system_table_mods, + bool allow_anonymous_records) { int i; int j; @@ -471,7 +472,8 @@ CheckAttributeNamesTypes(TupleDesc tupdesc, char relkind, TupleDescAttr(tupdesc, i)->atttypid, TupleDescAttr(tupdesc, i)->attcollation, NIL, /* assume we're creating a new rowtype */ - allow_system_table_mods); + allow_system_table_mods, + allow_anonymous_records); } } @@ -494,7 +496,8 @@ void CheckAttributeType(const char *attname, Oid atttypid, Oid attcollation, List *containing_rowtypes, - bool allow_system_table_mods) + bool allow_system_table_mods, + bool allow_anonymous_records) { char att_typtype = get_typtype(atttypid); Oid att_typelem; @@ -507,7 +510,8 @@ CheckAttributeType(const char *attname, * catalogs (this allows creating pg_statistic and cloning it during * VACUUM FULL) */ - if (atttypid != ANYARRAYOID || !allow_system_table_mods) + if (!((atttypid == ANYARRAYOID && allow_system_table_mods) || + (atttypid == RECORDOID && allow_anonymous_records))) ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("column \"%s\" has pseudo-type %s", @@ -520,7 +524,8 @@ CheckAttributeType(const char *attname, */ CheckAttributeType(attname, getBaseType(atttypid), attcollation, containing_rowtypes, - allow_system_table_mods); + allow_system_table_mods, + allow_anonymous_records); } else if (att_typtype == TYPTYPE_COMPOSITE) { @@ -558,7 +563,8 @@ CheckAttributeType(const char *attname, CheckAttributeType(NameStr(attr->attname), attr->atttypid, attr->attcollation, containing_rowtypes, - allow_system_table_mods); + allow_system_table_mods, + allow_anonymous_records); } relation_close(relation, AccessShareLock); @@ -572,7 +578,8 @@ CheckAttributeType(const char *attname, */ CheckAttributeType(attname, att_typelem, attcollation, containing_rowtypes, - allow_system_table_mods); + allow_system_table_mods, + allow_anonymous_records); } /* @@ -1063,7 +1070,7 @@ heap_create_with_catalog(const char *relname, */ Assert(IsNormalProcessingMode() || IsBootstrapProcessingMode()); - CheckAttributeNamesTypes(tupdesc, relkind, allow_system_table_mods); + CheckAttributeNamesTypes(tupdesc, relkind, allow_system_table_mods, false); /* * This would fail later on anyway, if the relation already exists. But diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 44625a507b..ac5a285be7 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -435,7 +435,7 @@ ConstructTupleDescriptor(Relation heapRelation, */ CheckAttributeType(NameStr(to->attname), to->atttypid, to->attcollation, - NIL, false); + NIL, false, false); } /* diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 357c73073d..0a19209519 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -5488,7 +5488,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, /* make sure datatype is legal for a column */ CheckAttributeType(colDef->colname, typeOid, collOid, list_make1_oid(rel->rd_rel->reltype), - false); + false, false); /* construct new attribute's pg_attribute entry */ attribute.attrelid = myrelid; @@ -9126,7 +9126,7 @@ ATPrepAlterColumnType(List **wqueue, /* make sure datatype is legal for a column */ CheckAttributeType(colName, targettype, targetcollid, list_make1_oid(rel->rd_rel->reltype), - false); + false, false); if (tab->relkind == RELKIND_RELATION || tab->relkind == RELKIND_PARTITIONED_TABLE) diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index bf5df26009..0f224e7718 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -1567,7 +1567,7 @@ addRangeTableEntryForFunction(ParseState *pstate, * Ensure that the coldeflist defines a legal set of names (no * duplicates) and datatypes (no pseudo-types, for instance). */ - CheckAttributeNamesTypes(tupdesc, RELKIND_COMPOSITE_TYPE, false); + CheckAttributeNamesTypes(tupdesc, RELKIND_COMPOSITE_TYPE, false, true); } else ereport(ERROR, diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index c5e40ff017..9ee4824af8 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -133,12 +133,14 @@ extern Form_pg_attribute SystemAttributeByName(const char *attname, bool relhasoids); extern void CheckAttributeNamesTypes(TupleDesc tupdesc, char relkind, - bool allow_system_table_mods); + bool allow_system_table_mods, + bool allow_anonymous_records); extern void CheckAttributeType(const char *attname, Oid atttypid, Oid attcollation, List *containing_rowtypes, - bool allow_system_table_mods); + bool allow_system_table_mods, + bool allow_anonymous_records); /* pg_partitioned_table catalog manipulation functions */ extern void StorePartitionKey(Relation rel, diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out index 30053d07df..0a98725d98 100644 --- a/src/test/regress/expected/rowtypes.out +++ b/src/test/regress/expected/rowtypes.out @@ -668,6 +668,13 @@ select row(1, '(1,2)')::testtype6 *<> row(1, '(1,3)')::testtype6; (1 row) drop type testtype1, testtype2, testtype3, testtype4, testtype5, testtype6; +-- Test anonymous rowtype in coldeflist +SELECT q.b = row(2) FROM unnest(ARRAY[row(1, row(2))]) AS q(a int, b record); + ?column? +---------- + t +(1 row) + -- -- Test case derived from bug #5716: check multiple uses of a rowtype result -- diff --git a/src/test/regress/sql/rowtypes.sql b/src/test/regress/sql/rowtypes.sql index faf2e108d6..08d320426a 100644 --- a/src/test/regress/sql/rowtypes.sql +++ b/src/test/regress/sql/rowtypes.sql @@ -261,6 +261,8 @@ select row(1, '(1,2)')::testtype6 *<> row(1, '(1,3)')::testtype6; drop type testtype1, testtype2, testtype3, testtype4, testtype5, testtype6; +-- Test anonymous rowtype in coldeflist +SELECT q.b = row(2) FROM unnest(ARRAY[row(1, row(2))]) AS q(a int, b record); -- -- Test case derived from bug #5716: check multiple uses of a rowtype result -- 2.19.2