Add IS UNKNOWN, IS NOT UNKNOWN boolean tests, fix the existing boolean
authorTom Lane
Tue, 19 Jun 2001 22:39:12 +0000 (22:39 +0000)
committerTom Lane
Tue, 19 Jun 2001 22:39:12 +0000 (22:39 +0000)
tests to return the correct results per SQL9x when given NULL inputs.
Reimplement these tests as well as IS [NOT] NULL to have their own
expression node types, instead of depending on special functions.
From Joe Conway, with a little help from Tom Lane.

18 files changed:
doc/src/sgml/func.sgml
doc/src/sgml/syntax.sgml
src/backend/executor/execQual.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/readfuncs.c
src/backend/optimizer/util/clauses.c
src/backend/parser/gram.y
src/backend/parser/keywords.c
src/backend/parser/parse_clause.c
src/backend/parser/parse_coerce.c
src/backend/parser/parse_expr.c
src/backend/utils/adt/ruleutils.c
src/include/catalog/catversion.h
src/include/nodes/nodes.h
src/include/nodes/parsenodes.h
src/include/parser/parse_coerce.h

index dc646320cb95f2b4735b801e99811e4bbceba42e..00d476edb6f1ef8720fa79739a94c87ae252ec4a 100644 (file)
@@ -1,4 +1,4 @@
-
+
 
 
  Functions and Operators
    Microsoft Access) to work, but this may
    be discontinued in a future release.
   
+
+  
+   Boolean values can be tested using the constructs
+
+expression IS TRUE
+expression IS NOT TRUE
+expression IS FALSE
+expression IS NOT FALSE
+expression IS UNKNOWN
+expression IS NOT UNKNOWN
+
+   These are similar to IS NULL in that they will
+   always return TRUE or FALSE, never NULL, even when the operand is NULL.
+   A NULL input is treated as the logical value UNKNOWN.
+  
  
 
 
index 9234e3c26d4e586c1f2d4cd2e8259b331cfaaa27..300851235cac27c67827b044560b2a61155c5455 100644 (file)
@@ -1,5 +1,5 @@
 
 
 
@@ -1060,7 +1060,7 @@ SELECT (5 !) - 6;
       
        IS
        
-       test for TRUE, FALSE, NULL
+       test for TRUE, FALSE, UNKNOWN, NULL
       
 
       
index 84aa271629bd39b533f6575831d2c7b78ce323f1..fb950fdfd14b63b3c8f67abd0643877764ed3a9f 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.86 2001/04/19 04:29:02 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.87 2001/06/19 22:39:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -62,6 +62,10 @@ static Datum ExecEvalAnd(Expr *andExpr, ExprContext *econtext, bool *isNull);
 static Datum ExecEvalOr(Expr *orExpr, ExprContext *econtext, bool *isNull);
 static Datum ExecEvalCase(CaseExpr *caseExpr, ExprContext *econtext,
             bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalNullTest(NullTest *ntest, ExprContext *econtext,
+            bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalBooleanTest(BooleanTest *btest, ExprContext *econtext,
+            bool *isNull, ExprDoneCond *isDone);
 
 
 /*----------
@@ -1091,6 +1095,126 @@ ExecEvalCase(CaseExpr *caseExpr, ExprContext *econtext,
    return (Datum) 0;
 }
 
+/* ----------------------------------------------------------------
+ *     ExecEvalNullTest
+ *
+ *     Evaluate a NullTest node.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalNullTest(NullTest *ntest,
+                ExprContext *econtext,
+                bool *isNull,
+                ExprDoneCond *isDone)
+{
+   Datum       result;
+
+   result = ExecEvalExpr(ntest->arg, econtext, isNull, isDone);
+   switch (ntest->nulltesttype)
+    {
+        case IS_NULL:
+            if (*isNull)
+            {
+                *isNull = false;
+                return BoolGetDatum(true);
+            }
+            else
+                return BoolGetDatum(false);
+        case IS_NOT_NULL:
+            if (*isNull)
+            {
+                *isNull = false;
+                return BoolGetDatum(false);
+            }
+            else
+                return BoolGetDatum(true);
+        default:
+            elog(ERROR, "ExecEvalNullTest: unexpected nulltesttype %d",
+                 (int) ntest->nulltesttype);
+            return (Datum) 0;  /* keep compiler quiet */
+    }
+}
+
+/* ----------------------------------------------------------------
+ *     ExecEvalBooleanTest
+ *
+ *     Evaluate a BooleanTest node.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalBooleanTest(BooleanTest *btest,
+                   ExprContext *econtext,
+                   bool *isNull,
+                   ExprDoneCond *isDone)
+{
+   Datum       result;
+
+   result = ExecEvalExpr(btest->arg, econtext, isNull, isDone);
+   switch (btest->booltesttype)
+    {
+        case IS_TRUE:
+            if (*isNull)
+            {
+                *isNull = false;
+                return BoolGetDatum(false);
+            }
+            else if (DatumGetBool(result))
+                return BoolGetDatum(true);
+           else
+                return BoolGetDatum(false);
+        case IS_NOT_TRUE:
+            if (*isNull)
+            {
+                *isNull = false;
+                return BoolGetDatum(true);
+            }
+            else if (DatumGetBool(result))
+                return BoolGetDatum(false);
+           else
+                return BoolGetDatum(true);
+        case IS_FALSE:
+            if (*isNull)
+            {
+                *isNull = false;
+                return BoolGetDatum(false);
+            }
+            else if (DatumGetBool(result))
+                return BoolGetDatum(false);
+           else
+                return BoolGetDatum(true);
+        case IS_NOT_FALSE:
+            if (*isNull)
+            {
+                *isNull = false;
+                return BoolGetDatum(true);
+            }
+            else if (DatumGetBool(result))
+                return BoolGetDatum(true);
+           else
+                return BoolGetDatum(false);
+        case IS_UNKNOWN:
+            if (*isNull)
+            {
+                *isNull = false;
+                return BoolGetDatum(true);
+            }
+           else
+                return BoolGetDatum(false);
+        case IS_NOT_UNKNOWN:
+            if (*isNull)
+            {
+                *isNull = false;
+                return BoolGetDatum(false);
+            }
+           else
+                return BoolGetDatum(true);
+        default:
+            elog(ERROR, "ExecEvalBooleanTest: unexpected booltesttype %d",
+                 (int) btest->booltesttype);
+            return (Datum) 0;  /* keep compiler quiet */
+    }
+}
+
 /* ----------------------------------------------------------------
  *     ExecEvalFieldSelect
  *
@@ -1266,6 +1390,18 @@ ExecEvalExpr(Node *expression,
                                    isNull,
                                    isDone);
            break;
+       case T_NullTest:
+           retDatum = ExecEvalNullTest((NullTest *) expression,
+                                   econtext,
+                                   isNull,
+                                   isDone);
+           break;
+       case T_BooleanTest:
+           retDatum = ExecEvalBooleanTest((BooleanTest *) expression,
+                                   econtext,
+                                   isNull,
+                                   isDone);
+           break;
 
        default:
            elog(ERROR, "ExecEvalExpr: unknown expression type %d",
index 77ae4fb781adf31c9b553970a7ae008f7285f772..6cf5b35d26664fddc6a9551986740ed4d39dcfb0 100644 (file)
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.144 2001/06/09 23:21:54 petere Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.145 2001/06/19 22:39:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1017,6 +1017,42 @@ _copyCaseWhen(CaseWhen *from)
    return newnode;
 }
 
+/* ----------------
+ *     _copyNullTest
+ * ----------------
+ */
+static NullTest *
+_copyNullTest(NullTest *from)
+{
+   NullTest *newnode = makeNode(NullTest);
+
+   /*
+    * copy remainder of node
+    */
+   Node_Copy(from, newnode, arg);
+   newnode->nulltesttype = from->nulltesttype;
+
+   return newnode;
+}
+
+/* ----------------
+ *     _copyBooleanTest
+ * ----------------
+ */
+static BooleanTest *
+_copyBooleanTest(BooleanTest *from)
+{
+   BooleanTest *newnode = makeNode(BooleanTest);
+
+   /*
+    * copy remainder of node
+    */
+   Node_Copy(from, newnode, arg);
+   newnode->booltesttype = from->booltesttype;
+
+   return newnode;
+}
+
 static ArrayRef *
 _copyArrayRef(ArrayRef *from)
 {
@@ -2954,6 +2990,12 @@ copyObject(void *from)
        case T_CaseWhen:
            retval = _copyCaseWhen(from);
            break;
+       case T_NullTest:
+           retval = _copyNullTest(from);
+           break;
+       case T_BooleanTest:
+           retval = _copyBooleanTest(from);
+           break;
        case T_FkConstraint:
            retval = _copyFkConstraint(from);
            break;
index f7bfcc1977656c65e1c1ee79a8b4e500367abd10..b12b4c29127e664840f6d1e57a87a71de71d1e4d 100644 (file)
@@ -20,7 +20,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.92 2001/06/09 23:21:54 petere Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.93 2001/06/19 22:39:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1712,6 +1712,26 @@ _equalCaseWhen(CaseWhen *a, CaseWhen *b)
    return true;
 }
 
+static bool
+_equalNullTest(NullTest *a, NullTest *b)
+{
+   if (!equal(a->arg, b->arg))
+       return false;
+   if (a->nulltesttype != b->nulltesttype)
+       return false;
+   return true;
+}
+
+static bool
+_equalBooleanTest(BooleanTest *a, BooleanTest *b)
+{
+   if (!equal(a->arg, b->arg))
+       return false;
+   if (a->booltesttype != b->booltesttype)
+       return false;
+   return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -2120,6 +2140,12 @@ equal(void *a, void *b)
        case T_CaseWhen:
            retval = _equalCaseWhen(a, b);
            break;
+       case T_NullTest:
+           retval = _equalNullTest(a, b);
+           break;
+       case T_BooleanTest:
+           retval = _equalBooleanTest(a, b);
+           break;
        case T_FkConstraint:
            retval = _equalFkConstraint(a, b);
            break;
index ebcacd49750cc9a85246a087050dac32e61d2f0f..e555e9591ec807ffc953c82190095539b8d8b13d 100644 (file)
@@ -5,7 +5,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.141 2001/05/20 20:28:18 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.142 2001/06/19 22:39:11 tgl Exp $
  *
  * NOTES
  *   Every (plan) node in POSTGRES has an associated "out" routine which
@@ -1259,12 +1259,6 @@ _outAExpr(StringInfo str, A_Expr *node)
        case NOT:
            appendStringInfo(str, "NOT ");
            break;
-       case ISNULL:
-           appendStringInfo(str, "ISNULL ");
-           break;
-       case NOTNULL:
-           appendStringInfo(str, "NOTNULL ");
-           break;
        case OP:
            _outToken(str, node->opname);
            appendStringInfo(str, " ");
@@ -1402,6 +1396,32 @@ _outCaseWhen(StringInfo str, CaseWhen *node)
    _outNode(str, node->result);
 }
 
+/*
+ * NullTest
+ */
+static void
+_outNullTest(StringInfo str, NullTest *node)
+{
+   appendStringInfo(str, " NULLTEST :arg ");
+   _outNode(str, node->arg);
+
+   appendStringInfo(str, " :nulltesttype %d ",
+                    (int) node->nulltesttype);
+}
+
+/*
+ * BooleanTest
+ */
+static void
+_outBooleanTest(StringInfo str, BooleanTest *node)
+{
+   appendStringInfo(str, " BOOLEANTEST :arg ");
+   _outNode(str, node->arg);
+
+   appendStringInfo(str, " :booltesttype %d ",
+                    (int) node->booltesttype);
+}
+
 /*
  * _outNode -
  *   converts a Node into ascii string and append it to 'str'
@@ -1639,7 +1659,12 @@ _outNode(StringInfo str, void *obj)
            case T_CaseWhen:
                _outCaseWhen(str, obj);
                break;
-
+           case T_NullTest:
+               _outNullTest(str, obj);
+               break;
+           case T_BooleanTest:
+               _outBooleanTest(str, obj);
+               break;
            case T_VariableSetStmt:
                break;
            case T_SelectStmt:
index a83f0b64dbfa58d192cd1f0d757b90861a6ce90f..2f0dec048b3af39ca0cd3658545251a9afc619e6 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.110 2001/06/05 05:26:04 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.111 2001/06/19 22:39:11 tgl Exp $
  *
  * NOTES
  *   Most of the read functions for plan nodes are tested. (In fact, they
@@ -859,6 +859,56 @@ _readCaseWhen(void)
    return local_node;
 }
 
+/* ----------------
+ *     _readNullTest
+ *
+ * NullTest is a subclass of Node
+ * ----------------
+ */
+static NullTest *
+_readNullTest(void)
+{
+   NullTest    *local_node;
+   char        *token;
+   int         length;
+
+   local_node = makeNode(NullTest);
+
+   token = pg_strtok(&length); /* eat :arg */
+   local_node->arg = nodeRead(true);   /* now read it */
+
+   token = pg_strtok(&length); /* eat :nulltesttype */
+   token = pg_strtok(&length); /* get nulltesttype */
+   local_node->nulltesttype = (NullTestType) atoi(token);
+
+   return local_node;
+}
+
+/* ----------------
+ *     _readBooleanTest
+ *
+ * BooleanTest is a subclass of Node
+ * ----------------
+ */
+static BooleanTest *
+_readBooleanTest(void)
+{
+   BooleanTest *local_node;
+   char        *token;
+   int         length;
+
+   local_node = makeNode(BooleanTest);
+
+   token = pg_strtok(&length); /* eat :arg */
+   local_node->arg = nodeRead(true);   /* now read it */
+
+   token = pg_strtok(&length); /* eat :booltesttype */
+   token = pg_strtok(&length); /* get booltesttype */
+   local_node->booltesttype = (BoolTestType) atoi(token);
+
+   return local_node;
+}
+
 /* ----------------
  *     _readVar
  *
@@ -1966,6 +2016,10 @@ parsePlanString(void)
        return_value = _readCaseExpr();
    else if (length == 4 && strncmp(token, "WHEN", length) == 0)
        return_value = _readCaseWhen();
+   else if (length == 8 && strncmp(token, "NULLTEST", length) == 0)
+       return_value = _readNullTest();
+   else if (length == 11 && strncmp(token, "BOOLEANTEST", length) == 0)
+       return_value = _readBooleanTest();
    else
        elog(ERROR, "badly formatted planstring \"%.10s\"...", token);
 
index e0cc97e3a1dc3314ef104e2bc34b9bac9aadc97d..6ee962fd75c1f01b322775f853a69a34b1f26f93 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.85 2001/05/20 20:28:19 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.86 2001/06/19 22:39:11 tgl Exp $
  *
  * HISTORY
  *   AUTHOR            DATE            MAJOR EVENT
@@ -1580,6 +1580,10 @@ expression_tree_walker(Node *node,
                    return true;
            }
            break;
+       case T_NullTest:
+           return walker(((NullTest *) node)->arg, context);
+       case T_BooleanTest:
+           return walker(((BooleanTest *) node)->arg, context);
        case T_SubLink:
            {
                SubLink    *sublink = (SubLink *) node;
@@ -1933,6 +1937,26 @@ expression_tree_mutator(Node *node,
                return (Node *) newnode;
            }
            break;
+       case T_NullTest:
+           {
+               NullTest *ntest = (NullTest *) node;
+               NullTest *newnode;
+
+               FLATCOPY(newnode, ntest, NullTest);
+               MUTATE(newnode->arg, ntest->arg, Node *);
+               return (Node *) newnode;
+           }
+           break;
+       case T_BooleanTest:
+           {
+               BooleanTest *btest = (BooleanTest *) node;
+               BooleanTest *newnode;
+
+               FLATCOPY(newnode, btest, BooleanTest);
+               MUTATE(newnode->arg, btest->arg, Node *);
+               return (Node *) newnode;
+           }
+           break;
        case T_SubLink:
            {
 
index 263830244dc2f132dd89866ec32eae882e185fa8..e47c3f0b33153b005bc0e449ca9caae1e9929526 100644 (file)
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.230 2001/06/09 23:21:54 petere Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.231 2001/06/19 22:39:11 tgl Exp $
  *
  * HISTORY
  *   AUTHOR            DATE            MAJOR EVENT
@@ -315,7 +315,7 @@ static void doNegateFloat(Value *v);
        SCHEMA, SCROLL, SECOND_P, SELECT, SESSION, SESSION_USER, SET, SOME, SUBSTRING,
        TABLE, TEMPORARY, THEN, TIME, TIMESTAMP, TIMEZONE_HOUR,
        TIMEZONE_MINUTE, TO, TRAILING, TRANSACTION, TRIM, TRUE_P,
-       UNION, UNIQUE, UPDATE, USER, USING,
+       UNION, UNIQUE, UNKNOWN, UPDATE, USER, USING,
        VALUES, VARCHAR, VARYING, VIEW,
        WHEN, WHERE, WITH, WORK, YEAR_P, ZONE
 
@@ -386,7 +386,7 @@ static void doNegateFloat(Value *v);
 %left      Op              /* multi-character ops and user-defined operators */
 %nonassoc  NOTNULL
 %nonassoc  ISNULL
-%nonassoc  IS NULL_P TRUE_P FALSE_P    /* sets precedence for IS NULL, etc */
+%nonassoc  IS NULL_P TRUE_P FALSE_P UNKNOWN    /* sets precedence for IS NULL, etc */
 %left      '+' '-'
 %left      '*' '/' '%'
 %left      '^'
@@ -4434,9 +4434,19 @@ a_expr:  c_expr
                     * (like Microsoft's).  Turn these into IS NULL exprs.
                     */
                    if (exprIsNullConstant($3))
-                       $$ = makeA_Expr(ISNULL, NULL, $1, NULL);
+                   {
+                       NullTest *n = makeNode(NullTest);
+                       n->arg = $1;
+                       n->nulltesttype = IS_NULL;
+                       $$ = (Node *)n;
+                   }
                    else if (exprIsNullConstant($1))
-                       $$ = makeA_Expr(ISNULL, NULL, $3, NULL);
+                   {
+                       NullTest *n = makeNode(NullTest);
+                       n->arg = $3;
+                       n->nulltesttype = IS_NULL;
+                       $$ = (Node *)n;
+                   }
                    else
                        $$ = makeA_Expr(OP, "=", $1, $3);
                }
@@ -4499,59 +4509,95 @@ a_expr:  c_expr
                    n->agg_distinct = FALSE;
                    $$ = makeA_Expr(OP, "!~~*", $1, (Node *) n);
                }
-
+       /* NullTest clause
+        * Define SQL92-style Null test clause.
+        * Allow two forms described in the standard:
+        *  a IS NULL
+        *  a IS NOT NULL
+        * Allow two SQL extensions
+        *  a ISNULL
+        *  a NOTNULL
+        * NOTE: this is not yet fully SQL-compatible, since SQL92
+        * allows a row constructor as argument, not just a scalar.
+        */
        | a_expr ISNULL
-               {   $$ = makeA_Expr(ISNULL, NULL, $1, NULL); }
+               {
+                   NullTest *n = makeNode(NullTest);
+                   n->arg = $1;
+                   n->nulltesttype = IS_NULL;
+                   $$ = (Node *)n;
+               }
        | a_expr IS NULL_P
-               {   $$ = makeA_Expr(ISNULL, NULL, $1, NULL); }
+               {
+                   NullTest *n = makeNode(NullTest);
+                   n->arg = $1;
+                   n->nulltesttype = IS_NULL;
+                   $$ = (Node *)n;
+               }
        | a_expr NOTNULL
-               {   $$ = makeA_Expr(NOTNULL, NULL, $1, NULL); }
+               {
+                   NullTest *n = makeNode(NullTest);
+                   n->arg = $1;
+                   n->nulltesttype = IS_NOT_NULL;
+                   $$ = (Node *)n;
+               }
        | a_expr IS NOT NULL_P
-               {   $$ = makeA_Expr(NOTNULL, NULL, $1, NULL); }
+               {
+                   NullTest *n = makeNode(NullTest);
+                   n->arg = $1;
+                   n->nulltesttype = IS_NOT_NULL;
+                   $$ = (Node *)n;
+               }
        /* IS TRUE, IS FALSE, etc used to be function calls
         *  but let's make them expressions to allow the optimizer
         *  a chance to eliminate them if a_expr is a constant string.
         * - thomas 1997-12-22
+        *
+        *  Created BooleanTest Node type, and changed handling
+        *  for NULL inputs
+        * - jec 2001-06-18
         */
        | a_expr IS TRUE_P
                {
-                   A_Const *n = makeNode(A_Const);
-                   n->val.type = T_String;
-                   n->val.val.str = "t";
-                   n->typename = makeNode(TypeName);
-                   n->typename->name = xlateSqlType("bool");
-                   n->typename->typmod = -1;
-                   $$ = makeA_Expr(OP, "=", $1,(Node *)n);
+                   BooleanTest *b = makeNode(BooleanTest);
+                   b->arg = $1;
+                   b->booltesttype = IS_TRUE;
+                   $$ = (Node *)b;
                }
-       | a_expr IS NOT FALSE_P
+       | a_expr IS NOT TRUE_P
                {
-                   A_Const *n = makeNode(A_Const);
-                   n->val.type = T_String;
-                   n->val.val.str = "t";
-                   n->typename = makeNode(TypeName);
-                   n->typename->name = xlateSqlType("bool");
-                   n->typename->typmod = -1;
-                   $$ = makeA_Expr(OP, "=", $1,(Node *)n);
+                   BooleanTest *b = makeNode(BooleanTest);
+                   b->arg = $1;
+                   b->booltesttype = IS_NOT_TRUE;
+                   $$ = (Node *)b;
                }
        | a_expr IS FALSE_P
                {
-                   A_Const *n = makeNode(A_Const);
-                   n->val.type = T_String;
-                   n->val.val.str = "f";
-                   n->typename = makeNode(TypeName);
-                   n->typename->name = xlateSqlType("bool");
-                   n->typename->typmod = -1;
-                   $$ = makeA_Expr(OP, "=", $1,(Node *)n);
+                   BooleanTest *b = makeNode(BooleanTest);
+                   b->arg = $1;
+                   b->booltesttype = IS_FALSE;
+                   $$ = (Node *)b;
                }
-       | a_expr IS NOT TRUE_P
+       | a_expr IS NOT FALSE_P
                {
-                   A_Const *n = makeNode(A_Const);
-                   n->val.type = T_String;
-                   n->val.val.str = "f";
-                   n->typename = makeNode(TypeName);
-                   n->typename->name = xlateSqlType("bool");
-                   n->typename->typmod = -1;
-                   $$ = makeA_Expr(OP, "=", $1,(Node *)n);
+                   BooleanTest *b = makeNode(BooleanTest);
+                   b->arg = $1;
+                   b->booltesttype = IS_NOT_FALSE;
+                   $$ = (Node *)b;
+               }
+       | a_expr IS UNKNOWN
+               {
+                   BooleanTest *b = makeNode(BooleanTest);
+                   b->arg = $1;
+                   b->booltesttype = IS_UNKNOWN;
+                   $$ = (Node *)b;
+               }
+       | a_expr IS NOT UNKNOWN
+               {
+                   BooleanTest *b = makeNode(BooleanTest);
+                   b->arg = $1;
+                   b->booltesttype = IS_NOT_UNKNOWN;
+                   $$ = (Node *)b;
                }
        | a_expr BETWEEN b_expr AND b_expr          %prec BETWEEN
                {
@@ -5206,12 +5252,14 @@ case_expr:  CASE case_arg when_clause_list case_default END_TRANS
        | COALESCE '(' expr_list ')'
                {
                    CaseExpr *c = makeNode(CaseExpr);
-                   CaseWhen *w;
                    List *l;
                    foreach (l,$3)
                    {
-                       w = makeNode(CaseWhen);
-                       w->expr = makeA_Expr(NOTNULL, NULL, lfirst(l), NULL);
+                       CaseWhen *w = makeNode(CaseWhen);
+                       NullTest *n = makeNode(NullTest);
+                       n->arg = lfirst(l);
+                       n->nulltesttype = IS_NOT_NULL;
+                       w->expr = (Node *) n;
                        w->result = lfirst(l);
                        c->args = lappend(c->args, w);
                    }
@@ -5765,6 +5813,7 @@ ColLabel:  ColId                      { $$ = $1; }
        | TRUE_P                        { $$ = "true"; }
        | UNION                         { $$ = "union"; }
        | UNIQUE                        { $$ = "unique"; }
+       | UNKNOWN                       { $$ = "unknown"; }
        | USER                          { $$ = "user"; }
        | USING                         { $$ = "using"; }
        | VACUUM                        { $$ = "vacuum"; }
index 6064ca8a8ffdb1b0b9e1d5d9b545d60d8d1925c5..ccdfb88a2e2a0ec01d557203dcb0887aa9d4ad9a 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.92 2001/05/08 21:06:43 petere Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.93 2001/06/19 22:39:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -265,6 +265,7 @@ static ScanKeyword ScanKeywords[] = {
    {"type", TYPE_P},
    {"union", UNION},
    {"unique", UNIQUE},
+   {"unknown", UNKNOWN},
    {"unlisten", UNLISTEN},
    {"until", UNTIL},
    {"update", UPDATE},
index 02c6a4ac8c77cf0309968e8d943fe97b916d36e6..585b21b0f45e71a4c8042a5ad67ed42852475667 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.80 2001/05/18 21:24:19 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.81 2001/06/19 22:39:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -286,16 +286,14 @@ transformJoinUsingClause(ParseState *pstate, List *leftVars, List *rightVars)
     */
    result = transformExpr(pstate, result, EXPR_COLUMN_FIRST);
 
+   /*
+    * We expect the result to yield bool directly, otherwise complain.
+    * We could try coerce_to_boolean() here, but it seems likely that an
+    * "=" operator that doesn't return bool is wrong anyway.
+    */
    if (exprType(result) != BOOLOID)
-   {
-
-       /*
-        * This could only happen if someone defines a funny version of
-        * '='
-        */
        elog(ERROR, "JOIN/USING clause must return type bool, not type %s",
             typeidTypeName(exprType(result)));
-   }
 
    return result;
 }  /* transformJoinUsingClause() */
@@ -328,11 +326,10 @@ transformJoinOnClause(ParseState *pstate, JoinExpr *j,
 
    /* This part is just like transformWhereClause() */
    result = transformExpr(pstate, j->quals, EXPR_COLUMN_FIRST);
-   if (exprType(result) != BOOLOID)
-   {
+
+   if (! coerce_to_boolean(pstate, &result))
        elog(ERROR, "JOIN/ON clause must return type bool, not type %s",
             typeidTypeName(exprType(result)));
-   }
 
    pstate->p_namespace = save_namespace;
 
@@ -689,11 +686,11 @@ transformFromClauseItem(ParseState *pstate, Node *n, List **containedRels)
                            /* Need COALESCE(l_colvar, r_colvar) */
                            CaseExpr   *c = makeNode(CaseExpr);
                            CaseWhen   *w = makeNode(CaseWhen);
-                           A_Expr     *a = makeNode(A_Expr);
+                           NullTest   *n = makeNode(NullTest);
 
-                           a->oper = NOTNULL;
-                           a->lexpr = l_colvar;
-                           w->expr = (Node *) a;
+                           n->arg = l_colvar;
+                           n->nulltesttype = IS_NOT_NULL;
+                           w->expr = (Node *) n;
                            w->result = l_colvar;
                            c->args = makeList1(w);
                            c->defresult = r_colvar;
@@ -777,11 +774,10 @@ transformWhereClause(ParseState *pstate, Node *clause)
 
    qual = transformExpr(pstate, clause, EXPR_COLUMN_FIRST);
 
-   if (exprType(qual) != BOOLOID)
-   {
+   if (! coerce_to_boolean(pstate, &qual))
        elog(ERROR, "WHERE clause must return type bool, not type %s",
             typeidTypeName(exprType(qual)));
-   }
+
    return qual;
 }
 
index 38f044217e5d26b17a2f2ab78869eaa8de4a8467..283fd302407cad58319afd709890304d7139e830 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.57 2001/05/22 16:37:16 petere Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.58 2001/06/19 22:39:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -321,6 +321,30 @@ coerce_type_typmod(ParseState *pstate, Node *node,
 }
 
 
+/* coerce_to_boolean()
+ *     Coerce an argument of a construct that requires boolean input
+ *     (AND, OR, NOT, etc).
+ *
+ * If successful, update *pnode to be the transformed argument (if any
+ * transformation is needed), and return TRUE.  If fail, return FALSE.
+ * (The caller must check for FALSE and emit a suitable error message.)
+ */
+bool
+coerce_to_boolean(ParseState *pstate, Node **pnode)
+{
+   Oid         inputTypeId = exprType(*pnode);
+   Oid         targetTypeId;
+
+   if (inputTypeId == BOOLOID)
+       return true;            /* no work */
+   targetTypeId = BOOLOID;
+   if (! can_coerce_type(1, &inputTypeId, &targetTypeId))
+       return false;           /* fail, but let caller choose error msg */
+   *pnode = coerce_type(pstate, *pnode, inputTypeId, targetTypeId, -1);
+   return true;
+}
+
+
 /* select_common_type()
  *     Determine the common supertype of a list of input expression types.
  *     This is used for determining the output type of CASE and UNION
index a196779f44c190f474d51ef91b7a00781f4106fb..5fda57f5f921c1a69d22fe5936d54efdf3df3213 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.97 2001/06/04 23:27:23 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.98 2001/06/19 22:39:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -167,32 +167,6 @@ transformExpr(ParseState *pstate, Node *expr, int precedence)
                            result = (Node *) make_op(a->opname, lexpr, rexpr);
                        }
                        break;
-                   case ISNULL:
-                       {
-                           Node       *lexpr = transformExpr(pstate,
-                                                             a->lexpr,
-                                                             precedence);
-
-                           result = ParseFuncOrColumn(pstate,
-                                                      "nullvalue",
-                                                      makeList1(lexpr),
-                                                      false, false,
-                                                      precedence);
-                       }
-                       break;
-                   case NOTNULL:
-                       {
-                           Node       *lexpr = transformExpr(pstate,
-                                                             a->lexpr,
-                                                             precedence);
-
-                           result = ParseFuncOrColumn(pstate,
-                                                      "nonnullvalue",
-                                                      makeList1(lexpr),
-                                                      false, false,
-                                                      precedence);
-                       }
-                       break;
                    case AND:
                        {
                            Node       *lexpr = transformExpr(pstate,
@@ -203,13 +177,15 @@ transformExpr(ParseState *pstate, Node *expr, int precedence)
                                                              precedence);
                            Expr       *expr = makeNode(Expr);
 
-                           if (exprType(lexpr) != BOOLOID)
+                           if (! coerce_to_boolean(pstate, &lexpr))
                                elog(ERROR, "left-hand side of AND is type '%s', not '%s'",
-                                    typeidTypeName(exprType(lexpr)), typeidTypeName(BOOLOID));
+                                    typeidTypeName(exprType(lexpr)),
+                                    typeidTypeName(BOOLOID));
 
-                           if (exprType(rexpr) != BOOLOID)
+                           if (! coerce_to_boolean(pstate, &rexpr))
                                elog(ERROR, "right-hand side of AND is type '%s', not '%s'",
-                                    typeidTypeName(exprType(rexpr)), typeidTypeName(BOOLOID));
+                                    typeidTypeName(exprType(rexpr)),
+                                    typeidTypeName(BOOLOID));
 
                            expr->typeOid = BOOLOID;
                            expr->opType = AND_EXPR;
@@ -227,12 +203,16 @@ transformExpr(ParseState *pstate, Node *expr, int precedence)
                                                              precedence);
                            Expr       *expr = makeNode(Expr);
 
-                           if (exprType(lexpr) != BOOLOID)
+                           if (! coerce_to_boolean(pstate, &lexpr))
                                elog(ERROR, "left-hand side of OR is type '%s', not '%s'",
-                                    typeidTypeName(exprType(lexpr)), typeidTypeName(BOOLOID));
-                           if (exprType(rexpr) != BOOLOID)
+                                    typeidTypeName(exprType(lexpr)),
+                                    typeidTypeName(BOOLOID));
+
+                           if (! coerce_to_boolean(pstate, &rexpr))
                                elog(ERROR, "right-hand side of OR is type '%s', not '%s'",
-                                    typeidTypeName(exprType(rexpr)), typeidTypeName(BOOLOID));
+                                    typeidTypeName(exprType(rexpr)),
+                                    typeidTypeName(BOOLOID));
+
                            expr->typeOid = BOOLOID;
                            expr->opType = OR_EXPR;
                            expr->args = makeList2(lexpr, rexpr);
@@ -246,9 +226,11 @@ transformExpr(ParseState *pstate, Node *expr, int precedence)
                                                              precedence);
                            Expr       *expr = makeNode(Expr);
 
-                           if (exprType(rexpr) != BOOLOID)
+                           if (! coerce_to_boolean(pstate, &rexpr))
                                elog(ERROR, "argument to NOT is type '%s', not '%s'",
-                                    typeidTypeName(exprType(rexpr)), typeidTypeName(BOOLOID));
+                                    typeidTypeName(exprType(rexpr)),
+                                    typeidTypeName(BOOLOID));
+
                            expr->typeOid = BOOLOID;
                            expr->opType = NOT_EXPR;
                            expr->args = makeList1(rexpr);
@@ -491,7 +473,8 @@ transformExpr(ParseState *pstate, Node *expr, int precedence)
                CaseWhen   *w = (CaseWhen *) expr;
 
                w->expr = transformExpr(pstate, w->expr, precedence);
-               if (exprType(w->expr) != BOOLOID)
+
+               if (! coerce_to_boolean(pstate, &w->expr))
                    elog(ERROR, "WHEN clause must have a boolean result");
 
                /*
@@ -510,6 +493,59 @@ transformExpr(ParseState *pstate, Node *expr, int precedence)
                break;
            }
 
+       case T_NullTest:
+           {
+               NullTest   *n = (NullTest *) expr;
+
+               n->arg = transformExpr(pstate, n->arg, precedence);
+               /* the argument can be any type, so don't coerce it */
+               result = expr;
+               break;
+           }
+
+       case T_BooleanTest:
+           {
+               BooleanTest   *b = (BooleanTest *) expr;
+
+               b->arg = transformExpr(pstate, b->arg, precedence);
+
+               if (! coerce_to_boolean(pstate, &b->arg))
+               {
+                   const char *clausename;
+
+                   switch (b->booltesttype)
+                   {
+                       case IS_TRUE:
+                           clausename = "IS TRUE";
+                           break;
+                       case IS_NOT_TRUE:
+                           clausename = "IS NOT TRUE";
+                           break;
+                       case IS_FALSE:
+                           clausename = "IS FALSE";
+                           break;
+                       case IS_NOT_FALSE:
+                           clausename = "IS NOT FALSE";
+                           break;
+                       case IS_UNKNOWN:
+                           clausename = "IS UNKNOWN";
+                           break;
+                       case IS_NOT_UNKNOWN:
+                           clausename = "IS NOT UNKNOWN";
+                           break;
+                       default:
+                           elog(ERROR, "transformExpr: unexpected booltesttype %d",
+                                (int) b->booltesttype);
+                           clausename = NULL; /* keep compiler quiet */
+                   }
+
+                   elog(ERROR, "Argument of %s must be boolean",
+                        clausename);
+               }
+               result = expr;
+               break;
+           }
+
            /*
             * Quietly accept node types that may be presented when we are
             * called on an already-transformed tree.
@@ -669,8 +705,14 @@ exprType(Node *expr)
        case T_CaseWhen:
            type = exprType(((CaseWhen *) expr)->result);
            break;
+       case T_NullTest:
+           type = BOOLOID;
+           break;
+       case T_BooleanTest:
+           type = BOOLOID;
+           break;
        case T_Ident:
-           /* is this right? */
+           /* XXX is this right? */
            type = UNKNOWNOID;
            break;
        default:
index 5635b90a9fb8ad7e0dd997bccb9ffcf84dcb5a52..ae1a8c1fb18a750e2b871549af6d4c29bde3fd11 100644 (file)
@@ -3,7 +3,7 @@
  *             back to source text
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.77 2001/04/18 17:04:24 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.78 2001/06/19 22:39:12 tgl Exp $
  *
  *   This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -52,7 +52,6 @@
 #include "parser/parse_expr.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
-#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 
 
@@ -1948,6 +1947,60 @@ get_rule_expr(Node *node, deparse_context *context)
            }
            break;
 
+       case T_NullTest:
+           {
+               NullTest        *ntest = (NullTest *) node;
+
+               appendStringInfo(buf, "((");
+               get_rule_expr(ntest->arg, context);
+               switch (ntest->nulltesttype)
+               {
+                   case IS_NULL:
+                       appendStringInfo(buf, ") IS NULL)");
+                       break;
+                   case IS_NOT_NULL:
+                       appendStringInfo(buf, ") IS NOT NULL)");
+                       break;
+                   default:
+                       elog(ERROR, "get_rule_expr: unexpected nulltesttype %d",
+                            (int) ntest->nulltesttype);
+               }
+           }
+           break;
+
+       case T_BooleanTest:
+           {
+               BooleanTest     *btest = (BooleanTest *) node;
+
+               appendStringInfo(buf, "((");
+               get_rule_expr(btest->arg, context);
+               switch (btest->booltesttype)
+               {
+                   case IS_TRUE:
+                       appendStringInfo(buf, ") IS TRUE)");
+                       break;
+                   case IS_NOT_TRUE:
+                       appendStringInfo(buf, ") IS NOT TRUE)");
+                       break;
+                   case IS_FALSE:
+                       appendStringInfo(buf, ") IS FALSE)");
+                       break;
+                   case IS_NOT_FALSE:
+                       appendStringInfo(buf, ") IS NOT FALSE)");
+                       break;
+                   case IS_UNKNOWN:
+                       appendStringInfo(buf, ") IS UNKNOWN)");
+                       break;
+                   case IS_NOT_UNKNOWN:
+                       appendStringInfo(buf, ") IS NOT UNKNOWN)");
+                       break;
+                   default:
+                       elog(ERROR, "get_rule_expr: unexpected booltesttype %d",
+                            (int) btest->booltesttype);
+               }
+           }
+           break;
+
        case T_SubLink:
            get_sublink_expr(node, context);
            break;
@@ -1978,25 +2031,6 @@ get_func_expr(Expr *expr, deparse_context *context)
    List       *l;
    char       *sep;
 
-   /*
-    * nullvalue() and nonnullvalue() should get turned into special
-    * syntax
-    */
-   if (funcoid == F_NULLVALUE)
-   {
-       appendStringInfoChar(buf, '(');
-       get_rule_expr((Node *) lfirst(expr->args), context);
-       appendStringInfo(buf, " ISNULL)");
-       return;
-   }
-   if (funcoid == F_NONNULLVALUE)
-   {
-       appendStringInfoChar(buf, '(');
-       get_rule_expr((Node *) lfirst(expr->args), context);
-       appendStringInfo(buf, " NOTNULL)");
-       return;
-   }
-
    /*
     * Get the functions pg_proc tuple
     */
index 91feaec19e274167c781b8538399f12350393096..c34655308e09e384e6facd7dabf70a835785b1a9 100644 (file)
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: catversion.h,v 1.83 2001/06/16 18:59:31 tgl Exp $
+ * $Id: catversion.h,v 1.84 2001/06/19 22:39:12 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 200106161
+#define CATALOG_VERSION_NO 200106191
 
 #endif
index d62583c4d239c35cad8b9c2cebb172fabc812751..fe2d0357848d67fc7396069423eec38ae688f54f 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: nodes.h,v 1.90 2001/06/09 23:21:55 petere Exp $
+ * $Id: nodes.h,v 1.91 2001/06/19 22:39:12 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -218,8 +218,8 @@ typedef enum NodeTag
    T_RangeTblEntry,
    T_SortClause,
    T_GroupClause,
-   T_SubSelectXXX,             /* not used anymore; tag# available */
-   T_oldJoinExprXXX,           /* not used anymore; tag# available */
+   T_NullTest,
+   T_BooleanTest,
    T_CaseExpr,
    T_CaseWhen,
    T_RowMarkXXX,               /* not used anymore; tag# available */
index fe2d1bb7ffeadebd0d2b4b0d2ed9b3cc6bf2073a..43e64b6ad5f40bbd27341f6e00ba26c16a11b3e3 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parsenodes.h,v 1.131 2001/06/09 23:21:55 petere Exp $
+ * $Id: parsenodes.h,v 1.132 2001/06/19 22:39:12 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -983,9 +983,8 @@ typedef struct ParamNo
 typedef struct A_Expr
 {
    NodeTag     type;
-   int         oper;           /* type of operation
-                                * {OP,OR,AND,NOT,ISNULL,NOTNULL} */
-   char       *opname;         /* name of operator/function */
+   int         oper;           /* type of operation (OP,OR,AND,NOT) */
+   char       *opname;         /* name of operator */
    Node       *lexpr;          /* left argument */
    Node       *rexpr;          /* right argument */
 } A_Expr;
@@ -1054,6 +1053,50 @@ typedef struct CaseWhen
    Node       *result;         /* substitution result */
 } CaseWhen;
 
+/* ----------------
+ * NullTest
+ *
+ * NullTest represents the operation of testing a value for NULLness.
+ * Currently, we only support scalar input values, but eventually a
+ * row-constructor input should be supported.
+ * The appropriate test is performed and returned as a boolean Datum.
+ * ----------------
+ */
+
+typedef enum NullTestType
+{
+   IS_NULL, IS_NOT_NULL
+} NullTestType;
+
+typedef struct NullTest
+{
+   NodeTag         type;
+   Node            *arg;           /* input expression */
+   NullTestType    nulltesttype;   /* IS NULL, IS NOT NULL */
+} NullTest;
+
+/* ----------------
+ * BooleanTest
+ *
+ * BooleanTest represents the operation of determining whether a boolean
+ * is TRUE, FALSE, or UNKNOWN (ie, NULL).  All six meaningful combinations
+ * are supported.  Note that a NULL input does *not* cause a NULL result.
+ * The appropriate test is performed and returned as a boolean Datum.
+ * ----------------
+ */
+
+typedef enum BoolTestType
+{
+   IS_TRUE, IS_NOT_TRUE, IS_FALSE, IS_NOT_FALSE, IS_UNKNOWN, IS_NOT_UNKNOWN
+} BoolTestType;
+
+typedef struct BooleanTest
+{
+   NodeTag         type;
+   Node            *arg;           /* input expression */
+   BoolTestType    booltesttype;   /* test type */
+} BooleanTest;
+
 /*
  * ColumnDef - column definition (used in various creates)
  *
index f81a3be8307bb16bdfe5c50c14667d4185a5c379..1f508b1eae065eef7be2c9e59744919431c2da82 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parse_coerce.h,v 1.28 2001/05/22 16:37:17 petere Exp $
+ * $Id: parse_coerce.h,v 1.29 2001/06/19 22:39:12 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -136,6 +136,8 @@ extern Node *coerce_type(ParseState *pstate, Node *node, Oid inputTypeId,
 extern Node *coerce_type_typmod(ParseState *pstate, Node *node,
                   Oid targetTypeId, int32 atttypmod);
 
+extern bool coerce_to_boolean(ParseState *pstate, Node **pnode);
+
 extern Oid select_common_type(List *typeids, const char *context);
 extern Node *coerce_to_common_type(ParseState *pstate, Node *node,
                      Oid targetTypeId,