Implement SQL-compliant treatment of row comparisons for < <= > >= cases
authorTom Lane
Wed, 28 Dec 2005 01:30:02 +0000 (01:30 +0000)
committerTom Lane
Wed, 28 Dec 2005 01:30:02 +0000 (01:30 +0000)
(previously we only did = and <> correctly).  Also, allow row comparisons
with any operators that are in btree opclasses, not only those with these
specific names.  This gets rid of a whole lot of indefensible assumptions
about the behavior of particular operators based on their names ... though
it's still true that IN and NOT IN expand to "= ANY".  The patch adds a
RowCompareExpr expression node type, and makes some changes in the
representation of ANY/ALL/ROWCOMPARE SubLinks so that they can share code
with RowCompareExpr.

I have not yet done anything about making RowCompareExpr an indexable
operator, but will look at that soon.

initdb forced due to changes in stored rules.

26 files changed:
doc/src/sgml/func.sgml
src/backend/catalog/dependency.c
src/backend/executor/execQual.c
src/backend/executor/nodeSubplan.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/readfuncs.c
src/backend/optimizer/path/costsize.c
src/backend/optimizer/plan/subselect.c
src/backend/optimizer/util/clauses.c
src/backend/parser/gram.y
src/backend/parser/parse_expr.c
src/backend/parser/parse_oper.c
src/backend/utils/adt/ruleutils.c
src/backend/utils/cache/lsyscache.c
src/include/catalog/catversion.h
src/include/nodes/execnodes.h
src/include/nodes/nodes.h
src/include/nodes/params.h
src/include/nodes/primnodes.h
src/include/parser/parse_oper.h
src/include/utils/lsyscache.h
src/pl/plpgsql/src/pl_exec.c
src/test/regress/expected/rowtypes.out
src/test/regress/sql/rowtypes.sql

index 86e01ff113d7021c2dd5a02d37e3c5b83221b165..d90bc15d411d33b1fab88256e0f3db622c75e549 100644 (file)
@@ -1,5 +1,5 @@
 
 
@@ -350,18 +350,18 @@ PostgreSQL documentation
     
     The ordinary comparison operators yield null (signifying unknown)
     when either input is null.  Another way to do comparisons is with the
-    IS DISTINCT FROM construct:
+    IS  NOT  DISTINCT FROM construct:
 
 expression IS DISTINCT FROM expression
 expression IS NOT DISTINCT FROM expression
 
-    For non-null inputs, IS DISTINCT FROM this is
+    For non-null inputs, IS DISTINCT FROM is
     the same as the <> operator.  However, when both
     inputs are null it will return false, and when just one input is
     null it will return true.  Similarly, IS NOT DISTINCT
     FROM is identical to = for non-null
-    inputs, returns true when both inputs are null, and false
-    otherwise. Thus, these constructs effectively act as though null
+    inputs, but it returns true when both inputs are null, and false when only
+    one input is null. Thus, these constructs effectively act as though null
     were a normal data value, rather than unknown.
    
 
@@ -7999,8 +7999,8 @@ SELECT col1 FROM tab1
    equal if all their corresponding members are non-null and equal; the rows
    are unequal if any corresponding members are non-null and unequal;
    otherwise the result of that row comparison is unknown (null).
-   If all the row results are either unequal or null, with at least one null,
-   then the result of IN is null.
+   If all the per-row results are either unequal or null, with at least one
+   null, then the result of IN is null.
   
   
 
@@ -8055,8 +8055,8 @@ SELECT col1 FROM tab1
    equal if all their corresponding members are non-null and equal; the rows
    are unequal if any corresponding members are non-null and unequal;
    otherwise the result of that row comparison is unknown (null).
-   If all the row results are either unequal or null, with at least one null,
-   then the result of NOT IN is null.
+   If all the per-row results are either unequal or null, with at least one
+   null, then the result of NOT IN is null.
   
   
 
@@ -8109,23 +8109,19 @@ SELECT col1 FROM tab1
    subquery, which must return exactly as many columns as there are
    expressions in the left-hand row.  The left-hand expressions are
    evaluated and compared row-wise to each row of the subquery result,
-   using the given operator.  Presently,
-   only = and <> operators are allowed
-   in row-wise ANY constructs.
-   The result of ANY is true if any equal or unequal row is
-   found, respectively.
-   The result is false if no such row is found (including the special
-   case where the subquery returns no rows).
+   using the given operator.
+   The result of ANY is true if the comparison
+   returns true for any subquery row.
+   The result is false if the comparison returns false for every
+   subquery row (including the special case where the subquery returns no
+   rows).
+   The result is NULL if the comparison does not return true for any row,
+   and it returns NULL for at least one row.
   
 
   
-   As usual, null values in the rows are combined per
-   the normal rules of SQL Boolean expressions.  Two rows are considered
-   equal if all their corresponding members are non-null and equal; the rows
-   are unequal if any corresponding members are non-null and unequal;
-   otherwise the result of that row comparison is unknown (null).
-   If there is at least one null row result, then the result of ANY
-   cannot be false; it will be true or null. 
+   See  for details about the meaning
+   of a row-wise comparison.
   
   
 
@@ -8145,20 +8141,14 @@ SELECT col1 FROM tab1
    The result of ALL is true if all rows yield true
    (including the special case where the subquery returns no rows).
    The result is false if any false result is found.
+   The result is NULL if the comparison does not return false for any row,
+   and it returns NULL for at least one row.
   
 
   
    NOT IN is equivalent to <> ALL.
   
 
-  
-   Note that if there are no failures but at least one right-hand row yields
-   null for the operator's result, the result of the ALL construct
-   will be null, not true.
-   This is in accordance with SQL's normal rules for Boolean combinations
-   of null values.
-  
-
   
    As with EXISTS, it's unwise to assume that the subquery will
    be evaluated completely.
@@ -8175,24 +8165,19 @@ SELECT col1 FROM tab1
    subquery, which must return exactly as many columns as there are
    expressions in the left-hand row.  The left-hand expressions are
    evaluated and compared row-wise to each row of the subquery result,
-   using the given operator.  Presently,
-   only = and <> operators are allowed
-   in row-wise ALL queries.
-   The result of ALL is true if all subquery rows are equal
-   or unequal, respectively (including the special
+   using the given operator.
+   The result of ALL is true if the comparison
+   returns true for all subquery rows (including the special
    case where the subquery returns no rows).
-   The result is false if any row is found to be unequal or equal,
-   respectively.
+   The result is false if the comparison returns false for any
+   subquery row.
+   The result is NULL if the comparison does not return false for any
+   subquery row, and it returns NULL for at least one row.
   
 
   
-   As usual, null values in the rows are combined per
-   the normal rules of SQL Boolean expressions.  Two rows are considered
-   equal if all their corresponding members are non-null and equal; the rows
-   are unequal if any corresponding members are non-null and unequal;
-   otherwise the result of that row comparison is unknown (null).
-   If there is at least one null row result, then the result of ALL
-   cannot be true; it will be false or null. 
+   See  for details about the meaning
+   of a row-wise comparison.
   
   
 
@@ -8216,17 +8201,11 @@ SELECT col1 FROM tab1
    the subquery cannot return more than one row.  (If it returns zero rows,
    the result is taken to be null.)  The left-hand side is evaluated and
    compared row-wise to the single subquery result row.
-   Presently, only = and <> operators are allowed
-   in row-wise comparisons.
-   The result is true if the two rows are equal or unequal, respectively.
   
 
   
-   As usual, null values in the rows are combined per
-   the normal rules of SQL Boolean expressions.  Two rows are considered
-   equal if all their corresponding members are non-null and equal; the rows
-   are unequal if any corresponding members are non-null and unequal;
-   otherwise the result of the row comparison is unknown (null).
+   See  for details about the meaning
+   of a row-wise comparison.
   
   
  
@@ -8255,6 +8234,10 @@ SELECT col1 FROM tab1
    SOME
   
 
+  
+   row-wise comparison
+  
+
   
    comparison
    row-wise
@@ -8264,6 +8247,10 @@ SELECT col1 FROM tab1
    IS DISTINCT FROM
   
 
+  
+   IS NOT DISTINCT FROM
+  
+
   
    IS NULL
   
@@ -8288,7 +8275,7 @@ SELECT col1 FROM tab1
    <literal>IN</literal>
 
 
-expression IN (value, ...)
+expression IN (value , ...)
 
 
   
@@ -8319,7 +8306,7 @@ OR
    <literal>NOT IN</literal>
 
 
-expression NOT IN (value, ...)
+expression NOT IN (value , ...)
 
 
   
@@ -8425,7 +8412,7 @@ AND
   
   
 
-  
+   id="row-wise-comparison">
    Row-wise Comparison
 
 
@@ -8436,23 +8423,52 @@ AND
    Each side is a row constructor,
    as described in .
    The two row values must have the same number of fields.
-   Each side is evaluated and they are compared row-wise.
-   Presently, only = and <> operators are allowed
-   in row-wise comparisons.
-   The result is true if the two rows are equal or unequal, respectively.
+   Each side is evaluated and they are compared row-wise.  Row comparisons
+   are allowed when the operator is
+   =,
+   <>,
+   <,
+   <=,
+   > or
+   >=,
+   or has semantics similar to one of these.  (To be specific, an operator
+   can be a row comparison operator if it is a member of a btree operator
+   class, or is the negator of the = member of a btree operator
+   class.)
   
 
   
-   As usual, null values in the rows are combined per
-   the normal rules of SQL Boolean expressions.  Two rows are considered
+   The = and <> cases work slightly differently
+   from the others.  Two rows are considered
    equal if all their corresponding members are non-null and equal; the rows
    are unequal if any corresponding members are non-null and unequal;
    otherwise the result of the row comparison is unknown (null).
   
 
-  
-   IS DISTINCT FROM
-  
+  
+   For the <, <=, > and
+   >= cases, the row elements are compared left-to-right,
+   stopping as soon as an unequal or null pair of elements is found.
+   If either of this pair of elements is null, the result of the
+   row comparison is unknown (null); otherwise comparison of this pair
+   of elements determines the result.  For example,
+   ROW(1,2,NULL) < ROW(1,3,0)
+   yields true, not null, because the third pair of elements are not
+   considered.
+  
+
+  
+   
+    Prior to PostgreSQL 8.2, the
+    <, <=, > and >=
+    cases were not handled per SQL specification.  A comparison like
+    ROW(a,b) < ROW(c,d)
+    was implemented as
+    a < c AND b < d
+    whereas the correct behavior is equivalent to
+    a < c OR (a = c AND b < d).
+   
+  
 
 
 row_constructor IS DISTINCT FROM row_constructor
@@ -8466,6 +8482,18 @@ AND
    be either true or false, never null.
   
 
+
+row_constructor IS NOT DISTINCT FROM row_constructor
+
+
+  
+   This construct is similar to a = row comparison,
+   but it does not yield null for null inputs.  Instead, any null value is
+   considered unequal to (distinct from) any non-null value, and any two
+   nulls are considered equal (not distinct).  Thus the result will always
+   be either true or false, never null.
+  
+
 
 row_constructor IS NULL
 row_constructor IS NOT NULL
index 245b8965f8a2832ef1a273e7676b1f46a12c35df..fb0dce5d23e5f39dba1e67a39f644aae8995000a 100644 (file)
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.48 2005/11/22 18:17:07 momjian Exp $
+ *   $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.49 2005/12/28 01:29:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1129,23 +1129,28 @@ find_expr_references_walker(Node *node,
                           &context->addrs);
        /* fall through to examine arguments */
    }
-   if (IsA(node, SubLink))
+   if (is_subplan(node))
    {
-       SubLink    *sublink = (SubLink *) node;
-       ListCell   *opid;
+       /* Extra work needed here if we ever need this case */
+       elog(ERROR, "already-planned subqueries not supported");
+   }
+   if (IsA(node, RowCompareExpr))
+   {
+       RowCompareExpr *rcexpr = (RowCompareExpr *) node;
+       ListCell   *l;
 
-       foreach(opid, sublink->operOids)
+       foreach(l, rcexpr->opnos)
        {
-           add_object_address(OCLASS_OPERATOR, lfirst_oid(opid), 0,
+           add_object_address(OCLASS_OPERATOR, lfirst_oid(l), 0,
+                              &context->addrs);
+       }
+       foreach(l, rcexpr->opclasses)
+       {
+           add_object_address(OCLASS_OPCLASS, lfirst_oid(l), 0,
                               &context->addrs);
        }
        /* fall through to examine arguments */
    }
-   if (is_subplan(node))
-   {
-       /* Extra work needed here if we ever need this case */
-       elog(ERROR, "already-planned subqueries not supported");
-   }
    if (IsA(node, Query))
    {
        /* Recurse into RTE subquery or not-yet-planned sublink subquery */
index 2df9f1685cb81cfb5e507a9f15ff4496c20f3004..fe78a0fa0841daffdf8bf988888f2c0f8937e038 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.186 2005/12/14 16:28:32 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.187 2005/12/28 01:29:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -37,6 +37,7 @@
 #include "postgres.h"
 
 #include "access/heapam.h"
+#include "access/nbtree.h"
 #include "catalog/pg_type.h"
 #include "commands/typecmds.h"
 #include "executor/execdebug.h"
@@ -104,6 +105,9 @@ static Datum ExecEvalArray(ArrayExprState *astate,
 static Datum ExecEvalRow(RowExprState *rstate,
            ExprContext *econtext,
            bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalRowCompare(RowCompareExprState *rstate,
+           ExprContext *econtext,
+           bool *isNull, ExprDoneCond *isDone);
 static Datum ExecEvalCoalesce(CoalesceExprState *coalesceExpr,
                 ExprContext *econtext,
                 bool *isNull, ExprDoneCond *isDone);
@@ -2306,6 +2310,76 @@ ExecEvalRow(RowExprState *rstate,
    return HeapTupleGetDatum(tuple);
 }
 
+/* ----------------------------------------------------------------
+ *     ExecEvalRowCompare - ROW() comparison-op ROW()
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalRowCompare(RowCompareExprState *rstate,
+                  ExprContext *econtext,
+                  bool *isNull, ExprDoneCond *isDone)
+{
+   bool        result;
+   RowCompareType rctype = ((RowCompareExpr *) rstate->xprstate.expr)->rctype;
+   int32       cmpresult = 0;
+   ListCell   *l;
+   ListCell   *r;
+   int         i;
+
+   if (isDone)
+       *isDone = ExprSingleResult;
+   *isNull = true;             /* until we get a result */
+
+   i = 0;
+   forboth(l, rstate->largs, r, rstate->rargs)
+   {
+       ExprState  *le = (ExprState *) lfirst(l);
+       ExprState  *re = (ExprState *) lfirst(r);
+       FunctionCallInfoData locfcinfo;
+
+       InitFunctionCallInfoData(locfcinfo, &(rstate->funcs[i]), 2,
+                                NULL, NULL);
+       locfcinfo.arg[0] = ExecEvalExpr(le, econtext,
+                                       &locfcinfo.argnull[0], NULL);
+       locfcinfo.arg[1] = ExecEvalExpr(re, econtext,
+                                       &locfcinfo.argnull[1], NULL);
+       if (rstate->funcs[i].fn_strict &&
+           (locfcinfo.argnull[0] || locfcinfo.argnull[1]))
+           return (Datum) 0;   /* force NULL result */
+       locfcinfo.isnull = false;
+       cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo));
+       if (locfcinfo.isnull)
+           return (Datum) 0;   /* force NULL result */
+       if (cmpresult != 0)
+           break;              /* no need to compare remaining columns */
+       i++;
+   }
+
+   switch (rctype)
+   {
+       /* EQ and NE cases aren't allowed here */
+       case ROWCOMPARE_LT:
+           result = (cmpresult < 0);
+           break;
+       case ROWCOMPARE_LE:
+           result = (cmpresult <= 0);
+           break;
+       case ROWCOMPARE_GE:
+           result = (cmpresult >= 0);
+           break;
+       case ROWCOMPARE_GT:
+           result = (cmpresult > 0);
+           break;
+       default:
+           elog(ERROR, "unrecognized RowCompareType: %d", (int) rctype);
+           result = 0;         /* keep compiler quiet */
+           break;
+   }
+
+   *isNull = false;
+   return BoolGetDatum(result);
+}
+
 /* ----------------------------------------------------------------
  *     ExecEvalCoalesce
  * ----------------------------------------------------------------
@@ -3118,8 +3192,8 @@ ExecInitExpr(Expr *node, PlanState *parent)
                sstate->sub_estate = NULL;
                sstate->planstate = NULL;
 
-               sstate->exprs = (List *)
-                   ExecInitExpr((Expr *) subplan->exprs, parent);
+               sstate->testexpr =
+                   ExecInitExpr((Expr *) subplan->testexpr, parent);
                sstate->args = (List *)
                    ExecInitExpr((Expr *) subplan->args, parent);
 
@@ -3336,6 +3410,66 @@ ExecInitExpr(Expr *node, PlanState *parent)
                state = (ExprState *) rstate;
            }
            break;
+       case T_RowCompareExpr:
+           {
+               RowCompareExpr *rcexpr = (RowCompareExpr *) node;
+               RowCompareExprState *rstate = makeNode(RowCompareExprState);
+               int         nopers = list_length(rcexpr->opnos);
+               List       *outlist;
+               ListCell   *l;
+               ListCell   *l2;
+               int         i;
+
+               rstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRowCompare;
+               Assert(list_length(rcexpr->largs) == nopers);
+               outlist = NIL;
+               foreach(l, rcexpr->largs)
+               {
+                   Expr       *e = (Expr *) lfirst(l);
+                   ExprState  *estate;
+
+                   estate = ExecInitExpr(e, parent);
+                   outlist = lappend(outlist, estate);
+               }
+               rstate->largs = outlist;
+               Assert(list_length(rcexpr->rargs) == nopers);
+               outlist = NIL;
+               foreach(l, rcexpr->rargs)
+               {
+                   Expr       *e = (Expr *) lfirst(l);
+                   ExprState  *estate;
+
+                   estate = ExecInitExpr(e, parent);
+                   outlist = lappend(outlist, estate);
+               }
+               rstate->rargs = outlist;
+               Assert(list_length(rcexpr->opclasses) == nopers);
+               rstate->funcs = (FmgrInfo *) palloc(nopers * sizeof(FmgrInfo));
+               i = 0;
+               forboth(l, rcexpr->opnos, l2, rcexpr->opclasses)
+               {
+                   Oid     opno = lfirst_oid(l);
+                   Oid     opclass = lfirst_oid(l2);
+                   int     strategy;
+                   Oid     subtype;
+                   bool    recheck;
+                   Oid     proc;
+
+                   get_op_opclass_properties(opno, opclass,
+                                             &strategy, &subtype, &recheck);
+                   proc = get_opclass_proc(opclass, subtype, BTORDER_PROC);
+                   /*
+                    * If we enforced permissions checks on index support
+                    * functions, we'd need to make a check here.  But the
+                    * index support machinery doesn't do that, and neither
+                    * does this code.
+                    */
+                   fmgr_info(proc, &(rstate->funcs[i]));
+                   i++;
+               }
+               state = (ExprState *) rstate;
+           }
+           break;
        case T_CoalesceExpr:
            {
                CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
@@ -3382,6 +3516,12 @@ ExecInitExpr(Expr *node, PlanState *parent)
                            (errcode(ERRCODE_UNDEFINED_FUNCTION),
                             errmsg("could not identify a comparison function for type %s",
                                    format_type_be(minmaxexpr->minmaxtype))));
+               /*
+                * If we enforced permissions checks on index support
+                * functions, we'd need to make a check here.  But the
+                * index support machinery doesn't do that, and neither
+                * does this code.
+                */
                fmgr_info(typentry->cmp_proc, &(mstate->cfunc));
                state = (ExprState *) mstate;
            }
@@ -3484,7 +3624,7 @@ ExecInitExprInitPlan(SubPlan *node, PlanState *parent)
    sstate->sub_estate = NULL;
    sstate->planstate = NULL;
 
-   sstate->exprs = (List *) ExecInitExpr((Expr *) node->exprs, parent);
+   sstate->testexpr = ExecInitExpr((Expr *) node->testexpr, parent);
    sstate->args = (List *) ExecInitExpr((Expr *) node->args, parent);
 
    sstate->xprstate.expr = (Expr *) node;
index e35430d28b060d96d5a0b26c905b0443a7d2bc16..80679d9f63fc9c6c01edc32b2ef44ebfd3f26573 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/executor/nodeSubplan.c,v 1.71 2005/11/22 18:17:10 momjian Exp $
+ *   $PostgreSQL: pgsql/src/backend/executor/nodeSubplan.c,v 1.72 2005/12/28 01:29:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -23,6 +23,7 @@
 #include "executor/executor.h"
 #include "executor/nodeSubplan.h"
 #include "nodes/makefuncs.h"
+#include "optimizer/clauses.h"
 #include "parser/parse_expr.h"
 #include "utils/array.h"
 #include "utils/datum.h"
@@ -205,7 +206,6 @@ ExecScanSubPlan(SubPlanState *node,
    SubPlan    *subplan = (SubPlan *) node->xprstate.expr;
    PlanState  *planstate = node->planstate;
    SubLinkType subLinkType = subplan->subLinkType;
-   bool        useOr = subplan->useOr;
    MemoryContext oldcontext;
    TupleTableSlot *slot;
    Datum       result;
@@ -245,15 +245,13 @@ ExecScanSubPlan(SubPlanState *node,
    /*
     * For all sublink types except EXPR_SUBLINK and ARRAY_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.
-    * (MULTIEXPR_SUBLINK doesn't allow multiple tuples from the subplan.)
+    * (ROWCOMPARE_SUBLINK doesn't allow multiple tuples from the subplan.)
     * 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, NULL for
-    * MULTIEXPR_SUBLINK.
+    * ROWCOMPARE_SUBLINK.
     *
     * For EXPR_SUBLINK we require the subplan to produce no more than one
     * tuple, else an error is raised. For ARRAY_SUBLINK we allow the subplan
@@ -269,9 +267,9 @@ ExecScanSubPlan(SubPlanState *node,
         slot = ExecProcNode(planstate))
    {
        TupleDesc   tdesc = slot->tts_tupleDescriptor;
-       Datum       rowresult = BoolGetDatum(!useOr);
-       bool        rownull = false;
-       int         col = 1;
+       Datum       rowresult;
+       bool        rownull;
+       int         col;
        ListCell   *plst;
 
        if (subLinkType == EXISTS_SUBLINK)
@@ -304,7 +302,7 @@ ExecScanSubPlan(SubPlanState *node,
            node->curTuple = ExecCopySlotTuple(slot);
            MemoryContextSwitchTo(node->sub_estate->es_query_cxt);
 
-           result = heap_getattr(node->curTuple, col, tdesc, isNull);
+           result = heap_getattr(node->curTuple, 1, tdesc, isNull);
            /* keep scanning subplan to make sure there's only one tuple */
            continue;
        }
@@ -324,8 +322,8 @@ ExecScanSubPlan(SubPlanState *node,
            continue;
        }
 
-       /* cannot allow multiple input tuples for MULTIEXPR sublink either */
-       if (subLinkType == MULTIEXPR_SUBLINK && found)
+       /* cannot allow multiple input tuples for ROWCOMPARE sublink either */
+       if (subLinkType == ROWCOMPARE_SUBLINK && found)
            ereport(ERROR,
                    (errcode(ERRCODE_CARDINALITY_VIOLATION),
                     errmsg("more than one row returned by a subquery used as an expression")));
@@ -333,69 +331,25 @@ ExecScanSubPlan(SubPlanState *node,
        found = true;
 
        /*
-        * For ALL, ANY, and MULTIEXPR sublinks, iterate over combining
-        * operators for columns of tuple.
+        * For ALL, ANY, and ROWCOMPARE sublinks, load up the Params
+        * representing the columns of the sub-select, and then evaluate
+        * the combining expression.
         */
-       Assert(list_length(node->exprs) == list_length(subplan->paramIds));
-
-       forboth(l, node->exprs, plst, subplan->paramIds)
+       col = 1;
+       foreach(plst, subplan->paramIds)
        {
-           ExprState  *exprstate = (ExprState *) lfirst(l);
            int         paramid = lfirst_int(plst);
            ParamExecData *prmdata;
-           Datum       expresult;
-           bool        expnull;
 
-           /*
-            * Load up the Param representing this column of the sub-select.
-            */
            prmdata = &(econtext->ecxt_param_exec_vals[paramid]);
            Assert(prmdata->execPlan == NULL);
-           prmdata->value = slot_getattr(slot, col,
-                                         &(prmdata->isnull));
-
-           /*
-            * Now we can eval the combining operator for this column.
-            */
-           expresult = ExecEvalExprSwitchContext(exprstate, econtext,
-                                                 &expnull, NULL);
-
-           /*
-            * Combine the result into the row result as appropriate.
-            */
-           if (col == 1)
-           {
-               rowresult = expresult;
-               rownull = expnull;
-           }
-           else if (useOr)
-           {
-               /* combine within row per OR semantics */
-               if (expnull)
-                   rownull = true;
-               else if (DatumGetBool(expresult))
-               {
-                   rowresult = BoolGetDatum(true);
-                   rownull = false;
-                   break;      /* needn't look at any more columns */
-               }
-           }
-           else
-           {
-               /* combine within row per AND semantics */
-               if (expnull)
-                   rownull = true;
-               else if (!DatumGetBool(expresult))
-               {
-                   rowresult = BoolGetDatum(false);
-                   rownull = false;
-                   break;      /* needn't look at any more columns */
-               }
-           }
-
+           prmdata->value = slot_getattr(slot, col, &(prmdata->isnull));
            col++;
        }
 
+       rowresult = ExecEvalExprSwitchContext(node->testexpr, econtext,
+                                             &rownull, NULL);
+
        if (subLinkType == ANY_SUBLINK)
        {
            /* combine across rows per OR semantics */
@@ -422,7 +376,7 @@ ExecScanSubPlan(SubPlanState *node,
        }
        else
        {
-           /* must be MULTIEXPR_SUBLINK */
+           /* must be ROWCOMPARE_SUBLINK */
            result = rowresult;
            *isNull = rownull;
        }
@@ -433,11 +387,11 @@ ExecScanSubPlan(SubPlanState *node,
        /*
         * deal with empty subplan result.  result/isNull were previously
         * initialized correctly for all sublink types except EXPR, ARRAY, and
-        * MULTIEXPR; for those, return NULL.
+        * ROWCOMPARE; for those, return NULL.
         */
        if (subLinkType == EXPR_SUBLINK ||
            subLinkType == ARRAY_SUBLINK ||
-           subLinkType == MULTIEXPR_SUBLINK)
+           subLinkType == ROWCOMPARE_SUBLINK)
        {
            result = (Datum) 0;
            *isNull = true;
@@ -463,7 +417,7 @@ buildSubPlanHash(SubPlanState *node)
 {
    SubPlan    *subplan = (SubPlan *) node->xprstate.expr;
    PlanState  *planstate = node->planstate;
-   int         ncols = list_length(node->exprs);
+   int         ncols = list_length(subplan->paramIds);
    ExprContext *innerecontext = node->innerecontext;
    MemoryContext tempcxt = innerecontext->ecxt_per_tuple_memory;
    MemoryContext oldcontext;
@@ -471,7 +425,6 @@ buildSubPlanHash(SubPlanState *node)
    TupleTableSlot *slot;
 
    Assert(subplan->subLinkType == ANY_SUBLINK);
-   Assert(!subplan->useOr);
 
    /*
     * If we already had any hash tables, destroy 'em; then create empty hash
@@ -764,11 +717,12 @@ ExecInitSubPlan(SubPlanState *node, EState *estate)
        TupleDesc   tupDesc;
        TupleTable  tupTable;
        TupleTableSlot *slot;
-       List       *lefttlist,
+       List       *oplist,
+                  *lefttlist,
                   *righttlist,
                   *leftptlist,
                   *rightptlist;
-       ListCell   *lexpr;
+       ListCell   *l;
 
        /* We need a memory context to hold the hash table(s) */
        node->tablecxt =
@@ -780,7 +734,7 @@ ExecInitSubPlan(SubPlanState *node, EState *estate)
        /* and a short-lived exprcontext for function evaluation */
        node->innerecontext = CreateExprContext(estate);
        /* Silly little array of column numbers 1..n */
-       ncols = list_length(node->exprs);
+       ncols = list_length(subplan->paramIds);
        node->keyColIdx = (AttrNumber *) palloc(ncols * sizeof(AttrNumber));
        for (i = 0; i < ncols; i++)
            node->keyColIdx[i] = i + 1;
@@ -799,14 +753,34 @@ ExecInitSubPlan(SubPlanState *node, EState *estate)
         * We also extract the combining operators themselves to initialize
         * the equality and hashing functions for the hash tables.
         */
+       if (IsA(node->testexpr->expr, OpExpr))
+       {
+           /* single combining operator */
+           oplist = list_make1(node->testexpr);
+       }
+       else if (and_clause((Node *) node->testexpr->expr))
+       {
+           /* multiple combining operators */
+           Assert(IsA(node->testexpr, BoolExprState));
+           oplist = ((BoolExprState *) node->testexpr)->args;
+       }
+       else
+       {
+           /* shouldn't see anything else in a hashable subplan */
+           elog(ERROR, "unrecognized testexpr type: %d",
+                (int) nodeTag(node->testexpr->expr));
+           oplist = NIL;       /* keep compiler quiet */
+       }
+       Assert(list_length(oplist) == ncols);
+
        lefttlist = righttlist = NIL;
        leftptlist = rightptlist = NIL;
        node->eqfunctions = (FmgrInfo *) palloc(ncols * sizeof(FmgrInfo));
        node->hashfunctions = (FmgrInfo *) palloc(ncols * sizeof(FmgrInfo));
        i = 1;
-       foreach(lexpr, node->exprs)
+       foreach(l, oplist)
        {
-           FuncExprState *fstate = (FuncExprState *) lfirst(lexpr);
+           FuncExprState *fstate = (FuncExprState *) lfirst(l);
            OpExpr     *opexpr = (OpExpr *) fstate->xprstate.expr;
            ExprState  *exstate;
            Expr       *expr;
@@ -967,7 +941,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
 
        if (found &&
            (subLinkType == EXPR_SUBLINK ||
-            subLinkType == MULTIEXPR_SUBLINK))
+            subLinkType == ROWCOMPARE_SUBLINK))
            ereport(ERROR,
                    (errcode(ERRCODE_CARDINALITY_VIOLATION),
                     errmsg("more than one row returned by a subquery used as an expression")));
index 1d816ead3a24fa52d97077c95fdffeea55b1058b..7a16cbcff56baccc4116ea3a29b576a68a0b1d99 100644 (file)
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.323 2005/12/20 02:30:35 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.324 2005/12/28 01:29:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -862,10 +862,8 @@ _copySubLink(SubLink *from)
    SubLink    *newnode = makeNode(SubLink);
 
    COPY_SCALAR_FIELD(subLinkType);
-   COPY_SCALAR_FIELD(useOr);
-   COPY_NODE_FIELD(lefthand);
+   COPY_NODE_FIELD(testexpr);
    COPY_NODE_FIELD(operName);
-   COPY_NODE_FIELD(operOids);
    COPY_NODE_FIELD(subselect);
 
    return newnode;
@@ -880,8 +878,7 @@ _copySubPlan(SubPlan *from)
    SubPlan    *newnode = makeNode(SubPlan);
 
    COPY_SCALAR_FIELD(subLinkType);
-   COPY_SCALAR_FIELD(useOr);
-   COPY_NODE_FIELD(exprs);
+   COPY_NODE_FIELD(testexpr);
    COPY_NODE_FIELD(paramIds);
    COPY_NODE_FIELD(plan);
    COPY_SCALAR_FIELD(plan_id);
@@ -1033,6 +1030,23 @@ _copyRowExpr(RowExpr *from)
    return newnode;
 }
 
+/*
+ * _copyRowCompareExpr
+ */
+static RowCompareExpr *
+_copyRowCompareExpr(RowCompareExpr *from)
+{
+   RowCompareExpr    *newnode = makeNode(RowCompareExpr);
+
+   COPY_SCALAR_FIELD(rctype);
+   COPY_NODE_FIELD(opnos);
+   COPY_NODE_FIELD(opclasses);
+   COPY_NODE_FIELD(largs);
+   COPY_NODE_FIELD(rargs);
+
+   return newnode;
+}
+
 /*
  * _copyCoalesceExpr
  */
@@ -2876,6 +2890,9 @@ copyObject(void *from)
        case T_RowExpr:
            retval = _copyRowExpr(from);
            break;
+       case T_RowCompareExpr:
+           retval = _copyRowCompareExpr(from);
+           break;
        case T_CoalesceExpr:
            retval = _copyCoalesceExpr(from);
            break;
index 824a7ff82c34ba36d237fe86de575003e15765e8..b006cec150d5fe61a5f1d9264646140a3566e566 100644 (file)
@@ -18,7 +18,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.259 2005/12/20 02:30:35 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.260 2005/12/28 01:29:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -156,6 +156,7 @@ _equalParam(Param *a, Param *b)
            break;
        case PARAM_NUM:
        case PARAM_EXEC:
+       case PARAM_SUBLINK:
            COMPARE_SCALAR_FIELD(paramid);
            break;
        default:
@@ -295,10 +296,8 @@ static bool
 _equalSubLink(SubLink *a, SubLink *b)
 {
    COMPARE_SCALAR_FIELD(subLinkType);
-   COMPARE_SCALAR_FIELD(useOr);
-   COMPARE_NODE_FIELD(lefthand);
+   COMPARE_NODE_FIELD(testexpr);
    COMPARE_NODE_FIELD(operName);
-   COMPARE_NODE_FIELD(operOids);
    COMPARE_NODE_FIELD(subselect);
 
    return true;
@@ -308,8 +307,7 @@ static bool
 _equalSubPlan(SubPlan *a, SubPlan *b)
 {
    COMPARE_SCALAR_FIELD(subLinkType);
-   COMPARE_SCALAR_FIELD(useOr);
-   COMPARE_NODE_FIELD(exprs);
+   COMPARE_NODE_FIELD(testexpr);
    COMPARE_NODE_FIELD(paramIds);
    /* should compare plans, but have to settle for comparing plan IDs */
    COMPARE_SCALAR_FIELD(plan_id);
@@ -440,6 +438,18 @@ _equalRowExpr(RowExpr *a, RowExpr *b)
    return true;
 }
 
+static bool
+_equalRowCompareExpr(RowCompareExpr *a, RowCompareExpr *b)
+{
+   COMPARE_SCALAR_FIELD(rctype);
+   COMPARE_NODE_FIELD(opnos);
+   COMPARE_NODE_FIELD(opclasses);
+   COMPARE_NODE_FIELD(largs);
+   COMPARE_NODE_FIELD(rargs);
+
+   return true;
+}
+
 static bool
 _equalCoalesceExpr(CoalesceExpr *a, CoalesceExpr *b)
 {
@@ -1919,6 +1929,9 @@ equal(void *a, void *b)
        case T_RowExpr:
            retval = _equalRowExpr(a, b);
            break;
+       case T_RowCompareExpr:
+           retval = _equalRowCompareExpr(a, b);
+           break;
        case T_CoalesceExpr:
            retval = _equalCoalesceExpr(a, b);
            break;
index aa5fd99db86436eab4b60e8af3f7453a3f40359e..b60eab31fd52511f47715689fb3aa2358c6740f8 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.265 2005/12/20 02:30:35 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.266 2005/12/28 01:29:59 tgl Exp $
  *
  * NOTES
  *   Every node type that can appear in stored rules' parsetrees *must*
@@ -736,10 +736,8 @@ _outSubLink(StringInfo str, SubLink *node)
    WRITE_NODE_TYPE("SUBLINK");
 
    WRITE_ENUM_FIELD(subLinkType, SubLinkType);
-   WRITE_BOOL_FIELD(useOr);
-   WRITE_NODE_FIELD(lefthand);
+   WRITE_NODE_FIELD(testexpr);
    WRITE_NODE_FIELD(operName);
-   WRITE_NODE_FIELD(operOids);
    WRITE_NODE_FIELD(subselect);
 }
 
@@ -749,8 +747,7 @@ _outSubPlan(StringInfo str, SubPlan *node)
    WRITE_NODE_TYPE("SUBPLAN");
 
    WRITE_ENUM_FIELD(subLinkType, SubLinkType);
-   WRITE_BOOL_FIELD(useOr);
-   WRITE_NODE_FIELD(exprs);
+   WRITE_NODE_FIELD(testexpr);
    WRITE_NODE_FIELD(paramIds);
    WRITE_NODE_FIELD(plan);
    WRITE_INT_FIELD(plan_id);
@@ -855,6 +852,18 @@ _outRowExpr(StringInfo str, RowExpr *node)
    WRITE_ENUM_FIELD(row_format, CoercionForm);
 }
 
+static void
+_outRowCompareExpr(StringInfo str, RowCompareExpr *node)
+{
+   WRITE_NODE_TYPE("ROWCOMPARE");
+
+   WRITE_ENUM_FIELD(rctype, RowCompareType);
+   WRITE_NODE_FIELD(opnos);
+   WRITE_NODE_FIELD(opclasses);
+   WRITE_NODE_FIELD(largs);
+   WRITE_NODE_FIELD(rargs);
+}
+
 static void
 _outCoalesceExpr(StringInfo str, CoalesceExpr *node)
 {
@@ -1936,6 +1945,9 @@ _outNode(StringInfo str, void *obj)
            case T_RowExpr:
                _outRowExpr(str, obj);
                break;
+           case T_RowCompareExpr:
+               _outRowCompareExpr(str, obj);
+               break;
            case T_CoalesceExpr:
                _outCoalesceExpr(str, obj);
                break;
index 46c998344616c68f6625b18c2da1d6b27c0eabab..eb2886d843700f735edcba07ccb88d8b0dc675e1 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.182 2005/10/15 02:49:19 momjian Exp $
+ *   $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.183 2005/12/28 01:29:59 tgl Exp $
  *
  * NOTES
  *   Path and Plan nodes do not have any readfuncs support, because we
@@ -494,10 +494,8 @@ _readSubLink(void)
    READ_LOCALS(SubLink);
 
    READ_ENUM_FIELD(subLinkType, SubLinkType);
-   READ_BOOL_FIELD(useOr);
-   READ_NODE_FIELD(lefthand);
+   READ_NODE_FIELD(testexpr);
    READ_NODE_FIELD(operName);
-   READ_NODE_FIELD(operOids);
    READ_NODE_FIELD(subselect);
 
    READ_DONE();
@@ -645,6 +643,23 @@ _readRowExpr(void)
    READ_DONE();
 }
 
+/*
+ * _readRowCompareExpr
+ */
+static RowCompareExpr *
+_readRowCompareExpr(void)
+{
+   READ_LOCALS(RowCompareExpr);
+
+   READ_ENUM_FIELD(rctype, RowCompareType);
+   READ_NODE_FIELD(opnos);
+   READ_NODE_FIELD(opclasses);
+   READ_NODE_FIELD(largs);
+   READ_NODE_FIELD(rargs);
+
+   READ_DONE();
+}
+
 /*
  * _readCoalesceExpr
  */
@@ -996,6 +1011,8 @@ parseNodeString(void)
        return_value = _readArrayExpr();
    else if (MATCH("ROW", 3))
        return_value = _readRowExpr();
+   else if (MATCH("ROWCOMPARE", 10))
+       return_value = _readRowCompareExpr();
    else if (MATCH("COALESCE", 8))
        return_value = _readCoalesceExpr();
    else if (MATCH("MINMAX", 6))
index e45e454a3751c6308b134007259dc494ef1a1540..c258accd8e0430da8fdeef2d1c24d2153911479d 100644 (file)
@@ -49,7 +49,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.151 2005/11/26 22:14:56 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.152 2005/12/28 01:29:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1609,6 +1609,13 @@ cost_qual_eval_walker(Node *node, QualCost *total)
        total->per_tuple +=
            cpu_operator_cost * estimate_array_length(arraynode) * 0.5;
    }
+   else if (IsA(node, RowCompareExpr))
+   {
+       /* Conservatively assume we will check all the columns */
+       RowCompareExpr *rcexpr = (RowCompareExpr *) node;
+
+       total->per_tuple += cpu_operator_cost * list_length(rcexpr->opnos);
+   }
    else if (IsA(node, SubLink))
    {
        /* This routine should not be applied to un-planned expressions */
@@ -1624,7 +1631,6 @@ cost_qual_eval_walker(Node *node, QualCost *total)
         *
         * An exception occurs when we have decided we can implement the
         * subplan by hashing.
-        *
         */
        SubPlan    *subplan = (SubPlan *) node;
        Plan       *plan = subplan->plan;
@@ -1643,7 +1649,7 @@ cost_qual_eval_walker(Node *node, QualCost *total)
            /*
             * The per-tuple costs include the cost of evaluating the lefthand
             * expressions, plus the cost of probing the hashtable. Recursion
-            * into the exprs list will handle the lefthand expressions
+            * into the testexpr will handle the lefthand expressions
             * properly, and will count one cpu_operator_cost for each
             * comparison operator.  That is probably too low for the probing
             * cost, but it's hard to make a better estimate, so live with it
index 5775b0521fbd030ab9e0eea43834b2204c0d7de8..5efbb10f037f1b08540b2a3de9313df22fbe8844 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.102 2005/11/26 22:14:57 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.103 2005/12/28 01:29:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "nodes/makefuncs.h"
 #include "nodes/params.h"
 #include "optimizer/clauses.h"
-#include "optimizer/cost.h"
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/subselect.h"
 #include "optimizer/var.h"
 #include "parser/parsetree.h"
 #include "parser/parse_expr.h"
-#include "parser/parse_oper.h"
 #include "parser/parse_relation.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/builtins.h"
@@ -74,6 +72,12 @@ typedef struct PlannerParamItem
 } PlannerParamItem;
 
 
+typedef struct convert_testexpr_context
+{
+   int         rtindex;        /* RT index for Vars, or 0 for Params */
+   List       *righthandIds;   /* accumulated list of Vars or Param IDs */
+} convert_testexpr_context;
+
 typedef struct finalize_primnode_context
 {
    Bitmapset  *paramids;       /* Set of PARAM_EXEC paramids found */
@@ -81,10 +85,13 @@ typedef struct finalize_primnode_context
 } finalize_primnode_context;
 
 
-static List *convert_sublink_opers(List *lefthand, List *operOids,
-                     List *targetlist, int rtindex,
-                     List **righthandIds);
+static Node *convert_testexpr(Node *testexpr,
+                             int rtindex,
+                             List **righthandIds);
+static Node *convert_testexpr_mutator(Node *node,
+                                     convert_testexpr_context *context);
 static bool subplan_is_hashable(SubLink *slink, SubPlan *node);
+static bool hash_ok_operator(OpExpr *expr);
 static Node *replace_correlation_vars_mutator(Node *node, void *context);
 static Node *process_sublinks_mutator(Node *node, bool *isTopQual);
 static Bitmapset *finalize_plan(Plan *plan, List *rtable,
@@ -228,20 +235,20 @@ generate_new_param(Oid paramtype, int32 paramtypmod)
 }
 
 /*
- * Convert a bare SubLink (as created by the parser) into a SubPlan.
+ * Convert a SubLink (as created by the parser) into a SubPlan.
  *
- * We are given the raw SubLink and the already-processed lefthand argument
- * list (use this instead of the SubLink's own field).  We are also told if
+ * We are given the original SubLink and the already-processed testexpr
+ * (use this instead of the SubLink's own field).  We are also told if
  * this expression appears at top level of a WHERE/HAVING qual.
  *
  * The result is whatever we need to substitute in place of the SubLink
  * node in the executable expression.  This will be either the SubPlan
  * node (if we have to do the subplan as a subplan), or a Param node
- * representing the result of an InitPlan, or possibly an AND or OR tree
- * containing InitPlan Param nodes.
+ * representing the result of an InitPlan, or a row comparison expression
+ * tree containing InitPlan Param nodes.
  */
 static Node *
-make_subplan(SubLink *slink, List *lefthand, bool isTopQual)
+make_subplan(SubLink *slink, Node *testexpr, bool isTopQual)
 {
    SubPlan    *node = makeNode(SubPlan);
    Query      *subquery = (Query *) (slink->subselect);
@@ -264,7 +271,7 @@ make_subplan(SubLink *slink, List *lefthand, bool isTopQual)
     * first tuple will be retrieved.  For ALL and ANY subplans, we will be
     * able to stop evaluating if the test condition fails, so very often not
     * all the tuples will be retrieved; for lack of a better idea, specify
-    * 50% retrieval.  For EXPR and MULTIEXPR subplans, use default behavior
+    * 50% retrieval.  For EXPR and ROWCOMPARE subplans, use default behavior
     * (we're only expecting one row out, anyway).
     *
     * NOTE: if you change these numbers, also change cost_qual_eval_walker()
@@ -300,8 +307,7 @@ make_subplan(SubLink *slink, List *lefthand, bool isTopQual)
     * Initialize other fields of the SubPlan node.
     */
    node->subLinkType = slink->subLinkType;
-   node->useOr = slink->useOr;
-   node->exprs = NIL;
+   node->testexpr = NULL;
    node->paramIds = NIL;
    node->useHashTable = false;
    /* At top level of a qual, can treat UNKNOWN the same as FALSE */
@@ -326,11 +332,11 @@ make_subplan(SubLink *slink, List *lefthand, bool isTopQual)
 
    /*
     * Un-correlated or undirect correlated plans of EXISTS, EXPR, ARRAY, or
-    * MULTIEXPR types can be used as initPlans.  For EXISTS, EXPR, or ARRAY,
+    * ROWCOMPARE types can be used as initPlans.  For EXISTS, EXPR, or ARRAY,
     * we just produce a Param referring to the result of evaluating the
-    * initPlan.  For MULTIEXPR, we must build an AND or OR-clause of the
-    * individual comparison operators, using the appropriate lefthand side
-    * expressions and Params for the initPlan's target items.
+    * initPlan.  For ROWCOMPARE, we must modify the testexpr tree to contain
+    * PARAM_EXEC Params instead of the PARAM_SUBLINK Params emitted by the
+    * parser.
     */
    if (node->parParam == NIL && slink->subLinkType == EXISTS_SUBLINK)
    {
@@ -369,34 +375,30 @@ make_subplan(SubLink *slink, List *lefthand, bool isTopQual)
        PlannerInitPlan = lappend(PlannerInitPlan, node);
        result = (Node *) prm;
    }
-   else if (node->parParam == NIL && slink->subLinkType == MULTIEXPR_SUBLINK)
+   else if (node->parParam == NIL && slink->subLinkType == ROWCOMPARE_SUBLINK)
    {
-       List       *exprs;
-
-       /* Convert the lefthand exprs and oper OIDs into executable exprs */
-       exprs = convert_sublink_opers(lefthand,
-                                     slink->operOids,
-                                     plan->targetlist,
-                                     0,
-                                     &node->paramIds);
+       /* Adjust the Params */
+       result = convert_testexpr(testexpr,
+                                 0,
+                                 &node->paramIds);
        node->setParam = list_copy(node->paramIds);
        PlannerInitPlan = lappend(PlannerInitPlan, node);
 
        /*
-        * The executable expressions are returned to become part of the outer
-        * plan's expression tree; they are not kept in the initplan node.
+        * The executable expression is returned to become part of the outer
+        * plan's expression tree; it is not kept in the initplan node.
         */
-       if (list_length(exprs) > 1)
-           result = (Node *) (node->useOr ? make_orclause(exprs) :
-                              make_andclause(exprs));
-       else
-           result = (Node *) linitial(exprs);
    }
    else
    {
        List       *args;
        ListCell   *l;
 
+       /* Adjust the Params */
+       node->testexpr = convert_testexpr(testexpr,
+                                         0,
+                                         &node->paramIds);
+
        /*
         * We can't convert subplans of ALL_SUBLINK or ANY_SUBLINK types to
         * initPlans, even when they are uncorrelated or undirect correlated,
@@ -434,13 +436,6 @@ make_subplan(SubLink *slink, List *lefthand, bool isTopQual)
                node->plan = plan = materialize_finished_plan(plan);
        }
 
-       /* Convert the lefthand exprs and oper OIDs into executable exprs */
-       node->exprs = convert_sublink_opers(lefthand,
-                                           slink->operOids,
-                                           plan->targetlist,
-                                           0,
-                                           &node->paramIds);
-
        /*
         * Make node->args from parParam.
         */
@@ -465,10 +460,9 @@ make_subplan(SubLink *slink, List *lefthand, bool isTopQual)
 }
 
 /*
- * convert_sublink_opers: given a lefthand-expressions list and a list of
- * operator OIDs, build a list of actually executable expressions. The
- * righthand sides of the expressions are Params or Vars representing the
- * results of the sub-select.
+ * convert_testexpr: convert the testexpr given by the parser into
+ * actually executable form.  This entails replacing PARAM_SUBLINK Params
+ * with Params or Vars representing the results of the sub-select:
  *
  * If rtindex is 0, we build Params to represent the sub-select outputs.
  * The paramids of the Params created are returned in the *righthandIds list.
@@ -476,88 +470,82 @@ make_subplan(SubLink *slink, List *lefthand, bool isTopQual)
  * If rtindex is not 0, we build Vars using that rtindex as varno. Copies
  * of the Var nodes are returned in *righthandIds (this is a bit of a type
  * cheat, but we can get away with it).
+ *
+ * The given testexpr has already been recursively processed by
+ * process_sublinks_mutator.  Hence it can no longer contain any
+ * PARAM_SUBLINK Params for lower SubLink nodes; we can safely assume that
+ * any we find are for our own level of SubLink.
  */
-static List *
-convert_sublink_opers(List *lefthand, List *operOids,
-                     List *targetlist, int rtindex,
-                     List **righthandIds)
+static Node *
+convert_testexpr(Node *testexpr,
+                int rtindex,
+                List **righthandIds)
 {
-   List       *result = NIL;
-   ListCell   *l,
-              *lefthand_item,
-              *tlist_item;
+   Node       *result;
+   convert_testexpr_context context;
 
-   *righthandIds = NIL;
-   lefthand_item = list_head(lefthand);
-   tlist_item = list_head(targetlist);
+   context.rtindex = rtindex;
+   context.righthandIds = NIL;
+   result = convert_testexpr_mutator(testexpr, &context);
+   *righthandIds = context.righthandIds;
+   return result;
+}
 
-   foreach(l, operOids)
+static Node *
+convert_testexpr_mutator(Node *node,
+                        convert_testexpr_context *context)
+{
+   if (node == NULL)
+       return NULL;
+   if (IsA(node, Param))
    {
-       Oid         opid = lfirst_oid(l);
-       Node       *leftop = (Node *) lfirst(lefthand_item);
-       TargetEntry *te = (TargetEntry *) lfirst(tlist_item);
-       Node       *rightop;
-       Operator    tup;
+       Param  *param = (Param *) node;
 
-       Assert(!te->resjunk);
-
-       if (rtindex)
+       if (param->paramkind == PARAM_SUBLINK)
        {
-           /* Make the Var node representing the subplan's result */
-           rightop = (Node *) makeVar(rtindex,
-                                      te->resno,
-                                      exprType((Node *) te->expr),
-                                      exprTypmod((Node *) te->expr),
-                                      0);
-
            /*
-            * Copy it for caller.  NB: we need a copy to avoid having
-            * doubly-linked substructure in the modified parse tree.
+            * We expect to encounter the Params in column-number sequence.
+            * We could handle non-sequential order if necessary, but for now
+            * there's no need.  (This is also a useful cross-check that we
+            * aren't finding any unexpected Params.)
             */
-           *righthandIds = lappend(*righthandIds, copyObject(rightop));
-       }
-       else
-       {
-           /* Make the Param node representing the subplan's result */
-           Param      *prm;
-
-           prm = generate_new_param(exprType((Node *) te->expr),
-                                    exprTypmod((Node *) te->expr));
-           /* Record its ID */
-           *righthandIds = lappend_int(*righthandIds, prm->paramid);
-           rightop = (Node *) prm;
-       }
-
-       /* Look up the operator to pass to make_op_expr */
-       tup = SearchSysCache(OPEROID,
-                            ObjectIdGetDatum(opid),
-                            0, 0, 0);
-       if (!HeapTupleIsValid(tup))
-           elog(ERROR, "cache lookup failed for operator %u", opid);
+           if (param->paramid != list_length(context->righthandIds) + 1)
+               elog(ERROR, "unexpected PARAM_SUBLINK ID: %d", param->paramid);
 
-       /*
-        * Make the expression node.
-        *
-        * Note: we use make_op_expr in case runtime type conversion function
-        * calls must be inserted for this operator!  (But we are not
-        * expecting to have to resolve unknown Params, so it's okay to pass a
-        * null pstate.)
-        */
-       result = lappend(result,
-                        make_op_expr(NULL,
-                                     tup,
-                                     leftop,
-                                     rightop,
-                                     exprType(leftop),
-                                     exprType((Node *) te->expr)));
-
-       ReleaseSysCache(tup);
-
-       lefthand_item = lnext(lefthand_item);
-       tlist_item = lnext(tlist_item);
+           if (context->rtindex)
+           {
+               /* Make the Var node representing the subplan's result */
+               Var    *newvar;
+
+               newvar = makeVar(context->rtindex,
+                                param->paramid,
+                                param->paramtype,
+                                -1,
+                                0);
+               /*
+                * Copy it for caller.  NB: we need a copy to avoid having
+                * doubly-linked substructure in the modified parse tree.
+                */
+               context->righthandIds = lappend(context->righthandIds,
+                                               copyObject(newvar));
+               return (Node *) newvar;
+           }
+           else
+           {
+               /* Make the Param node representing the subplan's result */
+               Param      *newparam;
+
+               newparam = generate_new_param(param->paramtype, -1);
+               /* Record its ID */
+               context->righthandIds = lappend_int(context->righthandIds,
+                                                   newparam->paramid);
+               return (Node *) newparam;
+           }
+       }
    }
-
-   return result;
+   return expression_tree_mutator(node,
+                                  convert_testexpr_mutator,
+                                  (void *) context);
 }
 
 /*
@@ -573,15 +561,19 @@ subplan_is_hashable(SubLink *slink, SubPlan *node)
    ListCell   *l;
 
    /*
-    * The sublink type must be "= ANY" --- that is, an IN operator. (We
-    * require the operator name to be unqualified, which may be overly
-    * paranoid, or may not be.)  XXX since we also check that the operators
-    * are hashable, the test on operator name may be redundant?
+    * The sublink type must be "= ANY" --- that is, an IN operator.  We
+    * expect that the test expression will be either a single OpExpr, or an
+    * AND-clause containing OpExprs.  (If it's anything else then the parser
+    * must have determined that the operators have non-equality-like
+    * semantics.  In the OpExpr case we can't be sure what the operator's
+    * semantics are like, but the test below for hashability will reject
+    * anything that's not equality.)
     */
    if (slink->subLinkType != ANY_SUBLINK)
        return false;
-   if (list_length(slink->operName) != 1 ||
-       strcmp(strVal(linitial(slink->operName)), "=") != 0)
+   if (slink->testexpr == NULL ||
+       (!IsA(slink->testexpr, OpExpr) &&
+        !and_clause(slink->testexpr)))
        return false;
 
    /*
@@ -614,26 +606,47 @@ subplan_is_hashable(SubLink *slink, SubPlan *node)
     * could be relaxed by using two different sets of operators with the hash
     * table, but there is no obvious usefulness to that at present.)
     */
-   foreach(l, slink->operOids)
+   if (IsA(slink->testexpr, OpExpr))
    {
-       Oid         opid = lfirst_oid(l);
-       HeapTuple   tup;
-       Form_pg_operator optup;
-
-       tup = SearchSysCache(OPEROID,
-                            ObjectIdGetDatum(opid),
-                            0, 0, 0);
-       if (!HeapTupleIsValid(tup))
-           elog(ERROR, "cache lookup failed for operator %u", opid);
-       optup = (Form_pg_operator) GETSTRUCT(tup);
-       if (!optup->oprcanhash || optup->oprcom != opid ||
-           !func_strict(optup->oprcode))
-       {
-           ReleaseSysCache(tup);
+       if (!hash_ok_operator((OpExpr *) slink->testexpr))
            return false;
+   }
+   else
+   {
+       foreach(l, ((BoolExpr *) slink->testexpr)->args)
+       {
+           Node    *andarg = (Node *) lfirst(l);
+
+           if (!IsA(andarg, OpExpr))
+               return false;   /* probably can't happen */
+           if (!hash_ok_operator((OpExpr *) andarg))
+               return false;
        }
+   }
+
+   return true;
+}
+
+static bool
+hash_ok_operator(OpExpr *expr)
+{
+   Oid         opid = expr->opno;
+   HeapTuple   tup;
+   Form_pg_operator optup;
+
+   tup = SearchSysCache(OPEROID,
+                        ObjectIdGetDatum(opid),
+                        0, 0, 0);
+   if (!HeapTupleIsValid(tup))
+       elog(ERROR, "cache lookup failed for operator %u", opid);
+   optup = (Form_pg_operator) GETSTRUCT(tup);
+   if (!optup->oprcanhash || optup->oprcom != opid ||
+       !func_strict(optup->oprcode))
+   {
        ReleaseSysCache(tup);
+       return false;
    }
+   ReleaseSysCache(tup);
    return true;
 }
 
@@ -659,17 +672,28 @@ convert_IN_to_join(PlannerInfo *root, SubLink *sublink)
    RangeTblEntry *rte;
    RangeTblRef *rtr;
    InClauseInfo *ininfo;
-   List       *exprs;
 
    /*
-    * The sublink type must be "= ANY" --- that is, an IN operator. (We
-    * require the operator name to be unqualified, which may be overly
-    * paranoid, or may not be.)
+    * The sublink type must be "= ANY" --- that is, an IN operator.  We
+    * expect that the test expression will be either a single OpExpr, or an
+    * AND-clause containing OpExprs.  (If it's anything else then the parser
+    * must have determined that the operators have non-equality-like
+    * semantics.  In the OpExpr case we can't be sure what the operator's
+    * semantics are like, and must check for ourselves.)
     */
    if (sublink->subLinkType != ANY_SUBLINK)
        return NULL;
-   if (list_length(sublink->operName) != 1 ||
-       strcmp(strVal(linitial(sublink->operName)), "=") != 0)
+   if (sublink->testexpr && IsA(sublink->testexpr, OpExpr))
+   {
+       List    *opclasses;
+       List    *opstrats;
+
+       get_op_btree_interpretation(((OpExpr *) sublink->testexpr)->opno,
+                                   &opclasses, &opstrats);
+       if (!list_member_int(opstrats, ROWCOMPARE_EQ))
+           return NULL;
+   }
+   else if (!and_clause(sublink->testexpr))
        return NULL;
 
    /*
@@ -683,16 +707,14 @@ convert_IN_to_join(PlannerInfo *root, SubLink *sublink)
     * The left-hand expressions must contain some Vars of the current query,
     * else it's not gonna be a join.
     */
-   left_varnos = pull_varnos((Node *) sublink->lefthand);
+   left_varnos = pull_varnos(sublink->testexpr);
    if (bms_is_empty(left_varnos))
        return NULL;
 
    /*
-    * The left-hand expressions mustn't be volatile.  (Perhaps we should test
-    * the combining operators, too?  We'd only need to point the function
-    * directly at the sublink ...)
+    * The combining operators and left-hand expressions mustn't be volatile.
     */
-   if (contain_volatile_functions((Node *) sublink->lefthand))
+   if (contain_volatile_functions(sublink->testexpr))
        return NULL;
 
    /*
@@ -722,16 +744,13 @@ convert_IN_to_join(PlannerInfo *root, SubLink *sublink)
    root->in_info_list = lappend(root->in_info_list, ininfo);
 
    /*
-    * Build the result qual expressions.  As a side effect,
+    * Build the result qual expression.  As a side effect,
     * ininfo->sub_targetlist is filled with a list of Vars representing the
     * subselect outputs.
     */
-   exprs = convert_sublink_opers(sublink->lefthand,
-                                 sublink->operOids,
-                                 subselect->targetList,
-                                 rtindex,
-                                 &ininfo->sub_targetlist);
-   return (Node *) make_ands_explicit(exprs);
+   return convert_testexpr(sublink->testexpr,
+                           rtindex,
+                           &ininfo->sub_targetlist);
 }
 
 /*
@@ -802,19 +821,18 @@ process_sublinks_mutator(Node *node, bool *isTopQual)
    if (IsA(node, SubLink))
    {
        SubLink    *sublink = (SubLink *) node;
-       List       *lefthand;
+       Node       *testexpr;
 
        /*
         * First, recursively process the lefthand-side expressions, if any.
         */
        locTopQual = false;
-       lefthand = (List *)
-           process_sublinks_mutator((Node *) sublink->lefthand, &locTopQual);
+       testexpr = process_sublinks_mutator(sublink->testexpr, &locTopQual);
 
        /*
         * Now build the SubPlan node and make the expr to return.
         */
-       return make_subplan(sublink, lefthand, *isTopQual);
+       return make_subplan(sublink, testexpr, *isTopQual);
    }
 
    /*
index 2cdb3b357391accb1c8ec94f106b739b067474cc..2b6583c1dad08b5c85004a00db9bc2d0a1ee7f84 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.204 2005/12/20 02:30:36 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.205 2005/12/28 01:30:00 tgl Exp $
  *
  * HISTORY
  *   AUTHOR            DATE            MAJOR EVENT
@@ -540,6 +540,8 @@ expression_returns_set_walker(Node *node, void *context)
        return false;
    if (IsA(node, RowExpr))
        return false;
+   if (IsA(node, RowCompareExpr))
+       return false;
    if (IsA(node, CoalesceExpr))
        return false;
    if (IsA(node, MinMaxExpr))
@@ -651,12 +653,12 @@ contain_mutable_functions_walker(Node *node, void *context)
            return true;
        /* else fall through to check args */
    }
-   if (IsA(node, SubLink))
+   if (IsA(node, RowCompareExpr))
    {
-       SubLink    *sublink = (SubLink *) node;
+       RowCompareExpr *rcexpr = (RowCompareExpr *) node;
        ListCell   *opid;
 
-       foreach(opid, sublink->operOids)
+       foreach(opid, rcexpr->opnos)
        {
            if (op_volatile(lfirst_oid(opid)) != PROVOLATILE_IMMUTABLE)
                return true;
@@ -734,12 +736,13 @@ contain_volatile_functions_walker(Node *node, void *context)
            return true;
        /* else fall through to check args */
    }
-   if (IsA(node, SubLink))
+   if (IsA(node, RowCompareExpr))
    {
-       SubLink    *sublink = (SubLink *) node;
+       /* RowCompare probably can't have volatile ops, but check anyway */
+       RowCompareExpr *rcexpr = (RowCompareExpr *) node;
        ListCell   *opid;
 
-       foreach(opid, sublink->operOids)
+       foreach(opid, rcexpr->opnos)
        {
            if (op_volatile(lfirst_oid(opid)) == PROVOLATILE_VOLATILE)
                return true;
@@ -847,6 +850,8 @@ contain_nonstrict_functions_walker(Node *node, void *context)
        return true;
    if (IsA(node, RowExpr))
        return true;
+   if (IsA(node, RowCompareExpr))
+       return true;
    if (IsA(node, CoalesceExpr))
        return true;
    if (IsA(node, MinMaxExpr))
@@ -2857,8 +2862,8 @@ evaluate_expr(Expr *expr, Oid result_type)
  * FromExpr, JoinExpr, and SetOperationStmt nodes are handled, so that query
  * jointrees and setOperation trees can be processed without additional code.
  *
- * expression_tree_walker will handle SubLink nodes by recursing normally into
- * the "lefthand" arguments (which are expressions belonging to the outer
+ * expression_tree_walker will handle SubLink nodes by recursing normally
+ * into the "testexpr" subtree (which is an expression belonging to the outer
  * plan).  It will also call the walker on the sub-Query node; however, when
  * expression_tree_walker itself is called on a Query node, it does nothing
  * and returns "false".  The net effect is that unless the walker does
@@ -2882,7 +2887,7 @@ evaluate_expr(Expr *expr, Oid result_type)
  * walker on all the expression subtrees of the given Query node.
  *
  * expression_tree_walker will handle SubPlan nodes by recursing normally
- * into the "exprs" and "args" lists (which are expressions belonging to
+ * into the "testexpr" and the "args" list (which are expressions belonging to
  * the outer plan).  It will not touch the completed subplan, however. Since
  * there is no link to the original Query, it is not possible to recurse into
  * subselects of an already-planned expression tree.  This is OK for current
@@ -2992,7 +2997,7 @@ expression_tree_walker(Node *node,
            {
                SubLink    *sublink = (SubLink *) node;
 
-               if (expression_tree_walker((Node *) sublink->lefthand,
+               if (expression_tree_walker(sublink->testexpr,
                                           walker, context))
                    return true;
 
@@ -3007,8 +3012,8 @@ expression_tree_walker(Node *node,
            {
                SubPlan    *subplan = (SubPlan *) node;
 
-               /* recurse into the exprs list, but not into the Plan */
-               if (expression_tree_walker((Node *) subplan->exprs,
+               /* recurse into the testexpr, but not into the Plan */
+               if (expression_tree_walker(subplan->testexpr,
                                           walker, context))
                    return true;
                /* also examine args list */
@@ -3058,6 +3063,16 @@ expression_tree_walker(Node *node,
            return walker(((ArrayExpr *) node)->elements, context);
        case T_RowExpr:
            return walker(((RowExpr *) node)->args, context);
+       case T_RowCompareExpr:
+           {
+               RowCompareExpr *rcexpr = (RowCompareExpr *) node;
+
+               if (walker(rcexpr->largs, context))
+                   return true;
+               if (walker(rcexpr->rargs, context))
+                   return true;
+           }
+           break;
        case T_CoalesceExpr:
            return walker(((CoalesceExpr *) node)->args, context);
        case T_MinMaxExpr:
@@ -3263,7 +3278,7 @@ range_table_walker(List *rtable,
  * and qualifier clauses during the planning stage.
  *
  * expression_tree_mutator will handle SubLink nodes by recursing normally
- * into the "lefthand" arguments (which are expressions belonging to the outer
+ * into the "testexpr" subtree (which is an expression belonging to the outer
  * plan).  It will also call the mutator on the sub-Query node; however, when
  * expression_tree_mutator itself is called on a Query node, it does nothing
  * and returns the unmodified Query node.  The net effect is that unless the
@@ -3272,8 +3287,8 @@ range_table_walker(List *rtable,
  * SubLink node.  Mutators that want to descend into sub-selects will usually
  * do so by recognizing Query nodes and calling query_tree_mutator (below).
  *
- * expression_tree_mutator will handle a SubPlan node by recursing into
- * the "exprs" and "args" lists (which belong to the outer plan), but it
+ * expression_tree_mutator will handle a SubPlan node by recursing into the
+ * "testexpr" and the "args" list (which belong to the outer plan), but it
  * will simply copy the link to the inner plan, since that's typically what
  * expression tree mutators want.  A mutator that wants to modify the subplan
  * can force appropriate behavior by recognizing SubPlan expression nodes
@@ -3404,7 +3419,7 @@ expression_tree_mutator(Node *node,
                SubLink    *newnode;
 
                FLATCOPY(newnode, sublink, SubLink);
-               MUTATE(newnode->lefthand, sublink->lefthand, List *);
+               MUTATE(newnode->testexpr, sublink->testexpr, Node *);
 
                /*
                 * Also invoke the mutator on the sublink's Query node, so it
@@ -3420,8 +3435,8 @@ expression_tree_mutator(Node *node,
                SubPlan    *newnode;
 
                FLATCOPY(newnode, subplan, SubPlan);
-               /* transform exprs list */
-               MUTATE(newnode->exprs, subplan->exprs, List *);
+               /* transform testexpr */
+               MUTATE(newnode->testexpr, subplan->testexpr, Node *);
                /* transform args list (params to be passed to subplan) */
                MUTATE(newnode->args, subplan->args, List *);
                /* but not the sub-Plan itself, which is referenced as-is */
@@ -3513,6 +3528,17 @@ expression_tree_mutator(Node *node,
                return (Node *) newnode;
            }
            break;
+       case T_RowCompareExpr:
+           {
+               RowCompareExpr    *rcexpr = (RowCompareExpr *) node;
+               RowCompareExpr    *newnode;
+
+               FLATCOPY(newnode, rcexpr, RowCompareExpr);
+               MUTATE(newnode->largs, rcexpr->largs, List *);
+               MUTATE(newnode->rargs, rcexpr->rargs, List *);
+               return (Node *) newnode;
+           }
+           break;
        case T_CoalesceExpr:
            {
                CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
index 6c0145e29800af9bf83ce920e10192d2b65451bd..201a972e098ba4cfa02500dae6d35a9fbbbacb79 100644 (file)
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.519 2005/12/27 04:00:07 momjian Exp $
+ *   $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.520 2005/12/28 01:30:00 tgl Exp $
  *
  * HISTORY
  *   AUTHOR            DATE            MAJOR EVENT
@@ -6774,10 +6774,7 @@ a_expr:      c_expr                                  { $$ = $1; }
                        /* generate foo = ANY (subquery) */
                        SubLink *n = (SubLink *) $3;
                        n->subLinkType = ANY_SUBLINK;
-                       if (IsA($1, RowExpr))
-                           n->lefthand = ((RowExpr *) $1)->args;
-                       else
-                           n->lefthand = list_make1($1);
+                       n->testexpr = $1;
                        n->operName = list_make1(makeString("="));
                        $$ = (Node *)n;
                    }
@@ -6796,10 +6793,7 @@ a_expr:      c_expr                                  { $$ = $1; }
                        /* Make an = ANY node */
                        SubLink *n = (SubLink *) $4;
                        n->subLinkType = ANY_SUBLINK;
-                       if (IsA($1, RowExpr))
-                           n->lefthand = ((RowExpr *) $1)->args;
-                       else
-                           n->lefthand = list_make1($1);
+                       n->testexpr = $1;
                        n->operName = list_make1(makeString("="));
                        /* Stick a NOT on top */
                        $$ = (Node *) makeA_Expr(AEXPR_NOT, NIL, NULL, (Node *) n);
@@ -6814,10 +6808,7 @@ a_expr:      c_expr                                  { $$ = $1; }
                {
                    SubLink *n = makeNode(SubLink);
                    n->subLinkType = $3;
-                   if (IsA($1, RowExpr))
-                       n->lefthand = ((RowExpr *) $1)->args;
-                   else
-                       n->lefthand = list_make1($1);
+                   n->testexpr = $1;
                    n->operName = $2;
                    n->subselect = $4;
                    $$ = (Node *)n;
@@ -6950,7 +6941,7 @@ c_expr:       columnref                               { $$ = $1; }
                {
                    SubLink *n = makeNode(SubLink);
                    n->subLinkType = EXPR_SUBLINK;
-                   n->lefthand = NIL;
+                   n->testexpr = NULL;
                    n->operName = NIL;
                    n->subselect = $1;
                    $$ = (Node *)n;
@@ -6959,7 +6950,7 @@ c_expr:       columnref                               { $$ = $1; }
                {
                    SubLink *n = makeNode(SubLink);
                    n->subLinkType = EXISTS_SUBLINK;
-                   n->lefthand = NIL;
+                   n->testexpr = NULL;
                    n->operName = NIL;
                    n->subselect = $2;
                    $$ = (Node *)n;
@@ -6968,7 +6959,7 @@ c_expr:       columnref                               { $$ = $1; }
                {
                    SubLink *n = makeNode(SubLink);
                    n->subLinkType = ARRAY_SUBLINK;
-                   n->lefthand = NIL;
+                   n->testexpr = NULL;
                    n->operName = NIL;
                    n->subselect = $2;
                    $$ = (Node *)n;
index ece78b21820aa95ed25125b685a4ae983d0698ab..923d357ee1794c339a9b84a69fd017efc864ed24 100644 (file)
@@ -8,21 +8,20 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.188 2005/11/28 04:35:31 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.189 2005/12/28 01:30:00 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 #include "postgres.h"
 
-#include "catalog/pg_operator.h"
-#include "catalog/pg_proc.h"
 #include "commands/dbcommands.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/params.h"
 #include "nodes/plannodes.h"
+#include "optimizer/clauses.h"
 #include "parser/analyze.h"
 #include "parser/gramparse.h"
 #include "parser/parse_coerce.h"
@@ -33,7 +32,7 @@
 #include "parser/parse_type.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
-#include "utils/syscache.h"
+
 
 bool       Transform_null_equals = false;
 
@@ -64,8 +63,8 @@ static Node *transformIndirection(ParseState *pstate, Node *basenode,
                     List *indirection);
 static Node *typecast_expression(ParseState *pstate, Node *expr,
                    TypeName *typename);
-static Node *make_row_op(ParseState *pstate, List *opname,
-           RowExpr *lrow, RowExpr *rrow);
+static Node *make_row_comparison_op(ParseState *pstate, List *opname,
+                                   List *largs, List *rargs);
 static Node *make_row_distinct_op(ParseState *pstate, List *opname,
                     RowExpr *lrow, RowExpr *rrow);
 static Expr *make_distinct_op(ParseState *pstate, List *opname,
@@ -592,14 +591,14 @@ transformAExprOp(ParseState *pstate, A_Expr *a)
             ((SubLink *) rexpr)->subLinkType == EXPR_SUBLINK)
    {
        /*
-        * Convert "row op subselect" into a MULTIEXPR sublink. Formerly the
+        * Convert "row op subselect" into a ROWCOMPARE sublink. Formerly the
         * grammar did this, but now that a row construct is allowed anywhere
         * in expressions, it's easier to do it here.
         */
        SubLink    *s = (SubLink *) rexpr;
 
-       s->subLinkType = MULTIEXPR_SUBLINK;
-       s->lefthand = ((RowExpr *) lexpr)->args;
+       s->subLinkType = ROWCOMPARE_SUBLINK;
+       s->testexpr = lexpr;
        s->operName = a->name;
        result = transformExpr(pstate, (Node *) s);
    }
@@ -612,10 +611,10 @@ transformAExprOp(ParseState *pstate, A_Expr *a)
        Assert(IsA(lexpr, RowExpr));
        Assert(IsA(rexpr, RowExpr));
 
-       result = make_row_op(pstate,
-                            a->name,
-                            (RowExpr *) lexpr,
-                            (RowExpr *) rexpr);
+       result = make_row_comparison_op(pstate,
+                                       a->name,
+                                       ((RowExpr *) lexpr)->args,
+                                       ((RowExpr *) rexpr)->args);
    }
    else
    {
@@ -885,10 +884,10 @@ transformAExprIn(ParseState *pstate, A_Expr *a)
                ereport(ERROR,
                        (errcode(ERRCODE_SYNTAX_ERROR),
                         errmsg("arguments of row IN must all be row expressions")));
-           cmp = make_row_op(pstate,
-                             a->name,
-                             (RowExpr *) copyObject(lexpr),
-                             (RowExpr *) rexpr);
+           cmp = make_row_comparison_op(pstate,
+                                        a->name,
+                                        (List *) copyObject(((RowExpr *) lexpr)->args),
+                                        ((RowExpr *) rexpr)->args);
        }
        else
            cmp = (Node *) make_op(pstate,
@@ -1080,13 +1079,11 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
    if (sublink->subLinkType == EXISTS_SUBLINK)
    {
        /*
-        * EXISTS needs no lefthand or combining operator.  These fields
-        * should be NIL already, but make sure.
+        * EXISTS needs no test expression or combining operator.
+        * These fields should be null already, but make sure.
         */
-       sublink->lefthand = NIL;
+       sublink->testexpr = NULL;
        sublink->operName = NIL;
-       sublink->operOids = NIL;
-       sublink->useOr = FALSE;
    }
    else if (sublink->subLinkType == EXPR_SUBLINK ||
             sublink->subLinkType == ARRAY_SUBLINK)
@@ -1111,128 +1108,72 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
        }
 
        /*
-        * EXPR and ARRAY need no lefthand or combining operator. These fields
-        * should be NIL already, but make sure.
+        * EXPR and ARRAY need no test expression or combining operator.
+        * These fields should be null already, but make sure.
         */
-       sublink->lefthand = NIL;
+       sublink->testexpr = NULL;
        sublink->operName = NIL;
-       sublink->operOids = NIL;
-       sublink->useOr = FALSE;
    }
    else
    {
-       /* ALL, ANY, or MULTIEXPR: generate operator list */
-       List       *left_list = sublink->lefthand;
-       List       *right_list = qtree->targetList;
-       int         row_length = list_length(left_list);
-       bool        needNot = false;
-       List       *op = sublink->operName;
-       char       *opname = strVal(llast(op));
+       /* ALL, ANY, or ROWCOMPARE: generate row-comparing expression */
+       Node       *lefthand;
+       List       *left_list;
+       List       *right_list;
        ListCell   *l;
-       ListCell   *ll_item;
-
-       /* transform lefthand expressions */
-       foreach(l, left_list)
-           lfirst(l) = transformExpr(pstate, lfirst(l));
 
        /*
-        * If the expression is "<> ALL" (with unqualified opname) then
-        * convert it to "NOT IN".  This is a hack to improve efficiency of
-        * expressions output by pre-7.4 Postgres.
+        * Transform lefthand expression, and convert to a list
         */
-       if (sublink->subLinkType == ALL_SUBLINK &&
-           list_length(op) == 1 && strcmp(opname, "<>") == 0)
-       {
-           sublink->subLinkType = ANY_SUBLINK;
-           opname = pstrdup("=");
-           op = list_make1(makeString(opname));
-           sublink->operName = op;
-           needNot = true;
-       }
-
-       /* Set useOr if op is "<>" (possibly qualified) */
-       if (strcmp(opname, "<>") == 0)
-           sublink->useOr = TRUE;
+       lefthand = transformExpr(pstate, sublink->testexpr);
+       if (lefthand && IsA(lefthand, RowExpr))
+           left_list = ((RowExpr *) lefthand)->args;
        else
-           sublink->useOr = FALSE;
-
-       /* Combining operators other than =/<> is dubious... */
-       if (row_length != 1 &&
-           strcmp(opname, "=") != 0 &&
-           strcmp(opname, "<>") != 0)
-           ereport(ERROR,
-                   (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                    errmsg("row comparison cannot use operator %s",
-                           opname)));
+           left_list = list_make1(lefthand);
 
        /*
-        * To build the list of combining operator OIDs, we must scan
-        * subquery's targetlist to find values that will be matched against
-        * lefthand values.  We need to ignore resjunk targets, so doing the
-        * outer iteration over right_list is easier than doing it over
-        * left_list.
+        * Build a list of PARAM_SUBLINK nodes representing the
+        * output columns of the subquery.
         */
-       sublink->operOids = NIL;
-
-       ll_item = list_head(left_list);
-       foreach(l, right_list)
+       right_list = NIL;
+       foreach(l, qtree->targetList)
        {
            TargetEntry *tent = (TargetEntry *) lfirst(l);
-           Node       *lexpr;
-           Operator    optup;
-           Form_pg_operator opform;
+           Param      *param;
 
            if (tent->resjunk)
                continue;
 
-           if (ll_item == NULL)
-               ereport(ERROR,
-                       (errcode(ERRCODE_SYNTAX_ERROR),
-                        errmsg("subquery has too many columns")));
-           lexpr = lfirst(ll_item);
-           ll_item = lnext(ll_item);
+           param = makeNode(Param);
+           param->paramkind = PARAM_SUBLINK;
+           param->paramid = (AttrNumber) tent->resno;
+           param->paramtype = exprType((Node *) tent->expr);
 
-           /*
-            * It's OK to use oper() not compatible_oper() here, because
-            * make_subplan() will insert type coercion calls if needed.
-            */
-           optup = oper(op,
-                        exprType(lexpr),
-                        exprType((Node *) tent->expr),
-                        false);
-           opform = (Form_pg_operator) GETSTRUCT(optup);
-
-           if (opform->oprresult != BOOLOID)
-               ereport(ERROR,
-                       (errcode(ERRCODE_DATATYPE_MISMATCH),
-                 errmsg("operator %s must return type boolean, not type %s",
-                        opname,
-                        format_type_be(opform->oprresult)),
-                        errhint("The operator of a quantified predicate subquery must return type boolean.")));
-
-           if (get_func_retset(opform->oprcode))
-               ereport(ERROR,
-                       (errcode(ERRCODE_DATATYPE_MISMATCH),
-                        errmsg("operator %s must not return a set",
-                               opname),
-                        errhint("The operator of a quantified predicate subquery must return type boolean.")));
-
-           sublink->operOids = lappend_oid(sublink->operOids,
-                                           oprid(optup));
-
-           ReleaseSysCache(optup);
+           right_list = lappend(right_list, param);
        }
-       if (ll_item != NULL)
+
+       /*
+        * We could rely on make_row_comparison_op to complain if the
+        * list lengths differ, but we prefer to generate a more specific
+        * error message.
+        */
+       if (list_length(left_list) < list_length(right_list))
+           ereport(ERROR,
+                   (errcode(ERRCODE_SYNTAX_ERROR),
+                    errmsg("subquery has too many columns")));
+       if (list_length(left_list) > list_length(right_list))
            ereport(ERROR,
                    (errcode(ERRCODE_SYNTAX_ERROR),
                     errmsg("subquery has too few columns")));
 
-       if (needNot)
-       {
-           result = coerce_to_boolean(pstate, result, "NOT");
-           result = (Node *) makeBoolExpr(NOT_EXPR,
-                                          list_make1(result));
-       }
+       /*
+        * Identify the combining operator(s) and generate a suitable
+        * row-comparison expression.
+        */
+       sublink->testexpr = make_row_comparison_op(pstate,
+                                                  sublink->operName,
+                                                  left_list,
+                                                  right_list);
    }
 
    return result;
@@ -1673,6 +1614,9 @@ exprType(Node *expr)
        case T_RowExpr:
            type = ((RowExpr *) expr)->row_typeid;
            break;
+       case T_RowCompareExpr:
+           type = BOOLOID;
+           break;
        case T_CoalesceExpr:
            type = ((CoalesceExpr *) expr)->coalescetype;
            break;
@@ -1953,76 +1897,258 @@ typecast_expression(ParseState *pstate, Node *expr, TypeName *typename)
 }
 
 /*
- * Transform a "row op row" construct
+ * Transform a "row compare-op row" construct
  *
- * The input RowExprs are already transformed
+ * The inputs are lists of already-transformed expressions.
+ * As with coerce_type, pstate may be NULL if no special unknown-Param
+ * processing is wanted.
+ *
+ * The output may be a single OpExpr, an AND or OR combination of OpExprs,
+ * or a RowCompareExpr.  In all cases it is guaranteed to return boolean.
+ * The AND, OR, and RowCompareExpr cases further imply things about the
+ * behavior of the operators (ie, they behave as =, <>, or < <= > >=).
  */
 static Node *
-make_row_op(ParseState *pstate, List *opname,
-           RowExpr *lrow, RowExpr *rrow)
+make_row_comparison_op(ParseState *pstate, List *opname,
+                      List *largs, List *rargs)
 {
-   Node       *result = NULL;
-   List       *largs = lrow->args;
-   List       *rargs = rrow->args;
+   RowCompareExpr *rcexpr;
+   RowCompareType rctype;
+   List       *opexprs;
+   List       *opnos;
+   List       *opclasses;
    ListCell   *l,
               *r;
-   char       *oprname;
-   BoolExprType boolop;
-
-   if (list_length(largs) != list_length(rargs))
+   List      **opclass_lists;
+   List      **opstrat_lists;
+   Bitmapset  *strats;
+   int         nopers;
+   int         i;
+
+   nopers = list_length(largs);
+   if (nopers != list_length(rargs))
        ereport(ERROR,
                (errcode(ERRCODE_SYNTAX_ERROR),
                 errmsg("unequal number of entries in row expressions")));
 
    /*
-    * XXX it's really wrong to generate a simple AND combination for < <= >
-    * >=.  We probably need to invent a new runtime node type to handle those
-    * correctly.  For the moment, though, keep on doing this ...
+    * We can't compare zero-length rows because there is no principled
+    * basis for figuring out what the operator is.
     */
-   oprname = strVal(llast(opname));
-
-   if ((strcmp(oprname, "=") == 0) ||
-       (strcmp(oprname, "<") == 0) ||
-       (strcmp(oprname, "<=") == 0) ||
-       (strcmp(oprname, ">") == 0) ||
-       (strcmp(oprname, ">=") == 0))
-       boolop = AND_EXPR;
-   else if (strcmp(oprname, "<>") == 0)
-       boolop = OR_EXPR;
-   else
-   {
+   if (nopers == 0)
        ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                errmsg("operator %s is not supported for row expressions",
-                       oprname)));
-       boolop = 0;             /* keep compiler quiet */
-   }
+                errmsg("cannot compare rows of zero length")));
 
+   /*
+    * Identify all the pairwise operators, using make_op so that
+    * behavior is the same as in the simple scalar case.
+    */
+   opexprs = NIL;
    forboth(l, largs, r, rargs)
    {
        Node       *larg = (Node *) lfirst(l);
        Node       *rarg = (Node *) lfirst(r);
-       Node       *cmp;
+       OpExpr     *cmp;
 
-       cmp = (Node *) make_op(pstate, opname, larg, rarg);
-       cmp = coerce_to_boolean(pstate, cmp, "row comparison");
-       if (result == NULL)
-           result = cmp;
+       cmp = (OpExpr *) make_op(pstate, opname, larg, rarg);
+       Assert(IsA(cmp, OpExpr));
+
+       /*
+        * We don't use coerce_to_boolean here because we insist on the
+        * operator yielding boolean directly, not via coercion.  If it
+        * doesn't yield bool it won't be in any index opclasses...
+        */
+       if (cmp->opresulttype != BOOLOID)
+           ereport(ERROR,
+                   (errcode(ERRCODE_DATATYPE_MISMATCH),
+                    errmsg("row comparison operator must yield type boolean, "
+                           "not type %s",
+                           format_type_be(cmp->opresulttype))));
+       if (expression_returns_set((Node *) cmp))
+           ereport(ERROR,
+                   (errcode(ERRCODE_DATATYPE_MISMATCH),
+                    errmsg("row comparison operator must not return a set")));
+       opexprs = lappend(opexprs, cmp);
+   }
+
+   /*
+    * If rows are length 1, just return the single operator.  In this
+    * case we don't insist on identifying btree semantics for the operator
+    * (but we still require it to return boolean).
+    */
+   if (nopers == 1)
+       return (Node *) linitial(opexprs);
+
+   /*
+    * Now we must determine which row comparison semantics (= <> < <= > >=)
+    * apply to this set of operators.  We look for btree opclasses containing
+    * the operators, and see which interpretations (strategy numbers) exist
+    * for each operator.
+    */
+   opclass_lists = (List **) palloc(nopers * sizeof(List *));
+   opstrat_lists = (List **) palloc(nopers * sizeof(List *));
+   strats = NULL;
+   i = 0;
+   foreach(l, opexprs)
+   {
+       Bitmapset *this_strats;
+       ListCell   *j;
+
+       get_op_btree_interpretation(((OpExpr *) lfirst(l))->opno,
+                                   &opclass_lists[i], &opstrat_lists[i]);
+       /*
+        * convert strategy number list to a Bitmapset to make the intersection
+        * calculation easy.
+        */
+       this_strats = NULL;
+       foreach(j, opstrat_lists[i])
+       {
+           this_strats = bms_add_member(this_strats, lfirst_int(j));
+       }
+       if (i == 0)
+           strats = this_strats;
        else
-           result = (Node *) makeBoolExpr(boolop,
-                                          list_make2(result, cmp));
+           strats = bms_int_members(strats, this_strats);
+       i++;
    }
 
-   if (result == NULL)
+   switch (bms_membership(strats))
+   {
+       case BMS_EMPTY_SET:
+           /* No common interpretation, so fail */
+           ereport(ERROR,
+                   (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                    errmsg("could not determine interpretation of row comparison operator %s",
+                           strVal(llast(opname))),
+                    errhint("Row comparison operators must be associated with btree operator classes.")));
+           rctype = 0;         /* keep compiler quiet */
+           break;
+       case BMS_SINGLETON:
+           /* Simple case: just one possible interpretation */
+           rctype = bms_singleton_member(strats);
+           break;
+       case BMS_MULTIPLE:
+       default:                /* keep compiler quiet */
+           {
+               /*
+                * Prefer the interpretation with the most default opclasses.
+                */
+               int     best_defaults = 0;
+               bool    multiple_best = false;
+               int     this_rctype;
+
+               rctype = 0;     /* keep compiler quiet */
+               while ((this_rctype = bms_first_member(strats)) >= 0)
+               {
+                   int     ndefaults = 0;
+
+                   for (i = 0; i < nopers; i++)
+                   {
+                       forboth(l, opclass_lists[i], r, opstrat_lists[i])
+                       {
+                           Oid     opclass = lfirst_oid(l);
+                           int     opstrat = lfirst_int(r);
+
+                           if (opstrat == this_rctype &&
+                               opclass_is_default(opclass))
+                               ndefaults++;
+                       }
+                   }
+                   if (ndefaults > best_defaults)
+                   {
+                       best_defaults = ndefaults;
+                       rctype = this_rctype;
+                       multiple_best = false;
+                   }
+                   else if (ndefaults == best_defaults)
+                       multiple_best = true;
+               }
+               if (best_defaults == 0 || multiple_best)
+                   ereport(ERROR,
+                           (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                            errmsg("could not determine interpretation of row comparison operator %s",
+                                   strVal(llast(opname))),
+                            errdetail("There are multiple equally-plausible candidates.")));
+               break;
+           }
+   }
+
+   /*
+    * For = and <> cases, we just combine the pairwise operators with
+    * AND or OR respectively.
+    *
+    * Note: this is presently the only place where the parser generates
+    * BoolExpr with more than two arguments.  Should be OK since the
+    * rest of the system thinks BoolExpr is N-argument anyway.
+    */
+   if (rctype == ROWCOMPARE_EQ)
+       return (Node *) makeBoolExpr(AND_EXPR, opexprs);
+   if (rctype == ROWCOMPARE_NE)
+       return (Node *) makeBoolExpr(OR_EXPR, opexprs);
+
+   /*
+    * Otherwise we need to determine exactly which opclass to associate
+    * with each operator.
+    */
+   opclasses = NIL;
+   for (i = 0; i < nopers; i++)
    {
-       /* zero-length rows?  Generate constant TRUE or FALSE */
-       if (boolop == AND_EXPR)
-           result = makeBoolConst(true, false);
+       Oid     best_opclass = 0;
+       int     ndefault = 0;
+       int     nmatch = 0;
+
+       forboth(l, opclass_lists[i], r, opstrat_lists[i])
+       {
+           Oid     opclass = lfirst_oid(l);
+           int     opstrat = lfirst_int(r);
+
+           if (opstrat == rctype)
+           {
+               if (ndefault == 0)
+                   best_opclass = opclass;
+               if (opclass_is_default(opclass))
+                   ndefault++;
+               else
+                   nmatch++;
+           }
+       }
+       if (ndefault == 1 || (ndefault == 0 && nmatch == 1))
+           opclasses = lappend_oid(opclasses, best_opclass);
        else
-           result = makeBoolConst(false, false);
+           ereport(ERROR,
+                   (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                    errmsg("could not determine interpretation of row comparison operator %s",
+                           strVal(llast(opname))),
+                    errdetail("There are multiple equally-plausible candidates.")));
    }
 
-   return result;
+   /*
+    * Now deconstruct the OpExprs and create a RowCompareExpr.
+    *
+    * Note: can't just reuse the passed largs/rargs lists, because of
+    * possibility that make_op inserted coercion operations.
+    */
+   opnos = NIL;
+   largs = NIL;
+   rargs = NIL;
+   foreach(l, opexprs)
+   {
+       OpExpr     *cmp = (OpExpr *) lfirst(l);
+
+       opnos = lappend_oid(opnos, cmp->opno);
+       largs = lappend(largs, linitial(cmp->args));
+       rargs = lappend(rargs, lsecond(cmp->args));
+   }
+
+   rcexpr = makeNode(RowCompareExpr);
+   rcexpr->rctype = rctype;
+   rcexpr->opnos = opnos;
+   rcexpr->opclasses = opclasses;
+   rcexpr->largs = largs;
+   rcexpr->rargs = rargs;
+
+   return (Node *) rcexpr;
 }
 
 /*
index c83f7b4c4c49d30da5e353c61f97b16162247051..22fccc6ce86dff8ae8a1e2a6c3cd874072acb7c0 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/parser/parse_oper.c,v 1.83 2005/11/22 18:17:16 momjian Exp $
+ *   $PostgreSQL: pgsql/src/backend/parser/parse_oper.c,v 1.84 2005/12/28 01:30:00 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -39,6 +39,9 @@ static const char *op_signature_string(List *op, char oprkind,
                    Oid arg1, Oid arg2);
 static void op_error(List *op, char oprkind, Oid arg1, Oid arg2,
         FuncDetailCode fdresult);
+static Expr *make_op_expr(ParseState *pstate, Operator op,
+            Node *ltree, Node *rtree,
+            Oid ltypeId, Oid rtypeId);
 
 
 /*
@@ -942,7 +945,7 @@ make_scalar_array_op(ParseState *pstate, List *opname,
  * As with coerce_type, pstate may be NULL if no special unknown-Param
  * processing is wanted.
  */
-Expr *
+static Expr *
 make_op_expr(ParseState *pstate, Operator op,
             Node *ltree, Node *rtree,
             Oid ltypeId, Oid rtypeId)
index 5a102e5fed566d55f573bf7ae13d4e6d60951643..eaf4f195077130c2f6759869579b6be118b6fc22 100644 (file)
@@ -3,7 +3,7 @@
  *             back to source text
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.210 2005/12/10 19:21:03 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.211 2005/12/28 01:30:00 tgl Exp $
  *
  *   This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -215,7 +215,6 @@ static void printSubscripts(ArrayRef *aref, deparse_context *context);
 static char *generate_relation_name(Oid relid);
 static char *generate_function_name(Oid funcid, int nargs, Oid *argtypes);
 static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
-static void print_operator_name(StringInfo buf, List *opname);
 static text *string_to_text(char *str);
 
 #define only_marker(rte)  ((rte)->inh ? "" : "ONLY ")
@@ -3106,6 +3105,7 @@ get_rule_expr(Node *node, deparse_context *context,
                        break;
                    case PARAM_NUM:
                    case PARAM_EXEC:
+                   case PARAM_SUBLINK:
                        appendStringInfo(buf, "$%d", param->paramid);
                        break;
                    default:
@@ -3514,6 +3514,50 @@ get_rule_expr(Node *node, deparse_context *context,
            }
            break;
 
+       case T_RowCompareExpr:
+           {
+               RowCompareExpr *rcexpr = (RowCompareExpr *) node;
+               ListCell   *arg;
+               char       *sep;
+
+               /*
+                * SQL99 allows "ROW" to be omitted when there is more than
+                * one column, but for simplicity we always print it.
+                */
+               appendStringInfo(buf, "(ROW(");
+               sep = "";
+               foreach(arg, rcexpr->largs)
+               {
+                   Node       *e = (Node *) lfirst(arg);
+
+                   appendStringInfoString(buf, sep);
+                   get_rule_expr(e, context, true);
+                   sep = ", ";
+               }
+               /*
+                * We assume that the name of the first-column operator
+                * will do for all the rest too.  This is definitely
+                * open to failure, eg if some but not all operators
+                * were renamed since the construct was parsed, but there
+                * seems no way to be perfect.
+                */
+               appendStringInfo(buf, ") %s ROW(",
+                        generate_operator_name(linitial_oid(rcexpr->opnos),
+                                       exprType(linitial(rcexpr->largs)),
+                                       exprType(linitial(rcexpr->rargs))));
+               sep = "";
+               foreach(arg, rcexpr->rargs)
+               {
+                   Node       *e = (Node *) lfirst(arg);
+
+                   appendStringInfoString(buf, sep);
+                   get_rule_expr(e, context, true);
+                   sep = ", ";
+               }
+               appendStringInfo(buf, "))");
+           }
+           break;
+
        case T_CoalesceExpr:
            {
                CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
@@ -3967,6 +4011,7 @@ get_sublink_expr(SubLink *sublink, deparse_context *context)
 {
    StringInfo  buf = context->buf;
    Query      *query = (Query *) (sublink->subselect);
+   char       *opname = NULL;
    bool        need_paren;
 
    if (sublink->subLinkType == ARRAY_SUBLINK)
@@ -3974,25 +4019,67 @@ get_sublink_expr(SubLink *sublink, deparse_context *context)
    else
        appendStringInfoChar(buf, '(');
 
-   if (sublink->lefthand != NIL)
+   /*
+    * Note that we print the name of only the first operator, when there
+    * are multiple combining operators.  This is an approximation that
+    * could go wrong in various scenarios (operators in different schemas,
+    * renamed operators, etc) but there is not a whole lot we can do about
+    * it, since the syntax allows only one operator to be shown.
+    */
+   if (sublink->testexpr)
    {
-       need_paren = (list_length(sublink->lefthand) > 1);
-       if (need_paren)
+       if (IsA(sublink->testexpr, OpExpr))
+       {
+           /* single combining operator */
+           OpExpr   *opexpr = (OpExpr *) sublink->testexpr;
+
+           get_rule_expr(linitial(opexpr->args), context, true);
+           opname = generate_operator_name(opexpr->opno,
+                                           exprType(linitial(opexpr->args)),
+                                           exprType(lsecond(opexpr->args)));
+       }
+       else if (IsA(sublink->testexpr, BoolExpr))
+       {
+           /* multiple combining operators, = or <> cases */
+           char       *sep;
+           ListCell   *l;
+
            appendStringInfoChar(buf, '(');
-       get_rule_expr((Node *) sublink->lefthand, context, true);
-       if (need_paren)
+           sep = "";
+           foreach(l, ((BoolExpr *) sublink->testexpr)->args)
+           {
+               OpExpr   *opexpr = (OpExpr *) lfirst(l);
+
+               Assert(IsA(opexpr, OpExpr));
+               appendStringInfoString(buf, sep);
+               get_rule_expr(linitial(opexpr->args), context, true);
+               if (!opname)
+                   opname = generate_operator_name(opexpr->opno,
+                                           exprType(linitial(opexpr->args)),
+                                           exprType(lsecond(opexpr->args)));
+               sep = ", ";
+           }
            appendStringInfoChar(buf, ')');
-       appendStringInfoChar(buf, ' ');
+       }
+       else if (IsA(sublink->testexpr, RowCompareExpr))
+       {
+           /* multiple combining operators, < <= > >= cases */
+           RowCompareExpr *rcexpr = (RowCompareExpr *) sublink->testexpr;
+
+           appendStringInfoChar(buf, '(');
+           get_rule_expr((Node *) rcexpr->largs, context, true);
+           opname = generate_operator_name(linitial_oid(rcexpr->opnos),
+                                           exprType(linitial(rcexpr->largs)),
+                                           exprType(linitial(rcexpr->rargs)));
+           appendStringInfoChar(buf, ')');
+       }
+       else
+           elog(ERROR, "unrecognized testexpr type: %d",
+                (int) nodeTag(sublink->testexpr));
    }
 
    need_paren = true;
 
-   /*
-    * XXX we regurgitate the originally given operator name, with or without
-    * schema qualification.  This is not necessarily 100% right but it's the
-    * best we can do, since the operators actually used might not all be in
-    * the same schema.
-    */
    switch (sublink->subLinkType)
    {
        case EXISTS_SUBLINK:
@@ -4000,27 +4087,18 @@ get_sublink_expr(SubLink *sublink, deparse_context *context)
            break;
 
        case ANY_SUBLINK:
-           if (list_length(sublink->operName) == 1 &&
-               strcmp(strVal(linitial(sublink->operName)), "=") == 0)
-           {
-               /* Represent = ANY as IN */
-               appendStringInfo(buf, "IN ");
-           }
+           if (strcmp(opname, "=") == 0)       /* Represent = ANY as IN */
+               appendStringInfo(buf, " IN ");
            else
-           {
-               print_operator_name(buf, sublink->operName);
-               appendStringInfo(buf, " ANY ");
-           }
+               appendStringInfo(buf, " %s ANY ", opname);
            break;
 
        case ALL_SUBLINK:
-           print_operator_name(buf, sublink->operName);
-           appendStringInfo(buf, " ALL ");
+           appendStringInfo(buf, " %s ALL ", opname);
            break;
 
-       case MULTIEXPR_SUBLINK:
-           print_operator_name(buf, sublink->operName);
-           appendStringInfoChar(buf, ' ');
+       case ROWCOMPARE_SUBLINK:
+           appendStringInfo(buf, " %s ", opname);
            break;
 
        case EXPR_SUBLINK:
@@ -4812,30 +4890,6 @@ generate_operator_name(Oid operid, Oid arg1, Oid arg2)
    return buf.data;
 }
 
-/*
- * Print out a possibly-qualified operator name
- */
-static void
-print_operator_name(StringInfo buf, List *opname)
-{
-   ListCell   *op = list_head(opname);
-   int         nnames = list_length(opname);
-
-   if (nnames == 1)
-       appendStringInfoString(buf, strVal(lfirst(op)));
-   else
-   {
-       appendStringInfo(buf, "OPERATOR(");
-       while (nnames-- > 1)
-       {
-           appendStringInfo(buf, "%s.",
-                            quote_identifier(strVal(lfirst(op))));
-           op = lnext(op);
-       }
-       appendStringInfo(buf, "%s)", strVal(lfirst(op)));
-   }
-}
-
 /*
  * Given a C string, produce a TEXT datum.
  *
index 40dd68065960d9e0dfa420e728236c9741636db9..d470b28f24ff2aaaee2938a8fb5d2fbfc76914f8 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.130 2005/11/17 22:14:53 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.131 2005/12/28 01:30:01 tgl Exp $
  *
  * NOTES
  *   Eventually, the index information should go through here, too.
@@ -183,6 +183,99 @@ get_op_hash_function(Oid opno)
    return InvalidOid;
 }
 
+/*
+ * get_op_btree_interpretation
+ *     Given an operator's OID, find out which btree opclasses it belongs to,
+ *     and what strategy number it has within each one.  The results are
+ *     returned as an OID list and a parallel integer list.
+ *
+ * In addition to the normal btree operators, we consider a <> operator to be
+ * a "member" of an opclass if its negator is the opclass' equality operator.
+ * ROWCOMPARE_NE is returned as the strategy number for this case.
+ */
+void
+get_op_btree_interpretation(Oid opno, List **opclasses, List **opstrats)
+{
+   Oid         lefttype,
+               righttype;
+   CatCList   *catlist;
+   bool        op_negated;
+   int         i;
+
+   *opclasses = NIL;
+   *opstrats = NIL;
+
+   /*
+    * Get the nominal left-hand input type of the operator; we will ignore
+    * opclasses that don't have that as the expected input datatype.  This
+    * is a kluge to avoid being confused by binary-compatible opclasses
+    * (such as text_ops and varchar_ops, which share the same operators).
+    */
+   op_input_types(opno, &lefttype, &righttype);
+   Assert(OidIsValid(lefttype));
+
+   /*
+    * Find all the pg_amop entries containing the operator.
+    */
+   catlist = SearchSysCacheList(AMOPOPID, 1,
+                                ObjectIdGetDatum(opno),
+                                0, 0, 0);
+   /*
+    * If we can't find any opclass containing the op, perhaps it is a
+    * <> operator.  See if it has a negator that is in an opclass.
+    */
+   op_negated = false;
+   if (catlist->n_members == 0)
+   {
+       Oid     op_negator = get_negator(opno);
+
+       if (OidIsValid(op_negator))
+       {
+           op_negated = true;
+           ReleaseSysCacheList(catlist);
+           catlist = SearchSysCacheList(AMOPOPID, 1,
+                                        ObjectIdGetDatum(op_negator),
+                                        0, 0, 0);
+       }
+   }
+
+   /* Now search the opclasses */
+   for (i = 0; i < catlist->n_members; i++)
+   {
+       HeapTuple   op_tuple = &catlist->members[i]->tuple;
+       Form_pg_amop op_form = (Form_pg_amop) GETSTRUCT(op_tuple);
+       Oid         opclass_id;
+       StrategyNumber op_strategy;
+
+       opclass_id = op_form->amopclaid;
+
+       /* must be btree */
+       if (!opclass_is_btree(opclass_id))
+           continue;
+
+       /* must match operator input type exactly */
+       if (get_opclass_input_type(opclass_id) != lefttype)
+           continue;
+
+       /* Get the operator's btree strategy number */
+       op_strategy = (StrategyNumber) op_form->amopstrategy;
+       Assert(op_strategy >= 1 && op_strategy <= 5);
+
+       if (op_negated)
+       {
+           /* Only consider negators that are = */
+           if (op_strategy != BTEqualStrategyNumber)
+               continue;
+           op_strategy = ROWCOMPARE_NE;
+       }
+
+       *opclasses = lappend_oid(*opclasses, opclass_id);
+       *opstrats = lappend_int(*opstrats, op_strategy);
+   }
+
+   ReleaseSysCacheList(catlist);
+}
+
 
 /*             ---------- AMPROC CACHES ----------                      */
 
@@ -433,6 +526,55 @@ opclass_is_hash(Oid opclass)
    return result;
 }
 
+/*
+ * opclass_is_default
+ *
+ *     Returns TRUE iff the specified opclass is the default for its
+ *     index access method and input data type.
+ */
+bool
+opclass_is_default(Oid opclass)
+{
+   HeapTuple   tp;
+   Form_pg_opclass cla_tup;
+   bool        result;
+
+   tp = SearchSysCache(CLAOID,
+                       ObjectIdGetDatum(opclass),
+                       0, 0, 0);
+   if (!HeapTupleIsValid(tp))
+       elog(ERROR, "cache lookup failed for opclass %u", opclass);
+   cla_tup = (Form_pg_opclass) GETSTRUCT(tp);
+
+   result = cla_tup->opcdefault;
+   ReleaseSysCache(tp);
+   return result;
+}
+
+/*
+ * get_opclass_input_type
+ *
+ *     Returns the OID of the datatype the opclass indexes.
+ */
+Oid
+get_opclass_input_type(Oid opclass)
+{
+   HeapTuple   tp;
+   Form_pg_opclass cla_tup;
+   Oid         result;
+
+   tp = SearchSysCache(CLAOID,
+                       ObjectIdGetDatum(opclass),
+                       0, 0, 0);
+   if (!HeapTupleIsValid(tp))
+       elog(ERROR, "cache lookup failed for opclass %u", opclass);
+   cla_tup = (Form_pg_opclass) GETSTRUCT(tp);
+
+   result = cla_tup->opcintype;
+   ReleaseSysCache(tp);
+   return result;
+}
+
 /*             ---------- OPERATOR CACHE ----------                     */
 
 /*
index d2637e37ebb801cc3ff581acbfa172b76e6f8deb..a7bad3dfd932b5f78abf41f5dfabe28005c78528 100644 (file)
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.307 2005/11/17 22:14:54 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.308 2005/12/28 01:30:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 200511171
+#define CATALOG_VERSION_NO 200512271
 
 #endif
index 062985aacf3d608dc9b4db83d999c9dd323279e4..d07dc57297c3f7137602a5bc0f5342688bc260b9 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.146 2005/12/02 20:03:42 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.147 2005/12/28 01:30:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -563,7 +563,7 @@ typedef struct SubPlanState
    ExprState   xprstate;
    EState     *sub_estate;     /* subselect plan has its own EState */
    struct PlanState *planstate;    /* subselect plan's state tree */
-   List       *exprs;          /* states of combining expression(s) */
+   ExprState  *testexpr;       /* state of combining expression */
    List       *args;           /* states of argument expression(s) */
    bool        needShutdown;   /* TRUE = need to shutdown subplan */
    HeapTuple   curTuple;       /* copy of most recent tuple from subplan */
@@ -671,6 +671,18 @@ typedef struct RowExprState
    TupleDesc   tupdesc;        /* descriptor for result tuples */
 } RowExprState;
 
+/* ----------------
+ *     RowCompareExprState node
+ * ----------------
+ */
+typedef struct RowCompareExprState
+{
+   ExprState   xprstate;
+   List       *largs;          /* the left-hand input arguments */
+   List       *rargs;          /* the right-hand input arguments */
+   FmgrInfo   *funcs;          /* array of comparison function info */
+} RowCompareExprState;
+
 /* ----------------
  *     CoalesceExprState node
  * ----------------
index 0d6a4871ac2f32b01aef0fc800aabf1739a03fd4..2edf4155833334a588478ebe797782badf2df5be 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.179 2005/12/20 02:30:36 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.180 2005/12/28 01:30:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -125,6 +125,7 @@ typedef enum NodeTag
    T_CaseTestExpr,
    T_ArrayExpr,
    T_RowExpr,
+   T_RowCompareExpr,
    T_CoalesceExpr,
    T_MinMaxExpr,
    T_NullIfExpr,
@@ -159,6 +160,7 @@ typedef enum NodeTag
    T_CaseWhenState,
    T_ArrayExprState,
    T_RowExprState,
+   T_RowCompareExprState,
    T_CoalesceExprState,
    T_MinMaxExprState,
    T_CoerceToDomainState,
index 58e2fe691fa7c2a7d6e0af55d24ef71f680787ba..c328026b5fbcabd85d3a38c2ad2d34d1f1394b18 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/params.h,v 1.28 2004/12/31 22:03:34 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/params.h,v 1.29 2005/12/28 01:30:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
  * PARAM_EXEC: The parameter is an internal executor parameter.
  *             It has a number contained in the `paramid' field.
  *
+ * PARAM_SUBLINK: The parameter represents an output column of a SubLink
+ *             node's sub-select.  The column number is contained in the
+ *             `paramid' field.  (This type of Param is converted to
+ *             PARAM_EXEC during planning.)
+ *
  * PARAM_INVALID should never appear in a Param node; it's used to mark
  * the end of a ParamListInfo array.
  *
@@ -44,6 +49,7 @@
 #define PARAM_NAMED        11
 #define PARAM_NUM      12
 #define PARAM_EXEC     15
+#define PARAM_SUBLINK  16
 #define PARAM_INVALID  100
 
 
index 40fda441b97cd5de2f55ae8ae3e32d9068f91dc6..481f901892778bca2eed73779e82ab58380ef16d 100644 (file)
@@ -10,7 +10,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.110 2005/12/20 02:30:36 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.111 2005/12/28 01:30:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -158,6 +158,11 @@ typedef struct Const
  *
  *     PARAM_EXEC:  The parameter is an internal executor parameter.
  *             It has a number contained in the `paramid' field.
+ *
+ *     PARAM_SUBLINK:  The parameter represents an output column of a SubLink
+ *             node's sub-select.  The column number is contained in the
+ *             `paramid' field.  (This type of Param is converted to
+ *             PARAM_EXEC during planning.)
  * ----------------
  */
 typedef struct Param
@@ -329,7 +334,7 @@ typedef struct BoolExpr
    List       *args;           /* arguments to this expression */
 } BoolExpr;
 
-/* ----------------
+/*
  * SubLink
  *
  * A SubLink represents a subselect appearing in an expression, and in some
@@ -338,46 +343,42 @@ typedef struct BoolExpr
  * EXISTS_SUBLINK      EXISTS(SELECT ...)
  * ALL_SUBLINK         (lefthand) op ALL (SELECT ...)
  * ANY_SUBLINK         (lefthand) op ANY (SELECT ...)
- * MULTIEXPR_SUBLINK   (lefthand) op (SELECT ...)
+ * ROWCOMPARE_SUBLINK  (lefthand) op (SELECT ...)
  * EXPR_SUBLINK        (SELECT with single targetlist item ...)
  * ARRAY_SUBLINK       ARRAY(SELECT with single targetlist item ...)
- * For ALL, ANY, and MULTIEXPR, the lefthand is a list of expressions of the
- * same length as the subselect's targetlist.  MULTIEXPR will *always* have
+ * For ALL, ANY, and ROWCOMPARE, the lefthand is a list of expressions of the
+ * same length as the subselect's targetlist.  ROWCOMPARE will *always* have
  * a list with more than one entry; if the subselect has just one target
  * then the parser will create an EXPR_SUBLINK instead (and any operator
  * above the subselect will be represented separately).  Note that both
- * MULTIEXPR and EXPR require the subselect to deliver only one row.
+ * ROWCOMPARE and EXPR require the subselect to deliver only one row.
+ * ALL, ANY, and ROWCOMPARE require the combining operators to deliver boolean
+ * results.  ALL and ANY combine the per-row results using AND and OR
+ * semantics respectively.
  * ARRAY requires just one target column, and creates an array of the target
  * column's type using one or more rows resulting from the subselect.
- * ALL, ANY, and MULTIEXPR require the combining operators to deliver boolean
- * results.  These are reduced to one result per row using OR or AND semantics
- * depending on the "useOr" flag.  ALL and ANY combine the per-row results
- * using AND and OR semantics respectively.
  *
  * SubLink is classed as an Expr node, but it is not actually executable;
  * it must be replaced in the expression tree by a SubPlan node during
  * planning.
  *
- * NOTE: in the raw output of gram.y, lefthand contains a list of raw
- * expressions; useOr and operOids are not filled in yet.  Also, subselect
- * is a raw parsetree. During parse analysis, the parser transforms the
- * lefthand expression list using normal expression transformation rules.
- * It fills operOids with the OIDs representing the specific operator(s)
- * to apply to each pair of lefthand and targetlist expressions.
- * And subselect is transformed to a Query.  This is the representation
- * seen in saved rules and in the rewriter.
- *
- * In EXISTS, EXPR, and ARRAY SubLinks, lefthand, operName, and operOids are
- * unused and are always NIL.  useOr is not significant either for these
- * sublink types.
- * ----------------
+ * NOTE: in the raw output of gram.y, testexpr contains just the raw form
+ * of the lefthand expression (if any), and operName is the String name of
+ * the combining operator.  Also, subselect is a raw parsetree.  During parse
+ * analysis, the parser transforms testexpr into a complete boolean expression
+ * that compares the lefthand value(s) to PARAM_SUBLINK nodes representing the
+ * output columns of the subselect.  And subselect is transformed to a Query.
+ * This is the representation seen in saved rules and in the rewriter.
+ *
+ * In EXISTS, EXPR, and ARRAY SubLinks, testexpr and operName are unused and
+ * are always null.
  */
 typedef enum SubLinkType
 {
    EXISTS_SUBLINK,
    ALL_SUBLINK,
    ANY_SUBLINK,
-   MULTIEXPR_SUBLINK,
+   ROWCOMPARE_SUBLINK,
    EXPR_SUBLINK,
    ARRAY_SUBLINK
 } SubLinkType;
@@ -386,12 +387,9 @@ typedef enum SubLinkType
 typedef struct SubLink
 {
    Expr        xpr;
-   SubLinkType subLinkType;    /* EXISTS, ALL, ANY, MULTIEXPR, EXPR */
-   bool        useOr;          /* TRUE to combine column results with "OR"
-                                * not "AND" */
-   List       *lefthand;       /* list of outer-query expressions on the left */
+   SubLinkType subLinkType;    /* see above */
+   Node       *testexpr;       /* outer-query test for ALL/ANY/ROWCOMPARE */
    List       *operName;       /* originally specified operator name */
-   List       *operOids;       /* OIDs of actual combining operators */
    Node       *subselect;      /* subselect as Query* or parsetree */
 } SubLink;
 
@@ -402,14 +400,18 @@ typedef struct SubLink
  * nodes after it has finished planning the subquery.  SubPlan contains
  * a sub-plantree and rtable instead of a sub-Query.
  *
- * In an ordinary subplan, "exprs" points to a list of executable expressions
- * (OpExpr trees) for the combining operators; their left-hand arguments are
- * the original lefthand expressions, and their right-hand arguments are
- * PARAM_EXEC Param nodes representing the outputs of the sub-select.
- * (NOTE: runtime coercion functions may be inserted as well.) But if the
- * sub-select becomes an initplan rather than a subplan, these executable
- * expressions are part of the outer plan's expression tree (and the SubPlan
- * node itself is not).  In this case "exprs" is NIL to avoid duplication.
+ * In an ordinary subplan, testexpr points to an executable expression
+ * (OpExpr, an AND/OR tree of OpExprs, or RowCompareExpr) for the combining
+ * operator(s); the left-hand arguments are the original lefthand expressions,
+ * and the right-hand arguments are PARAM_EXEC Param nodes representing the
+ * outputs of the sub-select.  (NOTE: runtime coercion functions may be
+ * inserted as well.)  This is just the same expression tree as testexpr in
+ * the original SubLink node, but the PARAM_SUBLINK nodes are replaced by
+ * suitably numbered PARAM_EXEC nodes.
+ *
+ * If the sub-select becomes an initplan rather than a subplan, the executable
+ * expression is part of the outer plan's expression tree (and the SubPlan
+ * node itself is not).  In this case testexpr is NULL to avoid duplication.
  *
  * The planner also derives lists of the values that need to be passed into
  * and out of the subplan. Input values are represented as a list "args" of
@@ -426,13 +428,10 @@ typedef struct SubPlan
 {
    Expr        xpr;
    /* Fields copied from original SubLink: */
-   SubLinkType subLinkType;    /* EXISTS, ALL, ANY, MULTIEXPR, EXPR */
-   bool        useOr;          /* TRUE to combine column results with "OR"
-                                * not "AND" */
-   /* The combining operators, transformed to executable expressions: */
-   List       *exprs;          /* list of OpExpr expression trees */
+   SubLinkType subLinkType;    /* see above */
+   /* The combining operators, transformed to an executable expression: */
+   Node       *testexpr;       /* OpExpr or RowCompareExpr expression tree */
    List       *paramIds;       /* IDs of Params embedded in the above */
-   /* Note: paramIds has a one-to-one correspondence to the exprs list */
    /* The subselect, transformed to a Plan: */
    struct Plan *plan;          /* subselect plan itself */
    int         plan_id;        /* dummy thing because of we haven't equal
@@ -642,6 +641,41 @@ typedef struct RowExpr
    CoercionForm row_format;    /* how to display this node */
 } RowExpr;
 
+/*
+ * RowCompareExpr - row-wise comparison, such as (a, b) <= (1, 2)
+ *
+ * We support row comparison for any operator that can be determined to
+ * act like =, <>, <, <=, >, or >= (we determine this by looking for the
+ * operator in btree opclasses).  Note that the same operator name might
+ * map to a different operator for each pair of row elements, since the
+ * element datatypes can vary.
+ *
+ * A RowCompareExpr node is only generated for the < <= > >= cases;
+ * the = and <> cases are translated to simple AND or OR combinations
+ * of the pairwise comparisons.  However, we include = and <> in the
+ * RowCompareType enum for the convenience of parser logic.
+ */
+typedef enum RowCompareType
+{
+   /* Values of this enum are chosen to match btree strategy numbers */
+   ROWCOMPARE_LT = 1,          /* BTLessStrategyNumber */
+   ROWCOMPARE_LE = 2,          /* BTLessEqualStrategyNumber */
+   ROWCOMPARE_EQ = 3,          /* BTEqualStrategyNumber */
+   ROWCOMPARE_GE = 4,          /* BTGreaterEqualStrategyNumber */
+   ROWCOMPARE_GT = 5,          /* BTGreaterStrategyNumber */
+   ROWCOMPARE_NE = 6           /* no such btree strategy */
+} RowCompareType;
+
+typedef struct RowCompareExpr
+{
+   Expr        xpr;
+   RowCompareType rctype;      /* LT LE GE or GT, never EQ or NE */
+   List       *opnos;          /* OID list of pairwise comparison ops */
+   List       *opclasses;      /* OID list of containing operator classes */
+   List       *largs;          /* the left-hand input arguments */
+   List       *rargs;          /* the right-hand input arguments */
+} RowCompareExpr;
+
 /*
  * CoalesceExpr - a COALESCE expression
  */
index 93e15d5108a3fdfccb1157e52c7ddb0a6861dc33..86cb4787890f453204b752d436d8391fe44b9207 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/parser/parse_oper.h,v 1.36 2004/12/31 22:03:38 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/parser/parse_oper.h,v 1.37 2005/12/28 01:30:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -59,8 +59,5 @@ extern Expr *make_op(ParseState *pstate, List *opname,
 extern Expr *make_scalar_array_op(ParseState *pstate, List *opname,
                     bool useOr,
                     Node *ltree, Node *rtree);
-extern Expr *make_op_expr(ParseState *pstate, Operator op,
-            Node *ltree, Node *rtree,
-            Oid ltypeId, Oid rtypeId);
 
 #endif   /* PARSE_OPER_H */
index bc3d0b4430a8f8df26753e7a2c874337416cac09..9ba878b2ea0568fe09e84b473d41ae789d368ce2 100644 (file)
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.101 2005/10/15 02:49:46 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.102 2005/12/28 01:30:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -31,6 +31,8 @@ extern void get_op_opclass_properties(Oid opno, Oid opclass,
                          bool *recheck);
 extern Oid get_opclass_member(Oid opclass, Oid subtype, int16 strategy);
 extern Oid get_op_hash_function(Oid opno);
+extern void get_op_btree_interpretation(Oid opno,
+                                       List **opclasses, List **opstrats);
 extern Oid get_opclass_proc(Oid opclass, Oid subtype, int16 procnum);
 extern char *get_attname(Oid relid, AttrNumber attnum);
 extern char *get_relid_attribute_name(Oid relid, AttrNumber attnum);
@@ -41,6 +43,8 @@ extern void get_atttypetypmod(Oid relid, AttrNumber attnum,
                  Oid *typid, int32 *typmod);
 extern bool opclass_is_btree(Oid opclass);
 extern bool opclass_is_hash(Oid opclass);
+extern bool opclass_is_default(Oid opclass);
+extern Oid get_opclass_input_type(Oid opclass);
 extern RegProcedure get_opcode(Oid opno);
 extern char *get_opname(Oid opno);
 extern void op_input_types(Oid opno, Oid *lefttype, Oid *righttype);
index 11b0bc0eb3f2fb140eed0f92c3bc7ba5dd1eb8c2..15c6085569c0c58d2d767bacd471b1e8dc6da27f 100644 (file)
@@ -3,7 +3,7 @@
  *           procedural language
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.157 2005/11/22 18:17:33 momjian Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.158 2005/12/28 01:30:01 tgl Exp $
  *
  *   This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -4283,6 +4283,18 @@ exec_simple_check_node(Node *node)
                return TRUE;
            }
 
+       case T_RowCompareExpr:
+           {
+               RowCompareExpr    *expr = (RowCompareExpr *) node;
+
+               if (!exec_simple_check_node((Node *) expr->largs))
+                   return FALSE;
+               if (!exec_simple_check_node((Node *) expr->rargs))
+                   return FALSE;
+
+               return TRUE;
+           }
+
        case T_CoalesceExpr:
            {
                CoalesceExpr *expr = (CoalesceExpr *) node;
index ce3bd80db7536595a91509466fecd8c7f2551144..f0b4791df45b7b83f937bd249b6ab85ed1f38818 100644 (file)
@@ -117,3 +117,125 @@ select (fn).first, substr((fn).last, 1, 20), length((fn).last) from people;
  Jim   | abcdefghijklabcdefgh | 1200000
 (2 rows)
 
+-- Test row comparison semantics.  Prior to PG 8.2 we did this in a totally
+-- non-spec-compliant way.
+select ROW(1,2) < ROW(1,3) as true;
+ true 
+------
+ t
+(1 row)
+
+select ROW(1,2) < ROW(1,1) as false;
+ false 
+-------
+ f
+(1 row)
+
+select ROW(1,2) < ROW(1,NULL) as null;
+ null 
+------
+(1 row)
+
+select ROW(1,2,3) < ROW(1,3,NULL) as true; -- the NULL is not examined
+ true 
+------
+ t
+(1 row)
+
+select ROW(11,'ABC') < ROW(11,'DEF') as true;
+ true 
+------
+ t
+(1 row)
+
+select ROW(11,'ABC') > ROW(11,'DEF') as false;
+ false 
+-------
+ f
+(1 row)
+
+select ROW(12,'ABC') > ROW(11,'DEF') as true;
+ true 
+------
+ t
+(1 row)
+
+-- = and <> have different NULL-behavior than < etc
+select ROW(1,2,3) < ROW(1,NULL,4) as null;
+ null 
+------
+(1 row)
+
+select ROW(1,2,3) = ROW(1,NULL,4) as false;
+ false 
+-------
+ f
+(1 row)
+
+select ROW(1,2,3) <> ROW(1,NULL,4) as true;
+ true 
+------
+ t
+(1 row)
+
+-- We allow operators beyond the six standard ones, if they have btree
+-- operator classes.
+select ROW('ABC','DEF') ~<=~ ROW('DEF','ABC') as true;
+ true 
+------
+ t
+(1 row)
+
+select ROW('ABC','DEF') ~>=~ ROW('DEF','ABC') as false;
+ false 
+-------
+ f
+(1 row)
+
+select ROW('ABC','DEF') ~~ ROW('DEF','ABC') as fail;
+ERROR:  could not determine interpretation of row comparison operator ~~
+HINT:  Row comparison operators must be associated with btree operator classes.
+-- Check row comparison with a subselect
+select unique1, unique2 from tenk1
+where (unique1, unique2) < any (select ten, ten from tenk1 where hundred < 3);
+ unique1 | unique2 
+---------+---------
+       1 |    2838
+       0 |    9998
+(2 rows)
+
+-- Also check row comparison with an indexable condition
+select thousand, tenthous from tenk1
+where (thousand, tenthous) >= (997, 5000)
+order by thousand, tenthous;
+ thousand | tenthous 
+----------+----------
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+(25 rows)
+
index d09ff662abcac7ea073a6374c39c6af2ccc8c3fe..613c4e91f9180c84f516bab122ca3661e462647e 100644 (file)
@@ -72,3 +72,35 @@ insert into pp values (repeat('abcdefghijkl', 100000));
 insert into people select ('Jim', f1, null)::fullname, current_date from pp;
 
 select (fn).first, substr((fn).last, 1, 20), length((fn).last) from people;
+
+-- Test row comparison semantics.  Prior to PG 8.2 we did this in a totally
+-- non-spec-compliant way.
+
+select ROW(1,2) < ROW(1,3) as true;
+select ROW(1,2) < ROW(1,1) as false;
+select ROW(1,2) < ROW(1,NULL) as null;
+select ROW(1,2,3) < ROW(1,3,NULL) as true; -- the NULL is not examined
+select ROW(11,'ABC') < ROW(11,'DEF') as true;
+select ROW(11,'ABC') > ROW(11,'DEF') as false;
+select ROW(12,'ABC') > ROW(11,'DEF') as true;
+
+-- = and <> have different NULL-behavior than < etc
+select ROW(1,2,3) < ROW(1,NULL,4) as null;
+select ROW(1,2,3) = ROW(1,NULL,4) as false;
+select ROW(1,2,3) <> ROW(1,NULL,4) as true;
+
+-- We allow operators beyond the six standard ones, if they have btree
+-- operator classes.
+select ROW('ABC','DEF') ~<=~ ROW('DEF','ABC') as true;
+select ROW('ABC','DEF') ~>=~ ROW('DEF','ABC') as false;
+select ROW('ABC','DEF') ~~ ROW('DEF','ABC') as fail;
+
+-- Check row comparison with a subselect
+select unique1, unique2 from tenk1
+where (unique1, unique2) < any (select ten, ten from tenk1 where hundred < 3);
+
+-- Also check row comparison with an indexable condition
+select thousand, tenthous from tenk1
+where (thousand, tenthous) >= (997, 5000)
+order by thousand, tenthous;
+