Remove the hack in the grammar that "optimized away" DEFAULT NULL clauses.
authorTom Lane
Mon, 29 Oct 2007 19:40:40 +0000 (19:40 +0000)
committerTom Lane
Mon, 29 Oct 2007 19:40:40 +0000 (19:40 +0000)
Instead put in a test to drop a NULL default at the last moment before
storing the catalog entry.  This changes the behavior in a couple of ways:
* Specifying DEFAULT NULL when creating an inheritance child table will
  successfully suppress inheritance of any default expression from the
  parent's column, where formerly it failed to do so.
* Specifying DEFAULT NULL for a column of a domain type will correctly
  override any default belonging to the domain; likewise for a sub-domain.
The latter change happens because by the time the clause is checked,
it won't be a simple null Const but a CoerceToDomain expression.

Personally I think this should be back-patched, but there doesn't seem to
be consensus for that on pgsql-hackers, so refraining.

src/backend/catalog/heap.c
src/backend/commands/typecmds.c
src/backend/parser/gram.y
src/backend/parser/parse_expr.c
src/backend/parser/parse_utilcmd.c
src/include/parser/gramparse.h
src/test/regress/expected/domain.out
src/test/regress/sql/domain.sql

index ea985a635ddcca93f00231fe3e4b47e50c253380..d22bb77a50c60389144b61f40961a28649ab37f0 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.324 2007/10/12 18:55:11 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.325 2007/10/29 19:40:39 tgl Exp $
  *
  *
  * INTERFACE ROUTINES
@@ -1722,6 +1722,21 @@ AddRelationRawConstraints(Relation rel,
                           atp->atttypid, atp->atttypmod,
                           NameStr(atp->attname));
 
+       /*
+        * If the expression is just a NULL constant, we do not bother
+        * to make an explicit pg_attrdef entry, since the default behavior
+        * is equivalent.
+        *
+        * Note a nonobvious property of this test: if the column is of a
+        * domain type, what we'll get is not a bare null Const but a
+        * CoerceToDomain expr, so we will not discard the default.  This is
+        * critical because the column default needs to be retained to
+        * override any default that the domain might have.
+        */
+       if (expr == NULL ||
+           (IsA(expr, Const) && ((Const *) expr)->constisnull))
+           continue;
+
        StoreAttrDefault(rel, colDef->attnum, nodeToString(expr));
 
        cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
index 75a8b7530e8d687070e754f96e3c9e79a2bad1b3..1f58d989f266221a3090ca80bf29b692e43c6fc0 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.108 2007/09/29 17:18:58 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.109 2007/10/29 19:40:39 tgl Exp $
  *
  * DESCRIPTION
  *   The "DefineFoo" routines take the parse tree and pick out the
@@ -765,20 +765,40 @@ DefineDomain(CreateDomainStmt *stmt)
                                              domainName);
 
                    /*
-                    * Expression must be stored as a nodeToString result, but
-                    * we also require a valid textual representation (mainly
-                    * to make life easier for pg_dump).
+                    * If the expression is just a NULL constant, we treat
+                    * it like not having a default.
+                    *
+                    * Note that if the basetype is another domain, we'll see
+                    * a CoerceToDomain expr here and not discard the default.
+                    * This is critical because the domain default needs to be
+                    * retained to override any default that the base domain
+                    * might have.
                     */
-                   defaultValue =
-                       deparse_expression(defaultExpr,
-                                          deparse_context_for(domainName,
-                                                              InvalidOid),
-                                          false, false);
-                   defaultValueBin = nodeToString(defaultExpr);
+                   if (defaultExpr == NULL ||
+                       (IsA(defaultExpr, Const) &&
+                        ((Const *) defaultExpr)->constisnull))
+                   {
+                       defaultValue = NULL;
+                       defaultValueBin = NULL;
+                   }
+                   else
+                   {
+                       /*
+                        * Expression must be stored as a nodeToString result,
+                        * but we also require a valid textual representation
+                        * (mainly to make life easier for pg_dump).
+                        */
+                       defaultValue =
+                           deparse_expression(defaultExpr,
+                                              deparse_context_for(domainName,
+                                                                  InvalidOid),
+                                              false, false);
+                       defaultValueBin = nodeToString(defaultExpr);
+                   }
                }
                else
                {
-                   /* DEFAULT NULL is same as not having a default */
+                   /* No default (can this still happen?) */
                    defaultValue = NULL;
                    defaultValueBin = NULL;
                }
@@ -1443,7 +1463,7 @@ AlterDomainDefault(List *names, Node *defaultRaw)
    MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
    MemSet(new_record_repl, ' ', sizeof(new_record_repl));
 
-   /* Store the new default, if null then skip this step */
+   /* Store the new default into the tuple */
    if (defaultRaw)
    {
        /* Create a dummy ParseState for transformExpr */
@@ -1459,30 +1479,46 @@ AlterDomainDefault(List *names, Node *defaultRaw)
                                  NameStr(typTup->typname));
 
        /*
-        * Expression must be stored as a nodeToString result, but we also
-        * require a valid textual representation (mainly to make life easier
-        * for pg_dump).
+        * If the expression is just a NULL constant, we treat the command
+        * like ALTER ... DROP DEFAULT.  (But see note for same test in
+        * DefineDomain.)
         */
-       defaultValue = deparse_expression(defaultExpr,
+       if (defaultExpr == NULL ||
+           (IsA(defaultExpr, Const) && ((Const *) defaultExpr)->constisnull))
+       {
+           /* Default is NULL, drop it */
+           new_record_nulls[Anum_pg_type_typdefaultbin - 1] = 'n';
+           new_record_repl[Anum_pg_type_typdefaultbin - 1] = 'r';
+           new_record_nulls[Anum_pg_type_typdefault - 1] = 'n';
+           new_record_repl[Anum_pg_type_typdefault - 1] = 'r';
+       }
+       else
+       {
+           /*
+            * Expression must be stored as a nodeToString result, but we also
+            * require a valid textual representation (mainly to make life
+            * easier for pg_dump).
+            */
+           defaultValue = deparse_expression(defaultExpr,
                                deparse_context_for(NameStr(typTup->typname),
                                                    InvalidOid),
                                          false, false);
 
-       /*
-        * Form an updated tuple with the new default and write it back.
-        */
-       new_record[Anum_pg_type_typdefaultbin - 1] = DirectFunctionCall1(textin,
-                                                            CStringGetDatum(
-                                                nodeToString(defaultExpr)));
+           /*
+            * Form an updated tuple with the new default and write it back.
+            */
+           new_record[Anum_pg_type_typdefaultbin - 1] = DirectFunctionCall1(textin,
+                               CStringGetDatum(nodeToString(defaultExpr)));
 
-       new_record_repl[Anum_pg_type_typdefaultbin - 1] = 'r';
-       new_record[Anum_pg_type_typdefault - 1] = DirectFunctionCall1(textin,
+           new_record_repl[Anum_pg_type_typdefaultbin - 1] = 'r';
+           new_record[Anum_pg_type_typdefault - 1] = DirectFunctionCall1(textin,
                                              CStringGetDatum(defaultValue));
-       new_record_repl[Anum_pg_type_typdefault - 1] = 'r';
+           new_record_repl[Anum_pg_type_typdefault - 1] = 'r';
+       }
    }
    else
-       /* Default is NULL, drop it */
    {
+       /* ALTER ... DROP DEFAULT */
        new_record_nulls[Anum_pg_type_typdefaultbin - 1] = 'n';
        new_record_repl[Anum_pg_type_typdefaultbin - 1] = 'r';
        new_record_nulls[Anum_pg_type_typdefault - 1] = 'n';
index 1452fe7ff2b3859bc2622b17148ec7f0431a2374..f9f7a49a312bf99193f90cfa89a2c0ba293a44bb 100644 (file)
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.603 2007/09/24 01:29:28 adunstan Exp $
+ *   $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.604 2007/10/29 19:40:39 tgl Exp $
  *
  * HISTORY
  *   AUTHOR            DATE            MAJOR EVENT
@@ -1685,14 +1685,7 @@ alter_rel_cmd:
        ;
 
 alter_column_default:
-           SET DEFAULT a_expr
-               {
-                   /* Treat SET DEFAULT NULL the same as DROP DEFAULT */
-                   if (exprIsNullConstant($3))
-                       $$ = NULL;
-                   else
-                       $$ = $3;
-               }
+           SET DEFAULT a_expr          { $$ = $3; }
            | DROP DEFAULT              { $$ = NULL; }
        ;
 
@@ -2080,15 +2073,7 @@ ColConstraintElem:
                    Constraint *n = makeNode(Constraint);
                    n->contype = CONSTR_DEFAULT;
                    n->name = NULL;
-                   if (exprIsNullConstant($2))
-                   {
-                       /* DEFAULT NULL should be reported as empty expr */
-                       n->raw_expr = NULL;
-                   }
-                   else
-                   {
-                       n->raw_expr = $2;
-                   }
+                   n->raw_expr = $2;
                    n->cooked_expr = NULL;
                    n->keys = NULL;
                    n->indexspace = NULL;
@@ -9763,23 +9748,6 @@ parser_init(void)
    QueryIsRule = FALSE;
 }
 
-/* exprIsNullConstant()
- * Test whether an a_expr is a plain NULL constant or not.
- */
-bool
-exprIsNullConstant(Node *arg)
-{
-   if (arg && IsA(arg, A_Const))
-   {
-       A_Const *con = (A_Const *) arg;
-
-       if (con->val.type == T_Null &&
-           con->typename == NULL)
-           return TRUE;
-   }
-   return FALSE;
-}
-
 /* doNegate()
  * Handle negation of a numeric constant.
  *
index 754e18d687cc4ec78588d7d2c960a9cddd079f7d..86fddc4a7a64ee1922738e16746395248ee021f4 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.221 2007/06/23 22:12:51 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.222 2007/10/29 19:40:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -606,6 +606,21 @@ transformParamRef(ParseState *pstate, ParamRef *pref)
    return (Node *) param;
 }
 
+/* Test whether an a_expr is a plain NULL constant or not */
+static bool
+exprIsNullConstant(Node *arg)
+{
+   if (arg && IsA(arg, A_Const))
+   {
+       A_Const *con = (A_Const *) arg;
+
+       if (con->val.type == T_Null &&
+           con->typename == NULL)
+           return true;
+   }
+   return false;
+}
+
 static Node *
 transformAExprOp(ParseState *pstate, A_Expr *a)
 {
index 54e7dfdb161c27138489cabfb02bace8b2300f9c..287e82ddeec10baecdb72da4e74f8e0b3570ec8e 100644 (file)
@@ -19,7 +19,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/backend/parser/parse_utilcmd.c,v 2.3 2007/08/27 03:36:08 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/parse_utilcmd.c,v 2.4 2007/10/29 19:40:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -440,7 +440,6 @@ transformColumnDefinition(ParseState *pstate, CreateStmtContext *cxt,
                            (errcode(ERRCODE_SYNTAX_ERROR),
                             errmsg("multiple default values specified for column \"%s\" of table \"%s\"",
                                  column->colname, cxt->relation->relname)));
-               /* Note: DEFAULT NULL maps to constraint->raw_expr == NULL */
                column->raw_default = constraint->raw_expr;
                Assert(constraint->cooked_expr == NULL);
                saw_default = true;
index ee6be87c02f30c0bdbca8846c25d8084ae83ac29..34f47a9bea683386e465696dd2a6606c28bdfbf5 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/parser/gramparse.h,v 1.38 2007/01/05 22:19:56 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/parser/gramparse.h,v 1.39 2007/10/29 19:40:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -54,6 +54,5 @@ extern void parser_init(void);
 extern int base_yyparse(void);
 extern List *SystemFuncName(char *name);
 extern TypeName *SystemTypeName(char *name);
-extern bool exprIsNullConstant(Node *arg);
 
 #endif   /* GRAMPARSE_H */
index c7d5b50334537ccd1db499bb75b4d3b0eb1a1e6d..b951ce8caa8752e36a0e52a38437ad590267aaa1 100644 (file)
@@ -205,7 +205,15 @@ create table defaulttest
             , col8 ddef5
             );
 NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "defaulttest_pkey" for table "defaulttest"
-insert into defaulttest default values;
+insert into defaulttest(col4) values(0); -- fails, col5 defaults to null
+ERROR:  null value in column "col5" violates not-null constraint
+alter table defaulttest alter column col5 drop default;
+insert into defaulttest default values; -- succeeds, inserts domain default
+-- We used to treat SET DEFAULT NULL as equivalent to DROP DEFAULT; wrong
+alter table defaulttest alter column col5 set default null;
+insert into defaulttest(col4) values(0); -- fails
+ERROR:  null value in column "col5" violates not-null constraint
+alter table defaulttest alter column col5 drop default;
 insert into defaulttest default values;
 insert into defaulttest default values;
 -- Test defaults with copy
index 7da99719911348c134f4fe58c73e711d0f896da6..7a4ec383d20cac750f67d0dc54c3eb22cb7ae782 100644 (file)
@@ -168,7 +168,13 @@ create table defaulttest
             , col7 ddef4 DEFAULT 8000
             , col8 ddef5
             );
-insert into defaulttest default values;
+insert into defaulttest(col4) values(0); -- fails, col5 defaults to null
+alter table defaulttest alter column col5 drop default;
+insert into defaulttest default values; -- succeeds, inserts domain default
+-- We used to treat SET DEFAULT NULL as equivalent to DROP DEFAULT; wrong
+alter table defaulttest alter column col5 set default null;
+insert into defaulttest(col4) values(0); -- fails
+alter table defaulttest alter column col5 drop default;
 insert into defaulttest default values;
 insert into defaulttest default values;