Fix ExecSubPlan to handle nulls per the SQL spec --- it didn't combine
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 12 Nov 1999 06:39:34 +0000 (06:39 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 12 Nov 1999 06:39:34 +0000 (06:39 +0000)
nulls with non-nulls using proper three-valued boolean logic.  Also clean
up ExecQual to make it clearer that ExecQual *does* follow the SQL spec
for boolean nulls.  See '[BUGS] (null) != (null)' thread around 10/26/99
for more detail.

src/backend/executor/execQual.c
src/backend/executor/nodeSubplan.c
src/include/executor/nodeSubplan.h

index 2d972f5922990695ca8144035bb1ed250d5ad932..80910db11469ebc41c1daea392aed1423b9f2321 100644 (file)
@@ -7,14 +7,14 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.63 1999/10/08 03:49:55 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.64 1999/11/12 06:39:34 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 /*
  *      INTERFACE ROUTINES
  *             ExecEvalExpr    - evaluate an expression and return a datum
- *             ExecQual                - return true/false if qualification is satisified
+ *             ExecQual                - return true/false if qualification is satisfied
  *             ExecTargetList  - form a new tuple by projecting the given tuple
  *
  *      NOTES
@@ -71,7 +71,6 @@ static Datum ExecEvalOr(Expr *orExpr, ExprContext *econtext, bool *isNull);
 static Datum ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull);
 static Datum ExecMakeFunctionResult(Node *node, List *arguments,
                                           ExprContext *econtext, bool *isNull, bool *isDone);
-static bool ExecQualClause(Node *clause, ExprContext *econtext);
 
 /*
  *       ExecEvalArrayRef
@@ -1253,7 +1252,9 @@ ExecEvalExpr(Node *expression,
                                                retDatum = (Datum) ExecEvalNot(expr, econtext, isNull);
                                                break;
                                        case SUBPLAN_EXPR:
-                                               retDatum = (Datum) ExecSubPlan((SubPlan *) expr->oper, expr->args, econtext);
+                                               retDatum = (Datum) ExecSubPlan((SubPlan *) expr->oper,
+                                                                                                          expr->args, econtext,
+                                                                                                          isNull);
                                                break;
                                        default:
                                                elog(ERROR, "ExecEvalExpr: unknown expression type %d", expr->opType);
@@ -1279,46 +1280,6 @@ ExecEvalExpr(Node *expression,
  * ----------------------------------------------------------------
  */
 
-/* ----------------------------------------------------------------
- *             ExecQualClause
- *
- *             this is a workhorse for ExecQual.  ExecQual has to deal
- *             with a list of qualifications, so it passes each qualification
- *             in the list to this function one at a time.  ExecQualClause
- *             returns true when the qualification *fails* and false if
- *             the qualification succeeded (meaning we have to test the
- *             rest of the qualification)
- * ----------------------------------------------------------------
- */
-static bool
-ExecQualClause(Node *clause, ExprContext *econtext)
-{
-       Datum           expr_value;
-       bool            isNull;
-       bool            isDone;
-
-       /* when there is a null clause, consider the qualification to fail */
-       if (clause == NULL)
-               return true;
-
-       /*
-        * pass isDone, but ignore it.  We don't iterate over multiple returns
-        * in the qualifications.
-        */
-       expr_value = ExecEvalExpr(clause, econtext, &isNull, &isDone);
-
-       /*
-        * remember, we return true when the qualification fails;
-        * NULL is considered failure.
-        */
-       if (isNull)
-               return true;
-       if (DatumGetInt32(expr_value) == 0)
-               return true;
-
-       return false;
-}
-
 /* ----------------------------------------------------------------
  *             ExecQual
  *
@@ -1329,7 +1290,7 @@ ExecQualClause(Node *clause, ExprContext *econtext)
 bool
 ExecQual(List *qual, ExprContext *econtext)
 {
-       List       *clause;
+       List       *qlist;
 
        /*
         * debugging stuff
@@ -1340,25 +1301,38 @@ ExecQual(List *qual, ExprContext *econtext)
 
        IncrProcessed();
 
-       /*
-        * return true immediately if no qual
-        */
-       if (qual == NIL)
-               return true;
-
        /*
         * a "qual" is a list of clauses.  To evaluate the qual, we evaluate
-        * each of the clauses in the list.
+        * each of the clauses in the list.  (For an empty list, we'll return
+        * TRUE.)
         *
-        * ExecQualClause returns true when we know the qualification *failed*
-        * so we just pass each clause in qual to it until we know the qual
-        * failed or there are no more clauses.
+        * If any of the clauses return NULL, we treat this as FALSE.  This
+        * is correct per the SQL spec: if any ANDed conditions are NULL, then
+        * the AND result is either FALSE or NULL, and in either case the
+        * WHERE condition fails.  NOTE: it would NOT be correct to use this
+        * simplified logic in a sub-clause; ExecEvalAnd must do the full
+        * three-state condition evaluation.  We can get away with simpler
+        * logic here because we know how the result will be used.
         */
-
-       foreach(clause, qual)
+       foreach(qlist, qual)
        {
-               if (ExecQualClause((Node *) lfirst(clause), econtext))
-                       return false;           /* qual failed, so return false */
+               Node       *clause = (Node *) lfirst(qlist);
+               Datum           expr_value;
+               bool            isNull;
+               bool            isDone;
+
+               /* if there is a null clause, consider the qualification to fail */
+               if (clause == NULL)
+                       return false;
+               /*
+                * pass isDone, but ignore it.  We don't iterate over multiple returns
+                * in the qualifications.
+                */
+               expr_value = ExecEvalExpr(clause, econtext, &isNull, &isDone);
+               if (isNull)
+                       return false;           /* treat NULL as FALSE */
+               if (DatumGetInt32(expr_value) == 0)
+                       return false;
        }
 
        return true;
index 32a39ee18d96f43a78bc1a0572f3e52479785e0f..452e3414b3cc89607d2877fd38b1eb8bd5c19724 100644 (file)
 #include "executor/nodeSubplan.h"
 #include "tcop/pquery.h"
 
+/* should be exported by execMain.c */
+extern void ExecCheckPerms(CmdType op, int resRel, List *rtable, Query *q);
+
 /* ----------------------------------------------------------------
  *             ExecSubPlan(node)
  *
  * ----------------------------------------------------------------
  */
 Datum
-ExecSubPlan(SubPlan *node, List *pvar, ExprContext *econtext)
+ExecSubPlan(SubPlan *node, List *pvar, ExprContext *econtext, bool *isNull)
 {
        Plan       *plan = node->plan;
        SubLink    *sublink = node->sublink;
        SubLinkType subLinkType = sublink->subLinkType;
+       bool            useor = sublink->useor;
        TupleTableSlot *slot;
-       List       *lst;
-       Datum           result = (Datum) false;
+       Datum           result;
        bool            found = false;  /* TRUE if got at least one subplan tuple */
+       List       *lst;
 
-       if (node->setParam != NULL)
+       if (node->setParam != NIL)
                elog(ERROR, "ExecSubPlan: can't set parent params from subquery");
 
        /*
         * Set Params of this plan from parent plan correlation Vars
         */
-       if (node->parParam != NULL)
+       if (node->parParam != NIL)
        {
                foreach(lst, node->parParam)
                {
                        ParamExecData *prm = &(econtext->ecxt_param_exec_vals[lfirsti(lst)]);
 
+                       Assert(pvar != NIL);
                        prm->value = ExecEvalExpr((Node *) lfirst(pvar),
                                                                          econtext,
                                                                          &(prm->isnull), NULL);
@@ -53,21 +58,32 @@ ExecSubPlan(SubPlan *node, List *pvar, ExprContext *econtext)
                }
                plan->chgParam = nconc(plan->chgParam, listCopy(node->parParam));
        }
+       Assert(pvar == NIL);
 
        ExecReScan(plan, (ExprContext *) NULL, plan);
 
        /*
-        * For all sublink types except EXPR_SUBLINK, the result type is
-        * boolean, and we have a fairly clear idea of how to combine multiple
-        * subitems and deal with NULL values or an empty subplan result.
+        * For all sublink types except EXPR_SUBLINK, the result is boolean
+        * as are the results of the combining operators.  We combine results
+        * within a tuple (if there are multiple columns) using OR semantics
+        * if "useor" is true, AND semantics if not.  We then combine results
+        * across tuples (if the subplan produces more than one) using OR
+        * semantics for ANY_SUBLINK or AND semantics for ALL_SUBLINK.  NULL
+        * results from the combining operators are handled according to the
+        * usual SQL semantics for OR and AND.  The result for no input
+        * tuples is FALSE for ANY_SUBLINK, TRUE for ALL_SUBLINK.
         *
-        * For EXPR_SUBLINK, the result type is whatever the combining operator
-        * returns.  We have no way to deal with more than one column in the
-        * subplan result --- hopefully the parser forbids that.  More
-        * seriously, it's unclear what to do with NULL values or an empty
-        * subplan result. For now, we error out, but should something else
-        * happen?
+        * For EXPR_SUBLINK we require the subplan to produce no more than one
+        * tuple, else an error is raised.  If zero tuples are produced, we
+        * return NULL.  (XXX it would probably be more correct to evaluate
+        * the combining operator with a NULL input?)  Assuming we get a tuple:
+        * if there is only one column then we just return its result as-is, NULL
+        * or otherwise.  If there is more than one column we combine the results
+        * per "useor" --- this only makes sense if the combining operators yield
+        * boolean, and we assume the parser has checked that.
         */
+       result = (Datum) (subLinkType == ALL_SUBLINK ? true : false);
+       *isNull = false;
 
        for (slot = ExecProcNode(plan, plan);
                 !TupIsNull(slot);
@@ -75,24 +91,26 @@ ExecSubPlan(SubPlan *node, List *pvar, ExprContext *econtext)
        {
                HeapTuple       tup = slot->val;
                TupleDesc       tdesc = slot->ttc_tupleDescriptor;
-               int                     i = 1;
-
-               if (subLinkType == EXPR_SUBLINK && found)
-               {
-                       elog(ERROR, "ExecSubPlan: more than one tuple returned by expression subselect");
-                       return (Datum) false;
-               }
+               Datum           rowresult = (Datum) (useor ? false : true);
+               bool            rownull = false;
+               int                     col = 1;
 
                if (subLinkType == EXISTS_SUBLINK)
                        return (Datum) true;
 
+               /* cannot allow multiple input tuples for EXPR sublink */
+               if (subLinkType == EXPR_SUBLINK && found)
+                       elog(ERROR, "ExecSubPlan: more than one tuple returned by expression subselect");
+
                found = true;
 
+               /* iterate over combining operators for columns of tuple */
                foreach(lst, sublink->oper)
                {
                        Expr       *expr = (Expr *) lfirst(lst);
                        Const      *con = lsecond(expr->args);
-                       bool            isnull;
+                       Datum           expresult;
+                       bool            expnull;
 
                        /*
                         * The righthand side of the expression should be either a Const
@@ -107,41 +125,90 @@ ExecSubPlan(SubPlan *node, List *pvar, ExprContext *econtext)
                                con = lfirst(((Expr *) con)->args);
                                Assert(IsA(con, Const));
                        }
-                       con->constvalue = heap_getattr(tup, i, tdesc, &(con->constisnull));
+                       con->constvalue = heap_getattr(tup, col, tdesc,
+                                                                                  &(con->constisnull));
                        /*
-                        * Now we can eval the expression.
+                        * Now we can eval the combining operator for this column.
                         */
-                       result = ExecEvalExpr((Node *) expr, econtext, &isnull,
-                                                                 (bool *) NULL);
-                       if (isnull)
+                       expresult = ExecEvalExpr((Node *) expr, econtext, &expnull,
+                                                                        (bool *) NULL);
+                       /*
+                        * Combine the result into the row result as appropriate.
+                        */
+                       if (col == 1)
                        {
-                               if (subLinkType == EXPR_SUBLINK)
-                                       elog(ERROR, "ExecSubPlan: null value returned by expression subselect");
-                               else
-                                       result = (Datum) false;
+                               rowresult = expresult;
+                               rownull = expnull;
                        }
-                       if (subLinkType != EXPR_SUBLINK)
+                       else if (useor)
                        {
-                               if ((!(bool) result && !(sublink->useor)) ||
-                                       ((bool) result && sublink->useor))
-                                       break;
+                               /* combine within row per OR semantics */
+                               if (expnull)
+                                       rownull = true;
+                               else if (DatumGetInt32(expresult) != 0)
+                               {
+                                       rowresult = (Datum) true;
+                                       rownull = false;
+                                       break;          /* needn't look at any more columns */
+                               }
                        }
-                       i++;
+                       else
+                       {
+                               /* combine within row per AND semantics */
+                               if (expnull)
+                                       rownull = true;
+                               else if (DatumGetInt32(expresult) == 0)
+                               {
+                                       rowresult = (Datum) false;
+                                       rownull = false;
+                                       break;          /* needn't look at any more columns */
+                               }
+                       }
+                       col++;
                }
 
-               if (subLinkType == ALL_SUBLINK && !(bool) result)
-                       break;
-               if (subLinkType == ANY_SUBLINK && (bool) result)
-                       break;
+               if (subLinkType == ANY_SUBLINK)
+               {
+                       /* combine across rows per OR semantics */
+                       if (rownull)
+                               *isNull = true;
+                       else if (DatumGetInt32(rowresult) != 0)
+                       {
+                               result = (Datum) true;
+                               *isNull = false;
+                               break;                  /* needn't look at any more rows */
+                       }
+               }
+               else if (subLinkType == ALL_SUBLINK)
+               {
+                       /* combine across rows per AND semantics */
+                       if (rownull)
+                               *isNull = true;
+                       else if (DatumGetInt32(rowresult) == 0)
+                       {
+                               result = (Datum) false;
+                               *isNull = false;
+                               break;                  /* needn't look at any more rows */
+                       }
+               }
+               else
+               {
+                       /* must be EXPR_SUBLINK */
+                       result = rowresult;
+                       *isNull = rownull;
+               }
        }
 
        if (!found)
        {
-               /* deal with empty subplan result.      Note default result is 'false' */
-               if (subLinkType == ALL_SUBLINK)
-                       result = (Datum) true;
-               else if (subLinkType == EXPR_SUBLINK)
-                       elog(ERROR, "ExecSubPlan: no tuples returned by expression subselect");
+               /* deal with empty subplan result.  result/isNull were previously
+                * initialized correctly for all sublink types except EXPR.
+                */
+               if (subLinkType == EXPR_SUBLINK)
+               {
+                               result = (Datum) false;
+                               *isNull = true;
+               }
        }
 
        return result;
@@ -152,7 +219,6 @@ ExecSubPlan(SubPlan *node, List *pvar, ExprContext *econtext)
  *
  * ----------------------------------------------------------------
  */
-extern void ExecCheckPerms(CmdType op, int resRel, List *rtable, Query *q);
 bool
 ExecInitSubPlan(SubPlan *node, EState *estate, Plan *parent)
 {
index 98251c24872ef50d8c5a8e02fd6a0ab2afebe01f..b25e4dee37945df8c110ceb4eeb207e8a6f42b18 100644 (file)
@@ -9,7 +9,8 @@
 
 #include "nodes/plannodes.h"
 
-extern Datum ExecSubPlan(SubPlan *node, List *pvar, ExprContext *econtext);
+extern Datum ExecSubPlan(SubPlan *node, List *pvar, ExprContext *econtext,
+                                                bool *isNull);
 extern bool ExecInitSubPlan(SubPlan *node, EState *estate, Plan *parent);
 extern void ExecReScanSetParamPlan(SubPlan *node, Plan *parent);
 extern void ExecSetParamPlan(SubPlan *node);