Fix ALTER DOMAIN NOT NULL syntax
authorPeter Eisentraut <[email protected]>
Mon, 15 Apr 2024 06:20:34 +0000 (08:20 +0200)
committerPeter Eisentraut <[email protected]>
Mon, 15 Apr 2024 06:34:45 +0000 (08:34 +0200)
This addresses a few problems with commit e5da0fe3c22 ("Catalog domain
not-null constraints").

In CREATE DOMAIN, a NOT NULL constraint looks like

    CREATE DOMAIN d1 AS int [ CONSTRAINT conname ] NOT NULL

(Before e5da0fe3c22, the constraint name was accepted but ignored.)

But in ALTER DOMAIN, a NOT NULL constraint looks like

    ALTER DOMAIN d1 ADD [ CONSTRAINT conname ] NOT NULL VALUE

where VALUE is where for a table constraint the column name would be.
(This works as of e5da0fe3c22.  Before e5da0fe3c22, this syntax
resulted in an internal error.)

But for domains, this latter syntax is confusing and needlessly
inconsistent between CREATE and ALTER.  So this changes it to just

    ALTER DOMAIN d1 ADD [ CONSTRAINT conname ] NOT NULL

(None of these syntaxes are per SQL standard; we are just living with
the bits of inconsistency that have built up over time.)

In passing, this also changes the psql \dD output to not show not-null
constraints in the column "Check", since it's already shown in the
column "Nullable".  This has also been off since e5da0fe3c22.

Reviewed-by: jian he <[email protected]>
Discussion: https://www.postgresql.org/message-id/flat/9ec24d7b-633d-463a-84c6-7acff769c9e8%40eisentraut.org

doc/src/sgml/ref/create_domain.sgml
src/backend/parser/gram.y
src/backend/utils/adt/ruleutils.c
src/bin/psql/describe.c
src/test/regress/expected/domain.out
src/test/regress/sql/domain.sql

index 73f9f28d6cfae4a50230624f8fe481b8e8026cf7..ce55520348620b0cb2292e785873a4168f627c30 100644 (file)
@@ -24,9 +24,9 @@ PostgreSQL documentation
 CREATE DOMAIN <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable>
     [ COLLATE <replaceable>collation</replaceable> ]
     [ DEFAULT <replaceable>expression</replaceable> ]
-    [ <replaceable class="parameter">constraint</replaceable> [ ... ] ]
+    [ <replaceable class="parameter">domain_constraint</replaceable> [ ... ] ]
 
-<phrase>where <replaceable class="parameter">constraint</replaceable> is:</phrase>
+<phrase>where <replaceable class="parameter">domain_constraint</replaceable> is:</phrase>
 
 [ CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ]
 { NOT NULL | NULL | CHECK (<replaceable class="parameter">expression</replaceable>) }
@@ -190,7 +190,7 @@ CREATE DOMAIN <replaceable class="parameter">name</replaceable> [ AS ] <replacea
   </variablelist>
  </refsect1>
 
- <refsect1>
+ <refsect1 id="sql-createdomain-notes">
   <title>Notes</title>
 
   <para>
@@ -279,6 +279,17 @@ CREATE TABLE us_snail_addy (
    The command <command>CREATE DOMAIN</command> conforms to the SQL
    standard.
   </para>
+
+  <para>
+   The syntax <literal>NOT NULL</literal> in this command is a
+   <productname>PostgreSQL</productname> extension.  (A standard-conforming
+   way to write the same would be <literal>CHECK (VALUE IS NOT
+   NULL)</literal>.  However, per <xref linkend="sql-createdomain-notes"/>,
+   such constraints are best avoided in practice anyway.)  The
+   <literal>NULL</literal> <quote>constraint</quote> is a
+   <productname>PostgreSQL</productname> extension (see also <xref
+   linkend="sql-createtable-compatibility"/>).
+  </para>
  </refsect1>
 
  <refsect1 id="sql-createdomain-see-also">
index 0523f7e891e38f298625e8dcbde7965b799f665c..e8b619926ef14174c70b72f19f89822d6f70eb4e 100644 (file)
@@ -524,7 +524,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <vsetstmt> generic_set set_rest set_rest_more generic_reset reset_rest
                 SetResetClause FunctionSetResetClause
 
-%type <node>   TableElement TypedTableElement ConstraintElem TableFuncElement
+%type <node>   TableElement TypedTableElement ConstraintElem DomainConstraintElem TableFuncElement
 %type <node>   columnDef columnOptions optionalPeriodName
 %type <defelt> def_elem reloption_elem old_aggr_elem operator_def_elem
 %type <node>   def_arg columnElem where_clause where_or_current_clause
@@ -596,7 +596,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <keyword> col_name_keyword reserved_keyword
 %type <keyword> bare_label_keyword
 
-%type <node>   TableConstraint TableLikeClause
+%type <node>   DomainConstraint TableConstraint TableLikeClause
 %type <ival>   TableLikeOptionList TableLikeOption
 %type <str>        column_compression opt_column_compression column_storage opt_column_storage
 %type <list>   ColQualList
@@ -4334,6 +4334,60 @@ ConstraintElem:
                }
        ;
 
+/*
+ * DomainConstraint is separate from TableConstraint because the syntax for
+ * NOT NULL constraints is different.  For table constraints, we need to
+ * accept a column name, but for domain constraints, we don't.  (We could
+ * accept something like NOT NULL VALUE, but that seems weird.)  CREATE DOMAIN
+ * (which uses ColQualList) has for a long time accepted NOT NULL without a
+ * column name, so it makes sense that ALTER DOMAIN (which uses
+ * DomainConstraint) does as well.  None of these syntaxes are per SQL
+ * standard; we are just living with the bits of inconsistency that have built
+ * up over time.
+ */
+DomainConstraint:
+           CONSTRAINT name DomainConstraintElem
+               {
+                   Constraint *n = castNode(Constraint, $3);
+
+                   n->conname = $2;
+                   n->location = @1;
+                   $$ = (Node *) n;
+               }
+           | DomainConstraintElem                  { $$ = $1; }
+       ;
+
+DomainConstraintElem:
+           CHECK '(' a_expr ')' ConstraintAttributeSpec
+               {
+                   Constraint *n = makeNode(Constraint);
+
+                   n->contype = CONSTR_CHECK;
+                   n->location = @1;
+                   n->raw_expr = $3;
+                   n->cooked_expr = NULL;
+                   processCASbits($5, @5, "CHECK",
+                                  NULL, NULL, &n->skip_validation,
+                                  &n->is_no_inherit, yyscanner);
+                   n->initially_valid = !n->skip_validation;
+                   $$ = (Node *) n;
+               }
+           | NOT NULL_P ConstraintAttributeSpec
+               {
+                   Constraint *n = makeNode(Constraint);
+
+                   n->contype = CONSTR_NOTNULL;
+                   n->location = @1;
+                   n->keys = list_make1(makeString("value"));
+                   /* no NOT VALID support yet */
+                   processCASbits($3, @3, "NOT NULL",
+                                  NULL, NULL, NULL,
+                                  &n->is_no_inherit, yyscanner);
+                   n->initially_valid = true;
+                   $$ = (Node *) n;
+               }
+       ;
+
 opt_no_inherit:    NO INHERIT                          {  $$ = true; }
            | /* EMPTY */                           {  $$ = false; }
        ;
@@ -11586,7 +11640,7 @@ AlterDomainStmt:
                    $$ = (Node *) n;
                }
            /* ALTER DOMAIN <domain> ADD CONSTRAINT ... */
-           | ALTER DOMAIN_P any_name ADD_P TableConstraint
+           | ALTER DOMAIN_P any_name ADD_P DomainConstraint
                {
                    AlterDomainStmt *n = makeNode(AlterDomainStmt);
 
index 24e3514b0053e54160e096290552746dfb9e13ad..b3428e2ae49372f3a45f63e5d02287b4ab055b1c 100644 (file)
@@ -2523,7 +2523,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
                else if (conForm->contypid)
                {
                    /* conkey is null for domain not-null constraints */
-                   appendStringInfoString(&buf, "NOT NULL VALUE");
+                   appendStringInfoString(&buf, "NOT NULL");
                }
                break;
            }
index 6433497bcd20893baad82f3a0c55c3fb8570f5a7..1f0a056d90b661fec7131dcaf807fe23a63fcfe9 100644 (file)
@@ -4449,7 +4449,7 @@ listDomains(const char *pattern, bool verbose, bool showSystem)
                      "       CASE WHEN t.typnotnull THEN 'not null' END as \"%s\",\n"
                      "       t.typdefault as \"%s\",\n"
                      "       pg_catalog.array_to_string(ARRAY(\n"
-                     "         SELECT pg_catalog.pg_get_constraintdef(r.oid, true) FROM pg_catalog.pg_constraint r WHERE t.oid = r.contypid\n"
+                     "         SELECT pg_catalog.pg_get_constraintdef(r.oid, true) FROM pg_catalog.pg_constraint r WHERE t.oid = r.contypid AND r.contype = 'c'\n"
                      "       ), ' ') as \"%s\"",
                      gettext_noop("Schema"),
                      gettext_noop("Name"),
index fa8459e10ff8b7509a633bec15c09bf00673cc67..db0b8a180a3ecfa1f99109960293afa104ace678 100644 (file)
@@ -785,6 +785,13 @@ alter domain con add constraint t check (VALUE < 1); -- fails
 ERROR:  column "col1" of table "domcontest" contains values that violate the new constraint
 alter domain con add constraint t check (VALUE < 34);
 alter domain con add check (VALUE > 0);
+\dD con
+                                         List of domains
+ Schema | Name |  Type   | Collation | Nullable | Default |                Check                 
+--------+------+---------+-----------+----------+---------+--------------------------------------
+ public | con  | integer |           |          |         | CHECK (VALUE < 34) CHECK (VALUE > 0)
+(1 row)
+
 insert into domcontest values (-5); -- fails
 ERROR:  value for domain con violates check constraint "con_check"
 insert into domcontest values (42); -- fails
@@ -805,26 +812,33 @@ create table domconnotnulltest
 , col2 connotnull
 );
 insert into domconnotnulltest default values;
-alter domain connotnull add not null value; -- fails
+alter domain connotnull add not null; -- fails
 ERROR:  column "col1" of table "domconnotnulltest" contains null values
 update domconnotnulltest set col1 = 5;
-alter domain connotnull add not null value; -- fails
+alter domain connotnull add not null; -- fails
 ERROR:  column "col2" of table "domconnotnulltest" contains null values
 update domconnotnulltest set col2 = 6;
-alter domain connotnull add constraint constr1 not null value;
+alter domain connotnull add constraint constr1 not null;
 select count(*) from pg_constraint where contypid = 'connotnull'::regtype and contype = 'n';
  count 
 -------
      1
 (1 row)
 
-alter domain connotnull add constraint constr1bis not null value;  -- redundant
+alter domain connotnull add constraint constr1bis not null;  -- redundant
 select count(*) from pg_constraint where contypid = 'connotnull'::regtype and contype = 'n';
  count 
 -------
      1
 (1 row)
 
+\dD connotnull
+                            List of domains
+ Schema |    Name    |  Type   | Collation | Nullable | Default | Check 
+--------+------------+---------+-----------+----------+---------+-------
+ public | connotnull | integer |           | not null |         | 
+(1 row)
+
 update domconnotnulltest set col1 = null; -- fails
 ERROR:  domain connotnull does not allow null values
 alter domain connotnull drop constraint constr1;
index 763c68f1db64119237f8399c0ef530eff66e1bff..b5a70ee8be701efbfa3f23bdc414cccff280796d 100644 (file)
@@ -458,6 +458,8 @@ alter domain con add constraint t check (VALUE < 1); -- fails
 alter domain con add constraint t check (VALUE < 34);
 alter domain con add check (VALUE > 0);
 
+\dD con
+
 insert into domcontest values (-5); -- fails
 insert into domcontest values (42); -- fails
 insert into domcontest values (5);
@@ -477,18 +479,20 @@ create table domconnotnulltest
 );
 
 insert into domconnotnulltest default values;
-alter domain connotnull add not null value; -- fails
+alter domain connotnull add not null; -- fails
 
 update domconnotnulltest set col1 = 5;
-alter domain connotnull add not null value; -- fails
+alter domain connotnull add not null; -- fails
 
 update domconnotnulltest set col2 = 6;
 
-alter domain connotnull add constraint constr1 not null value;
+alter domain connotnull add constraint constr1 not null;
 select count(*) from pg_constraint where contypid = 'connotnull'::regtype and contype = 'n';
-alter domain connotnull add constraint constr1bis not null value;  -- redundant
+alter domain connotnull add constraint constr1bis not null;  -- redundant
 select count(*) from pg_constraint where contypid = 'connotnull'::regtype and contype = 'n';
 
+\dD connotnull
+
 update domconnotnulltest set col1 = null; -- fails
 
 alter domain connotnull drop constraint constr1;