Implement SQL-compliant treatment of row comparisons for < <= > >= cases
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 28 Dec 2005 01:30:02 +0000 (01:30 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
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 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.300 2005/12/21 23:22:55 momjian Exp $
+$PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.301 2005/12/28 01:29:58 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -350,18 +350,18 @@ PostgreSQL documentation
     </indexterm>
     The ordinary comparison operators yield null (signifying <quote>unknown</>)
     when either input is null.  Another way to do comparisons is with the
-    <literal>IS DISTINCT FROM</literal> construct:
+    <literal>IS <optional> NOT </> DISTINCT FROM</literal> construct:
 <synopsis>
 <replaceable>expression</replaceable> IS DISTINCT FROM <replaceable>expression</replaceable>
 <replaceable>expression</replaceable> IS NOT DISTINCT FROM <replaceable>expression</replaceable>
 </synopsis>
-    For non-null inputs, <literal>IS DISTINCT FROM</literal> this is
+    For non-null inputs, <literal>IS DISTINCT FROM</literal> is
     the same as the <literal>&lt;&gt;</> operator.  However, when both
     inputs are null it will return false, and when just one input is
     null it will return true.  Similarly, <literal>IS NOT DISTINCT
     FROM</literal> is identical to <literal>=</literal> 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 <quote>unknown</>.
    </para>
 
@@ -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 <token>IN</token> is null.
+   If all the per-row results are either unequal or null, with at least one
+   null, then the result of <token>IN</token> is null.
   </para>
   </sect2>
 
@@ -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 <token>NOT IN</token> is null.
+   If all the per-row results are either unequal or null, with at least one
+   null, then the result of <token>NOT IN</token> is null.
   </para>
   </sect2>
 
@@ -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 <replaceable>operator</replaceable>.  Presently,
-   only <literal>=</literal> and <literal>&lt;&gt;</literal> operators are allowed
-   in row-wise <token>ANY</token> constructs.
-   The result of <token>ANY</token> is <quote>true</> if any equal or unequal row is
-   found, respectively.
-   The result is <quote>false</> if no such row is found (including the special
-   case where the subquery returns no rows).
+   using the given <replaceable>operator</replaceable>.
+   The result of <token>ANY</token> is <quote>true</> if the comparison
+   returns true for any subquery row.
+   The result is <quote>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.
   </para>
 
   <para>
-   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 <token>ANY</token>
-   cannot be false; it will be true or null. 
+   See <xref linkend="row-wise-comparison"> for details about the meaning
+   of a row-wise comparison.
   </para>
   </sect2>
 
@@ -8145,20 +8141,14 @@ SELECT col1 FROM tab1
    The result of <token>ALL</token> is <quote>true</> if all rows yield true
    (including the special case where the subquery returns no rows).
    The result is <quote>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.
   </para>
 
   <para>
    <token>NOT IN</token> is equivalent to <literal>&lt;&gt; ALL</literal>.
   </para>
 
-  <para>
-   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 <token>ALL</token> construct
-   will be null, not true.
-   This is in accordance with SQL's normal rules for Boolean combinations
-   of null values.
-  </para>
-
   <para>
    As with <token>EXISTS</token>, 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 <replaceable>operator</replaceable>.  Presently,
-   only <literal>=</literal> and <literal>&lt;&gt;</literal> operators are allowed
-   in row-wise <token>ALL</token> queries.
-   The result of <token>ALL</token> is <quote>true</> if all subquery rows are equal
-   or unequal, respectively (including the special
+   using the given <replaceable>operator</replaceable>.
+   The result of <token>ALL</token> is <quote>true</> if the comparison
+   returns true for all subquery rows (including the special
    case where the subquery returns no rows).
-   The result is <quote>false</> if any row is found to be unequal or equal,
-   respectively.
+   The result is <quote>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.
   </para>
 
   <para>
-   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 <token>ALL</token>
-   cannot be true; it will be false or null. 
+   See <xref linkend="row-wise-comparison"> for details about the meaning
+   of a row-wise comparison.
   </para>
   </sect2>
 
@@ -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 <literal>=</literal> and <literal>&lt;&gt;</literal> operators are allowed
-   in row-wise comparisons.
-   The result is <quote>true</> if the two rows are equal or unequal, respectively.
   </para>
 
   <para>
-   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 <xref linkend="row-wise-comparison"> for details about the meaning
+   of a row-wise comparison.
   </para>
   </sect2>
  </sect1>
@@ -8255,6 +8234,10 @@ SELECT col1 FROM tab1
    <primary>SOME</primary>
   </indexterm>
 
+  <indexterm>
+   <primary>row-wise comparison</primary>
+  </indexterm>
+
   <indexterm>
    <primary>comparison</primary>
    <secondary>row-wise</secondary>
@@ -8264,6 +8247,10 @@ SELECT col1 FROM tab1
    <primary>IS DISTINCT FROM</primary>
   </indexterm>
 
+  <indexterm>
+   <primary>IS NOT DISTINCT FROM</primary>
+  </indexterm>
+
   <indexterm>
    <primary>IS NULL</primary>
   </indexterm>
@@ -8288,7 +8275,7 @@ SELECT col1 FROM tab1
    <title><literal>IN</literal></title>
 
 <synopsis>
-<replaceable>expression</replaceable> IN (<replaceable>value</replaceable><optional>, ...</optional>)
+<replaceable>expression</replaceable> IN (<replaceable>value</replaceable> <optional>, ...</optional>)
 </synopsis>
 
   <para>
@@ -8319,7 +8306,7 @@ OR
    <title><literal>NOT IN</literal></title>
 
 <synopsis>
-<replaceable>expression</replaceable> NOT IN (<replaceable>value</replaceable><optional>, ...</optional>)
+<replaceable>expression</replaceable> NOT IN (<replaceable>value</replaceable> <optional>, ...</optional>)
 </synopsis>
 
   <para>
@@ -8425,7 +8412,7 @@ AND
   </para>
   </sect2>
 
-  <sect2>
+  <sect2 id="row-wise-comparison">
    <title>Row-wise Comparison</title>
 
 <synopsis>
@@ -8436,23 +8423,52 @@ AND
    Each side is a row constructor,
    as described in <xref linkend="sql-syntax-row-constructors">.
    The two row values must have the same number of fields.
-   Each side is evaluated and they are compared row-wise.
-   Presently, only <literal>=</literal> and <literal>&lt;&gt;</literal> operators are allowed
-   in row-wise comparisons.
-   The result is <quote>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 <replaceable>operator</replaceable> is
+   <literal>=</>,
+   <literal>&lt;&gt;</>,
+   <literal>&lt;</>,
+   <literal>&lt;=</>,
+   <literal>&gt;</> or
+   <literal>&gt;=</>,
+   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 <literal>=</> member of a btree operator
+   class.)
   </para>
 
   <para>
-   As usual, null values in the rows are combined per
-   the normal rules of SQL Boolean expressions.  Two rows are considered
+   The <literal>=</> and <literal>&lt;&gt;</> 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).
   </para>
 
-  <indexterm>
-   <primary>IS DISTINCT FROM</primary>
-  </indexterm>
+  <para>
+   For the <literal>&lt;</>, <literal>&lt;=</>, <literal>&gt;</> and
+   <literal>&gt;=</> 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,
+   <literal>ROW(1,2,NULL) &lt; ROW(1,3,0)</>
+   yields true, not null, because the third pair of elements are not
+   considered.
+  </para>
+
+  <note>
+   <para>
+    Prior to <productname>PostgreSQL</productname> 8.2, the
+    <literal>&lt;</>, <literal>&lt;=</>, <literal>&gt;</> and <literal>&gt;=</>
+    cases were not handled per SQL specification.  A comparison like
+    <literal>ROW(a,b) &lt; ROW(c,d)</>
+    was implemented as
+    <literal>a &lt; c AND b &lt; d</>
+    whereas the correct behavior is equivalent to
+    <literal>a &lt; c OR (a = c AND b &lt; d)</>.
+   </para>
+  </note>
 
 <synopsis>
 <replaceable>row_constructor</replaceable> IS DISTINCT FROM <replaceable>row_constructor</replaceable>
@@ -8466,6 +8482,18 @@ AND
    be either true or false, never null.
   </para>
 
+<synopsis>
+<replaceable>row_constructor</replaceable> IS NOT DISTINCT FROM <replaceable>row_constructor</replaceable>
+</synopsis>
+
+  <para>
+   This construct is similar to a <literal>=</literal> 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.
+  </para>
+
 <synopsis>
 <replaceable>row_constructor</replaceable> IS NULL
 <replaceable>row_constructor</replaceable> 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;
+