Re: unsupportable composite type partition keys - Mailing list pgsql-hackers

From Tom Lane
Subject Re: unsupportable composite type partition keys
Date
Msg-id 7782.1577051475@sss.pgh.pa.us
Whole thread Raw
In response to Re: unsupportable composite type partition keys  (Tom Lane <tgl@sss.pgh.pa.us>)
Responses Re: unsupportable composite type partition keys
List pgsql-hackers
I wrote:
> Now as far as point 1 goes, I think it's not really that awful to use
> CheckAttributeType() with a dummy attribute name.  The attached
> incomplete patch uses "partition key" which causes it to emit errors
> like
> regression=# create table fool (a int, b int) partition by list ((row(a, b)));
> ERROR:  column "partition key" has pseudo-type record
> I don't think that that's unacceptable.  But if we wanted to improve it,
> we could imagine adding another flag, say CHKATYPE_IS_PARTITION_KEY,
> that doesn't affect CheckAttributeType's semantics, just the wording of
> the error messages it throws.

Here's a fleshed-out patch that does it like that.

While poking at this, I also started to wonder why CheckAttributeType
wasn't recursing into ranges, since those are our other kind of
container type.  And the answer is that it must, because we allow
creation of ranges over composite types:

regression=# create table foo (f1 int, f2 int);
CREATE TABLE
regression=# create type foorange as range (subtype = foo);
CREATE TYPE
regression=# alter table foo add column r foorange;
ALTER TABLE

Simple things still work on table foo, but surely this is exactly
what CheckAttributeType is supposed to be preventing.  With the
second attached patch you get

regression=# alter table foo add column r foorange;
ERROR:  composite type foo cannot be made a member of itself

The second patch needs to go back all the way, the first one
only as far as we have partitions.

            regards, tom lane

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 8404904..82398da 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -572,6 +572,10 @@ CheckAttributeNamesTypes(TupleDesc tupdesc, char relkind,
  * are reliably identifiable only within a session, since the identity info
  * may use a typmod that is only locally assigned.  The caller is expected
  * to know whether these cases are safe.)
+ *
+ * flags can also control the phrasing of the error messages.  If
+ * CHKATYPE_IS_PARTKEY is specified, "attname" should be a partition key
+ * column number as text, not a real column name.
  * --------------------------------
  */
 void
@@ -598,10 +602,19 @@ CheckAttributeType(const char *attname,
         if (!((atttypid == ANYARRAYOID && (flags & CHKATYPE_ANYARRAY)) ||
               (atttypid == RECORDOID && (flags & CHKATYPE_ANYRECORD)) ||
               (atttypid == RECORDARRAYOID && (flags & CHKATYPE_ANYRECORD))))
-            ereport(ERROR,
-                    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-                     errmsg("column \"%s\" has pseudo-type %s",
-                            attname, format_type_be(atttypid))));
+        {
+            if (flags & CHKATYPE_IS_PARTKEY)
+                ereport(ERROR,
+                        (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+                /* translator: first %s is an integer not a name */
+                         errmsg("partition key column %s has pseudo-type %s",
+                                attname, format_type_be(atttypid))));
+            else
+                ereport(ERROR,
+                        (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+                         errmsg("column \"%s\" has pseudo-type %s",
+                                attname, format_type_be(atttypid))));
+        }
     }
     else if (att_typtype == TYPTYPE_DOMAIN)
     {
@@ -648,7 +661,7 @@ CheckAttributeType(const char *attname,
             CheckAttributeType(NameStr(attr->attname),
                                attr->atttypid, attr->attcollation,
                                containing_rowtypes,
-                               flags);
+                               flags & ~CHKATYPE_IS_PARTKEY);
         }

         relation_close(relation, AccessShareLock);
@@ -670,11 +683,21 @@ CheckAttributeType(const char *attname,
      * useless, and it cannot be dumped, so we must disallow it.
      */
     if (!OidIsValid(attcollation) && type_is_collatable(atttypid))
-        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.")));
+    {
+        if (flags & CHKATYPE_IS_PARTKEY)
+            ereport(ERROR,
+                    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+            /* translator: first %s is an integer not a name */
+                     errmsg("no collation was derived for partition key column %s with collatable type %s",
+                            attname, format_type_be(atttypid)),
+                     errhint("Use the COLLATE clause to set the collation explicitly.")));
+        else
+            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.")));
+    }
 }

 /*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e8e004e..845f010 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -15096,12 +15096,24 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu
         {
             /* Expression */
             Node       *expr = pelem->expr;
+            char        partattname[16];

             Assert(expr != NULL);
             atttype = exprType(expr);
             attcollation = exprCollation(expr);

             /*
+             * The expression must be of a storable type (e.g., not RECORD).
+             * The test is the same as for whether a table column is of a safe
+             * type (which is why we needn't check for the non-expression
+             * case).
+             */
+            snprintf(partattname, sizeof(partattname), "%d", attn + 1);
+            CheckAttributeType(partattname,
+                               atttype, attcollation,
+                               NIL, CHKATYPE_IS_PARTKEY);
+
+            /*
              * Strip any top-level COLLATE clause.  This ensures that we treat
              * "x COLLATE y" and "(x COLLATE y)" alike.
              */
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index eec71c2..2e63137 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -22,6 +22,7 @@
 /* flag bits for CheckAttributeType/CheckAttributeNamesTypes */
 #define CHKATYPE_ANYARRAY        0x01    /* allow ANYARRAY */
 #define CHKATYPE_ANYRECORD        0x02    /* allow RECORD and RECORD[] */
+#define CHKATYPE_IS_PARTKEY        0x04    /* attname is part key # not column */

 typedef struct RawColumnDefault
 {
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index f630168..50de4b3 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -375,7 +375,7 @@ CREATE TABLE partitioned (
 ERROR:  cannot use subquery in partition key expression
 CREATE TABLE partitioned (
     a int
-) PARTITION BY RANGE (('a'));
+) PARTITION BY RANGE ((42));
 ERROR:  cannot use constant expression as partition key
 CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
 CREATE TABLE partitioned (
@@ -402,6 +402,17 @@ CREATE TABLE partitioned (
 ERROR:  cannot use system column "xmin" in partition key
 LINE 3: ) PARTITION BY RANGE (xmin);
                               ^
+-- cannot use pseudotypes
+CREATE TABLE partitioned (
+    a int,
+    b int
+) PARTITION BY RANGE (((a, b)));
+ERROR:  partition key column 1 has pseudo-type record
+CREATE TABLE partitioned (
+    a int,
+    b int
+) PARTITION BY RANGE (a, ('unknown'));
+ERROR:  partition key column 2 has pseudo-type unknown
 -- functions in key must be immutable
 CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
 CREATE TABLE partitioned (
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index e835b65..9a40d7b 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -361,7 +361,7 @@ CREATE TABLE partitioned (

 CREATE TABLE partitioned (
     a int
-) PARTITION BY RANGE (('a'));
+) PARTITION BY RANGE ((42));

 CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
 CREATE TABLE partitioned (
@@ -384,6 +384,16 @@ CREATE TABLE partitioned (
     a int
 ) PARTITION BY RANGE (xmin);

+-- cannot use pseudotypes
+CREATE TABLE partitioned (
+    a int,
+    b int
+) PARTITION BY RANGE (((a, b)));
+CREATE TABLE partitioned (
+    a int,
+    b int
+) PARTITION BY RANGE (a, ('unknown'));
+
 -- functions in key must be immutable
 CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
 CREATE TABLE partitioned (
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 82398da..452a7f3 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -668,6 +668,15 @@ CheckAttributeType(const char *attname,
 
         containing_rowtypes = list_delete_last(containing_rowtypes);
     }
+    else if (att_typtype == TYPTYPE_RANGE)
+    {
+        /*
+         * If it's a range, recurse to check its subtype.
+         */
+        CheckAttributeType(attname, get_range_subtype(atttypid), attcollation,
+                           containing_rowtypes,
+                           flags);
+    }
     else if (OidIsValid((att_typelem = get_element_type(atttypid))))
     {
         /*

pgsql-hackers by date:

Previous
From: Noah Misch
Date:
Subject: Re: mdclose() does not cope w/ FileClose() failure
Next
From: Jeff Davis
Date:
Subject: Re: Memory-Bounded Hash Aggregation