diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index df677df37a227432d47e8dd32fe0ba18d2d28aa3..e15a44570b1acdbe252b906e647096c8db1bc81e 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/func.sgml,v 1.157 2003/06/27 00:33:25 tgl Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/func.sgml,v 1.158 2003/06/29 00:33:42 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -7457,52 +7457,14 @@ SELECT col1 FROM tab1
   </sect2>
 
   <sect2>
-   <title><literal>IN</literal> (scalar form)</title>
-
-<synopsis>
-<replaceable>expression</replaceable> IN (<replaceable>value</replaceable><optional>, ...</optional>)
-</synopsis>
-
-  <para>
-   The right-hand side of this form of <token>IN</token> is a parenthesized list
-   of scalar expressions.  The result is <quote>true</> if the left-hand expression's
-   result is equal to any of the right-hand expressions.  This is a shorthand
-   notation for
-
-<synopsis>
-<replaceable>expression</replaceable> = <replaceable>value1</replaceable>
-OR
-<replaceable>expression</replaceable> = <replaceable>value2</replaceable>
-OR
-...
-</synopsis>
-  </para>
-
-  <para>
-   Note that if the left-hand expression yields null, or if there are
-   no equal right-hand values and at least one right-hand expression yields
-   null, the result of the <token>IN</token> construct will be null, not false.
-   This is in accordance with SQL's normal rules for Boolean combinations
-   of null values.
-  </para>
-
-  <note>
-  <para>
-   This form of <token>IN</token> is not truly a subquery expression, but it
-   seems best to document it in the same place as subquery <token>IN</token>.
-  </para>
-  </note>
-  </sect2>
-
-  <sect2>
-   <title><literal>IN</literal> (subquery form)</title>
+   <title><literal>IN</literal></title>
 
 <synopsis>
 <replaceable>expression</replaceable> IN (<replaceable>subquery</replaceable>)
 </synopsis>
 
   <para>
-   The right-hand side of this form of <token>IN</token> is a parenthesized
+   The right-hand side is a parenthesized
    subquery, which must return exactly one column.  The left-hand expression
    is evaluated and compared to each row of the subquery result.
    The result of <token>IN</token> is <quote>true</> if any equal subquery row is found.
@@ -7538,7 +7500,7 @@ OR
   </para>
 
   <para>
-   As usual, null values in the expressions or subquery rows are combined per
+   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;
@@ -7549,55 +7511,14 @@ OR
   </sect2>
 
   <sect2>
-   <title><literal>NOT IN</literal> (scalar form)</title>
-
-<synopsis>
-<replaceable>expression</replaceable> NOT IN (<replaceable>value</replaceable><optional>, ...</optional>)
-</synopsis>
-
-  <para>
-   The right-hand side of this form of <token>NOT IN</token> is a parenthesized list
-   of scalar expressions.  The result is <quote>true</quote> if the left-hand expression's
-   result is unequal to all of the right-hand expressions.  This is a shorthand
-   notation for
-
-<synopsis>
-<replaceable>expression</replaceable> &lt;&gt; <replaceable>value1</replaceable>
-AND
-<replaceable>expression</replaceable> &lt;&gt; <replaceable>value2</replaceable>
-AND
-...
-</synopsis>
-  </para>
-
-  <para>
-   Note that if the left-hand expression yields null, or if there are
-   no equal right-hand values and at least one right-hand expression yields
-   null, the result of the <token>NOT IN</token> construct will be null, not true
-   as one might naively expect.
-   This is in accordance with SQL's normal rules for Boolean combinations
-   of null values.
-  </para>
-
-  <tip>
-  <para>
-   <literal>x NOT IN y</literal> is equivalent to <literal>NOT (x IN y)</literal> in all
-   cases.  However, null values are much more likely to trip up the novice when
-   working with <token>NOT IN</token> than when working with <token>IN</token>.
-   It's best to express your condition positively if possible.
-  </para>
-  </tip>
-  </sect2>
-
-  <sect2>
-   <title><literal>NOT IN </literal>(subquery form)</title>
+   <title><literal>NOT IN </literal></title>
 
 <synopsis>
 <replaceable>expression</replaceable> NOT IN (<replaceable>subquery</replaceable>)
 </synopsis>
 
   <para>
-   The right-hand side of this form of <token>NOT IN</token> is a parenthesized
+   The right-hand side is a parenthesized
    subquery, which must return exactly one column.  The left-hand expression
    is evaluated and compared to each row of the subquery result.
    The result of <token>NOT IN</token> is <quote>true</> if only unequal subquery rows
@@ -7633,7 +7554,7 @@ AND
   </para>
 
   <para>
-   As usual, null values in the expressions or subquery rows are combined per
+   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;
@@ -7652,7 +7573,7 @@ AND
 </synopsis>
 
   <para>
-   The right-hand side of this form of <token>ANY</token> is a parenthesized
+   The right-hand side is a parenthesized
    subquery, which must return exactly one column.  The left-hand expression
    is evaluated and compared to each row of the subquery result using the
    given <replaceable>operator</replaceable>, which must yield a Boolean
@@ -7700,7 +7621,7 @@ AND
   </para>
 
   <para>
-   As usual, null values in the expressions or subquery rows are combined per
+   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;
@@ -7718,7 +7639,7 @@ AND
 </synopsis>
 
   <para>
-   The right-hand side of this form of <token>ALL</token> is a parenthesized
+   The right-hand side is a parenthesized
    subquery, which must return exactly one column.  The left-hand expression
    is evaluated and compared to each row of the subquery result using the
    given <replaceable>operator</replaceable>, which must yield a Boolean
@@ -7765,7 +7686,7 @@ AND
   </para>
 
   <para>
-   As usual, null values in the expressions or subquery rows are combined per
+   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;
@@ -7780,24 +7701,201 @@ AND
 
 <synopsis>
 (<replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ...</optional>) <replaceable>operator</replaceable> (<replaceable>subquery</replaceable>)
-(<replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ...</optional>) <replaceable>operator</replaceable> (<replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ...</optional>)
 </synopsis>
 
   <para>
-   The left-hand side is a list of scalar expressions.  The right-hand side
-   can be either a list of scalar expressions of the same length, or a
-   parenthesized subquery, which must return exactly as many columns as there
+   The left-hand side is a list of scalar expressions.  The right-hand side is
+   a parenthesized subquery, which must return exactly as many columns as there
    are expressions on the left-hand side.  Furthermore, 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, or to the right-hand expression list.
+   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).
+  </para>
+  </sect2>
+ </sect1>
+
+
+ <sect1 id="functions-comparisons">
+  <title>Row and Array Comparisons</title>
+
+  <indexterm>
+   <primary>in</primary>
+  </indexterm>
+
+  <indexterm>
+   <primary>not in</primary>
+  </indexterm>
+
+  <indexterm>
+   <primary>any</primary>
+  </indexterm>
+
+  <indexterm>
+   <primary>all</primary>
+  </indexterm>
+
+  <indexterm>
+   <primary>some</primary>
+  </indexterm>
+
+  <para>
+   This section describes several specialized constructs for making
+   multiple comparisons between groups of values.  These forms are
+   syntactically related to the subquery forms of the previous section,
+   but do not involve subqueries.
+   The forms involving array subexpressions are
+   <productname>PostgreSQL</productname> extensions; the rest are
+   <acronym>SQL</acronym>-compliant.
+   All of the expression forms documented in this section return
+   Boolean (true/false) results.
+  </para>
+
+  <sect2>
+   <title><literal>IN</literal></title>
+
+<synopsis>
+<replaceable>expression</replaceable> IN (<replaceable>value</replaceable><optional>, ...</optional>)
+</synopsis>
+
+  <para>
+   The right-hand side is a parenthesized list
+   of scalar expressions.  The result is <quote>true</> if the left-hand expression's
+   result is equal to any of the right-hand expressions.  This is a shorthand
+   notation for
+
+<synopsis>
+<replaceable>expression</replaceable> = <replaceable>value1</replaceable>
+OR
+<replaceable>expression</replaceable> = <replaceable>value2</replaceable>
+OR
+...
+</synopsis>
+  </para>
+
+  <para>
+   Note that if the left-hand expression yields null, or if there are
+   no equal right-hand values and at least one right-hand expression yields
+   null, the result of the <token>IN</token> construct will be null, not false.
+   This is in accordance with SQL's normal rules for Boolean combinations
+   of null values.
+  </para>
+  </sect2>
+
+  <sect2>
+   <title><literal>NOT IN</literal></title>
+
+<synopsis>
+<replaceable>expression</replaceable> NOT IN (<replaceable>value</replaceable><optional>, ...</optional>)
+</synopsis>
+
+  <para>
+   The right-hand side is a parenthesized list
+   of scalar expressions.  The result is <quote>true</quote> if the left-hand expression's
+   result is unequal to all of the right-hand expressions.  This is a shorthand
+   notation for
+
+<synopsis>
+<replaceable>expression</replaceable> &lt;&gt; <replaceable>value1</replaceable>
+AND
+<replaceable>expression</replaceable> &lt;&gt; <replaceable>value2</replaceable>
+AND
+...
+</synopsis>
+  </para>
+
+  <para>
+   Note that if the left-hand expression yields null, or if there are
+   no equal right-hand values and at least one right-hand expression yields
+   null, the result of the <token>NOT IN</token> construct will be null, not true
+   as one might naively expect.
+   This is in accordance with SQL's normal rules for Boolean combinations
+   of null values.
+  </para>
+
+  <tip>
+  <para>
+   <literal>x NOT IN y</literal> is equivalent to <literal>NOT (x IN y)</literal> in all
+   cases.  However, null values are much more likely to trip up the novice when
+   working with <token>NOT IN</token> than when working with <token>IN</token>.
+   It's best to express your condition positively if possible.
+  </para>
+  </tip>
+  </sect2>
+
+  <sect2>
+   <title><literal>ANY</literal>/<literal>SOME</literal> (array)</title>
+
+<synopsis>
+<replaceable>expression</replaceable> <replaceable>operator</replaceable> ANY (<replaceable>array expression</replaceable>)
+<replaceable>expression</replaceable> <replaceable>operator</replaceable> SOME (<replaceable>array expression</replaceable>)
+</synopsis>
+
+  <para>
+   The right-hand side is a parenthesized expression, which must yield an
+   array value.
+   The left-hand expression
+   is evaluated and compared to each element of the array using the
+   given <replaceable>operator</replaceable>, which must yield a Boolean
+   result.
+   The result of <token>ANY</token> is <quote>true</> if any true result is obtained.
+   The result is <quote>false</> if no true result is found (including the special
+   case where the array has zero elements).
+  </para>
+
+  <para>
+   <token>SOME</token> is a synonym for <token>ANY</token>.
+  </para>
+  </sect2>
+
+  <sect2>
+   <title><literal>ALL</literal> (array)</title>
+
+<synopsis>
+<replaceable>expression</replaceable> <replaceable>operator</replaceable> ALL (<replaceable>array expression</replaceable>)
+</synopsis>
+
+  <para>
+   The right-hand side is a parenthesized expression, which must yield an
+   array value.
+   The left-hand expression
+   is evaluated and compared to each element of the array using the
+   given <replaceable>operator</replaceable>, which must yield a Boolean
+   result.
+   The result of <token>ALL</token> is <quote>true</> if all comparisons yield true
+   (including the special case where the array has zero elements).
+   The result is <quote>false</> if any false result is found.
+  </para>
+  </sect2>
+
+  <sect2>
+   <title>Row-wise Comparison</title>
+
+<synopsis>
+(<replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ...</optional>) <replaceable>operator</replaceable> (<replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ...</optional>)
+</synopsis>
+
+  <para>
+   Each side is a list of scalar expressions; the two lists must be
+   of the same length.  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.
   </para>
 
   <para>
-   As usual, null values in the expressions or subquery rows are combined per
+   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;
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index d57f645b281a85dfb32e18e6c6a1d3b72c0a3c64..80a4cebf06721ea4f323164ee27182e72564c1aa 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/catalog/dependency.c,v 1.25 2003/05/28 16:03:55 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/catalog/dependency.c,v 1.26 2003/06/29 00:33:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1025,6 +1025,14 @@ find_expr_references_walker(Node *node,
 						   &context->addrs);
 		/* fall through to examine arguments */
 	}
+	if (IsA(node, ScalarArrayOpExpr))
+	{
+		ScalarArrayOpExpr   *opexpr = (ScalarArrayOpExpr *) node;
+
+		add_object_address(OCLASS_OPERATOR, opexpr->opno, 0,
+						   &context->addrs);
+		/* fall through to examine arguments */
+	}
 	if (IsA(node, NullIfExpr))
 	{
 		NullIfExpr *nullifexpr = (NullIfExpr *) node;
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index ee29c195ae72def772515ec820851b89b2842488..bba9a6e49c700b3494c193db9bae0422b058961c 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.133 2003/06/27 00:33:25 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.134 2003/06/29 00:33:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -65,6 +65,8 @@ static Datum ExecEvalOper(FuncExprState *fcache, ExprContext *econtext,
 			 bool *isNull, ExprDoneCond *isDone);
 static Datum ExecEvalDistinct(FuncExprState *fcache, ExprContext *econtext,
 				 bool *isNull);
+static Datum ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate,
+								   ExprContext *econtext, bool *isNull);
 static ExprDoneCond ExecEvalFuncArgs(FunctionCallInfo fcinfo,
 				 List *argList, ExprContext *econtext);
 static Datum ExecEvalNot(BoolExprState *notclause, ExprContext *econtext,
@@ -1121,7 +1123,6 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
 /* ----------------------------------------------------------------
  *		ExecEvalFunc
  *		ExecEvalOper
- *		ExecEvalDistinct
  *
  *		Evaluate the functional result of a list of arguments by calling the
  *		function manager.
@@ -1241,6 +1242,149 @@ ExecEvalDistinct(FuncExprState *fcache,
 	return result;
 }
 
+/*
+ * ExecEvalScalarArrayOp
+ *
+ * Evaluate "scalar op ANY/ALL (array)".  The operator always yields boolean,
+ * and we combine the results across all array elements using OR and AND
+ * (for ANY and ALL respectively).  Of course we short-circuit as soon as
+ * the result is known.
+ */
+static Datum
+ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate,
+					  ExprContext *econtext, bool *isNull)
+{
+	ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) sstate->fxprstate.xprstate.expr;
+	bool		useOr = opexpr->useOr;
+	ArrayType  *arr;
+	int			nitems;
+	Datum		result;
+	bool		resultnull;
+	FunctionCallInfoData fcinfo;
+	ExprDoneCond argDone;
+	int			i;
+	int16		typlen;
+	bool		typbyval;
+	char		typalign;
+	char	   *s;
+
+	/*
+	 * Initialize function cache if first time through
+	 */
+	if (sstate->fxprstate.func.fn_oid == InvalidOid)
+	{
+		init_fcache(opexpr->opfuncid, &sstate->fxprstate,
+					econtext->ecxt_per_query_memory);
+		Assert(!sstate->fxprstate.func.fn_retset);
+	}
+
+	/* Need to prep callinfo structure */
+	MemSet(&fcinfo, 0, sizeof(fcinfo));
+	fcinfo.flinfo = &(sstate->fxprstate.func);
+	argDone = ExecEvalFuncArgs(&fcinfo, sstate->fxprstate.args, econtext);
+	if (argDone != ExprSingleResult)
+		elog(ERROR, "op ANY/ALL (array) does not support set arguments");
+	Assert(fcinfo.nargs == 2);
+
+	/*
+	 * If the array is NULL then we return NULL --- it's not very meaningful
+	 * to do anything else, even if the operator isn't strict.
+	 */
+	if (fcinfo.argnull[1])
+	{
+		*isNull = true;
+		return (Datum) 0;
+	}
+	/* Else okay to fetch and detoast the array */
+	arr = DatumGetArrayTypeP(fcinfo.arg[1]);
+
+	/*
+	 * If the array is empty, we return either FALSE or TRUE per the useOr
+	 * flag.  This is correct even if the scalar is NULL; since we would
+	 * evaluate the operator zero times, it matters not whether it would
+	 * want to return NULL.
+	 */
+	nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+	if (nitems <= 0)
+		return BoolGetDatum(!useOr);
+	/*
+	 * If the scalar is NULL, and the function is strict, return NULL.
+	 * This is just to avoid having to test for strictness inside the
+	 * loop.  (XXX but if arrays could have null elements, we'd need a
+	 * test anyway.)
+	 */
+	if (fcinfo.argnull[0] && sstate->fxprstate.func.fn_strict)
+	{
+		*isNull = true;
+		return (Datum) 0;
+	}
+
+	/*
+	 * We arrange to look up info about the element type only
+	 * once per series of calls, assuming the element type doesn't change
+	 * underneath us.
+	 */
+	if (sstate->element_type != ARR_ELEMTYPE(arr))
+	{
+		get_typlenbyvalalign(ARR_ELEMTYPE(arr),
+							 &sstate->typlen,
+							 &sstate->typbyval,
+							 &sstate->typalign);
+		sstate->element_type = ARR_ELEMTYPE(arr);
+	}
+	typlen = sstate->typlen;
+	typbyval = sstate->typbyval;
+	typalign = sstate->typalign;
+
+	result = BoolGetDatum(!useOr);
+	resultnull = false;
+
+	/* Loop over the array elements */
+	s = (char *) ARR_DATA_PTR(arr);
+	for (i = 0; i < nitems; i++)
+	{
+		Datum	elt;
+		Datum	thisresult;
+
+		/* Get array element */
+		elt = fetch_att(s, typbyval, typlen);
+
+		s = att_addlength(s, typlen, PointerGetDatum(s));
+		s = (char *) att_align(s, typalign);
+
+		/* Call comparison function */
+		fcinfo.arg[1] = elt;
+		fcinfo.argnull[1] = false;
+		fcinfo.isnull = false;
+		thisresult = FunctionCallInvoke(&fcinfo);
+
+		/* Combine results per OR or AND semantics */
+		if (fcinfo.isnull)
+			resultnull = true;
+		else if (useOr)
+		{
+			if (DatumGetBool(thisresult))
+			{
+				result = BoolGetDatum(true);
+				resultnull = false;
+				break;		/* needn't look at any more elements */
+			}
+		}
+		else
+		{
+			if (!DatumGetBool(thisresult))
+			{
+				result = BoolGetDatum(false);
+				resultnull = false;
+				break;		/* needn't look at any more elements */
+			}
+		}
+	}
+
+	*isNull = resultnull;
+	return result;
+}
+
 /* ----------------------------------------------------------------
  *		ExecEvalNot
  *		ExecEvalOr
@@ -2018,6 +2162,10 @@ ExecEvalExpr(ExprState *expression,
 			retDatum = ExecEvalDistinct((FuncExprState *) expression, econtext,
 										isNull);
 			break;
+		case T_ScalarArrayOpExpr:
+			retDatum = ExecEvalScalarArrayOp((ScalarArrayOpExprState *) expression,
+											 econtext, isNull);
+			break;
 		case T_BoolExpr:
 			{
 				BoolExprState *state = (BoolExprState *) expression;
@@ -2263,6 +2411,18 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				state = (ExprState *) fstate;
 			}
 			break;
+		case T_ScalarArrayOpExpr:
+			{
+				ScalarArrayOpExpr   *opexpr = (ScalarArrayOpExpr *) node;
+				ScalarArrayOpExprState *sstate = makeNode(ScalarArrayOpExprState);
+
+				sstate->fxprstate.args = (List *)
+					ExecInitExpr((Expr *) opexpr->args, parent);
+				sstate->fxprstate.func.fn_oid = InvalidOid; /* not initialized */
+				sstate->element_type = InvalidOid; /* ditto */
+				state = (ExprState *) sstate;
+			}
+			break;
 		case T_BoolExpr:
 			{
 				BoolExpr   *boolexpr = (BoolExpr *) node;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index b1884bb0d28988933d49ca609aa3dd62fd27887b..0caa7a55589ab8acc497aad9aa56fe56654095af 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.257 2003/06/27 14:45:28 petere Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.258 2003/06/29 00:33:43 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -802,6 +802,22 @@ _copyDistinctExpr(DistinctExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyScalarArrayOpExpr
+ */
+static ScalarArrayOpExpr *
+_copyScalarArrayOpExpr(ScalarArrayOpExpr *from)
+{
+	ScalarArrayOpExpr	   *newnode = makeNode(ScalarArrayOpExpr);
+
+	COPY_SCALAR_FIELD(opno);
+	COPY_SCALAR_FIELD(opfuncid);
+	COPY_SCALAR_FIELD(useOr);
+	COPY_NODE_FIELD(args);
+
+	return newnode;
+}
+
 /*
  * _copyBoolExpr
  */
@@ -2546,6 +2562,9 @@ copyObject(void *from)
 		case T_DistinctExpr:
 			retval = _copyDistinctExpr(from);
 			break;
+		case T_ScalarArrayOpExpr:
+			retval = _copyScalarArrayOpExpr(from);
+			break;
 		case T_BoolExpr:
 			retval = _copyBoolExpr(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 6f30c8fabd8517e93db2946230d15c34b9bf446e..0f6fbaae2ee6c55f145370fb8cf206af5c6adf9c 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -18,7 +18,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.200 2003/06/27 14:45:28 petere Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.201 2003/06/29 00:33:43 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -287,6 +287,27 @@ _equalDistinctExpr(DistinctExpr *a, DistinctExpr *b)
 	return true;
 }
 
+static bool
+_equalScalarArrayOpExpr(ScalarArrayOpExpr *a, ScalarArrayOpExpr *b)
+{
+	COMPARE_SCALAR_FIELD(opno);
+	/*
+	 * Special-case opfuncid: it is allowable for it to differ if one
+	 * node contains zero and the other doesn't.  This just means that the
+	 * one node isn't as far along in the parse/plan pipeline and hasn't
+	 * had the opfuncid cache filled yet.
+	 */
+	if (a->opfuncid != b->opfuncid &&
+		a->opfuncid != 0 &&
+		b->opfuncid != 0)
+		return false;
+
+	COMPARE_SCALAR_FIELD(useOr);
+	COMPARE_NODE_FIELD(args);
+
+	return true;
+}
+
 static bool
 _equalBoolExpr(BoolExpr *a, BoolExpr *b)
 {
@@ -1661,6 +1682,9 @@ equal(void *a, void *b)
 		case T_DistinctExpr:
 			retval = _equalDistinctExpr(a, b);
 			break;
+		case T_ScalarArrayOpExpr:
+			retval = _equalScalarArrayOpExpr(a, b);
+			break;
 		case T_BoolExpr:
 			retval = _equalBoolExpr(a, b);
 			break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 6c09574e13c060765ae7cfa9dbddb744af82d52f..84dd85ad5f88e0e520aee464b08dad75aae3a183 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -8,14 +8,14 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/nodeFuncs.c,v 1.21 2002/12/13 19:45:56 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/nodeFuncs.c,v 1.22 2003/06/29 00:33:43 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
 #include "nodes/nodeFuncs.h"
-#include "utils/lsyscache.h"
+
 
 static bool var_is_inner(Var *var);
 
@@ -72,20 +72,3 @@ var_is_rel(Var *var)
 	return (bool)
 		!(var_is_inner(var) || var_is_outer(var));
 }
-
-/*****************************************************************************
- *		OPER nodes
- *****************************************************************************/
-
-/*
- * set_opfuncid -
- *
- *		Set the opfuncid (procedure OID) in an OpExpr node,
- *		if it hasn't been set already.
- */
-void
-set_opfuncid(OpExpr *opexpr)
-{
-	if (opexpr->opfuncid == InvalidOid)
-		opexpr->opfuncid = get_opcode(opexpr->opno);
-}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2b8cb789d95f1320e9cb7c5933bd1acf1558beb..3b4858ee16ed99b58bc98a973e55dd08702030a3 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.210 2003/06/25 21:30:29 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.211 2003/06/29 00:33:43 tgl Exp $
  *
  * NOTES
  *	  Every node type that can appear in stored rules' parsetrees *must*
@@ -668,6 +668,17 @@ _outDistinctExpr(StringInfo str, DistinctExpr *node)
 	WRITE_NODE_FIELD(args);
 }
 
+static void
+_outScalarArrayOpExpr(StringInfo str, ScalarArrayOpExpr *node)
+{
+	WRITE_NODE_TYPE("SCALARARRAYOPEXPR");
+
+	WRITE_OID_FIELD(opno);
+	WRITE_OID_FIELD(opfuncid);
+	WRITE_BOOL_FIELD(useOr);
+	WRITE_NODE_FIELD(args);
+}
+
 static void
 _outBoolExpr(StringInfo str, BoolExpr *node)
 {
@@ -1333,6 +1344,16 @@ _outAExpr(StringInfo str, A_Expr *node)
 		case AEXPR_NOT:
 			appendStringInfo(str, " NOT");
 			break;
+		case AEXPR_OP_ANY:
+			appendStringInfo(str, " ");
+			WRITE_NODE_FIELD(name);
+			appendStringInfo(str, " ANY ");
+			break;
+		case AEXPR_OP_ALL:
+			appendStringInfo(str, " ");
+			WRITE_NODE_FIELD(name);
+			appendStringInfo(str, " ALL ");
+			break;
 		case AEXPR_DISTINCT:
 			appendStringInfo(str, " DISTINCT ");
 			WRITE_NODE_FIELD(name);
@@ -1619,6 +1640,9 @@ _outNode(StringInfo str, void *obj)
 			case T_DistinctExpr:
 				_outDistinctExpr(str, obj);
 				break;
+			case T_ScalarArrayOpExpr:
+				_outScalarArrayOpExpr(str, obj);
+				break;
 			case T_BoolExpr:
 				_outBoolExpr(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 1d2db3b37a9d3672e0bd16a3d0f68d07df9ad44b..b26a7a1ae8c56385fb27a7002a5c1edb1d1e385c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.156 2003/06/25 21:30:30 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.157 2003/06/29 00:33:43 tgl Exp $
  *
  * NOTES
  *	  Path and Plan nodes do not have any readfuncs support, because we
@@ -510,6 +510,32 @@ _readDistinctExpr(void)
 	READ_DONE();
 }
 
+/*
+ * _readScalarArrayOpExpr
+ */
+static ScalarArrayOpExpr *
+_readScalarArrayOpExpr(void)
+{
+	READ_LOCALS(ScalarArrayOpExpr);
+
+	READ_OID_FIELD(opno);
+	READ_OID_FIELD(opfuncid);
+	/*
+	 * The opfuncid is stored in the textual format primarily for debugging
+	 * and documentation reasons.  We want to always read it as zero to force
+	 * it to be re-looked-up in the pg_operator entry.  This ensures that
+	 * stored rules don't have hidden dependencies on operators' functions.
+	 * (We don't currently support an ALTER OPERATOR command, but might
+	 * someday.)
+	 */
+	local_node->opfuncid = InvalidOid;
+
+	READ_BOOL_FIELD(useOr);
+	READ_NODE_FIELD(args);
+
+	READ_DONE();
+}
+
 /*
  * _readBoolExpr
  */
@@ -951,6 +977,8 @@ parseNodeString(void)
 		return_value = _readOpExpr();
 	else if (MATCH("DISTINCTEXPR", 12))
 		return_value = _readDistinctExpr();
+	else if (MATCH("SCALARARRAYOPEXPR", 17))
+		return_value = _readScalarArrayOpExpr();
 	else if (MATCH("BOOLEXPR", 8))
 		return_value = _readBoolExpr();
 	else if (MATCH("SUBLINK", 7))
diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index 846d52140cca3403244cdacc55b8899401771081..67900d9e40f809d6db20ce1852c71f916df4fd4b 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/clausesel.c,v 1.58 2003/05/27 17:49:46 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/clausesel.c,v 1.59 2003/06/29 00:33:43 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -515,6 +515,12 @@ clause_selectivity(Query *root,
 		 */
 		s1 = (Selectivity) 0.5;
 	}
+	else if (IsA(clause, DistinctExpr) ||
+			 IsA(clause, ScalarArrayOpExpr))
+	{
+		/* can we do better? */
+		s1 = (Selectivity) 0.5;
+	}
 	else if (IsA(clause, NullTest))
 	{
 		/* Use node specific selectivity calculation function */
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 21bc152ce6e7ec19d239937661e51b9bdc4f3903..283987b63d53275846f7896ed546d97f68297638 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -49,7 +49,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/costsize.c,v 1.107 2003/02/16 02:30:38 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/costsize.c,v 1.108 2003/06/29 00:33:43 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1473,6 +1473,11 @@ cost_qual_eval_walker(Node *node, QualCost *total)
 	{
 		total->per_tuple += cpu_operator_cost;
 	}
+	else if (IsA(node, ScalarArrayOpExpr))
+	{
+		/* should charge more than 1 op cost, but how many? */
+		total->per_tuple += cpu_operator_cost * 10;
+	}
 	else if (IsA(node, SubLink))
 	{
 		/* This routine should not be applied to un-planned expressions */
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 4e17a85eb4b2cfd7d1b4e22d591d46ea3089df74..1da186e0aaac6b89395f04a948893006225a257b 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -9,20 +9,19 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.92 2003/02/16 02:30:38 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.93 2003/06/29 00:33:43 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
-
 #include "nodes/makefuncs.h"
-#include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planmain.h"
 #include "optimizer/tlist.h"
 #include "optimizer/var.h"
 #include "parser/parsetree.h"
+#include "utils/lsyscache.h"
 
 
 typedef struct
@@ -61,6 +60,8 @@ static Node *replace_vars_with_subplan_refs(Node *node,
 static Node *replace_vars_with_subplan_refs_mutator(Node *node,
 						replace_vars_with_subplan_refs_context *context);
 static bool fix_opfuncids_walker(Node *node, void *context);
+static void set_sa_opfuncid(ScalarArrayOpExpr *opexpr);
+
 
 /*****************************************************************************
  *
@@ -284,6 +285,8 @@ fix_expr_references_walker(Node *node, void *context)
 		set_opfuncid((OpExpr *) node);
 	else if (IsA(node, DistinctExpr))
 		set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
+	else if (IsA(node, ScalarArrayOpExpr))
+		set_sa_opfuncid((ScalarArrayOpExpr *) node);
 	else if (IsA(node, NullIfExpr))
 		set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
 	else if (IsA(node, SubPlan))
@@ -738,7 +741,35 @@ fix_opfuncids_walker(Node *node, void *context)
 		set_opfuncid((OpExpr *) node);
 	else if (IsA(node, DistinctExpr))
 		set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
+	else if (IsA(node, ScalarArrayOpExpr))
+		set_sa_opfuncid((ScalarArrayOpExpr *) node);
 	else if (IsA(node, NullIfExpr))
 		set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
 	return expression_tree_walker(node, fix_opfuncids_walker, context);
 }
+
+/*
+ * set_opfuncid
+ *		Set the opfuncid (procedure OID) in an OpExpr node,
+ *		if it hasn't been set already.
+ *
+ * Because of struct equivalence, this can also be used for
+ * DistinctExpr and NullIfExpr nodes.
+ */
+void
+set_opfuncid(OpExpr *opexpr)
+{
+	if (opexpr->opfuncid == InvalidOid)
+		opexpr->opfuncid = get_opcode(opexpr->opno);
+}
+
+/*
+ * set_sa_opfuncid
+ *		As above, for ScalarArrayOpExpr nodes.
+ */
+static void
+set_sa_opfuncid(ScalarArrayOpExpr *opexpr)
+{
+	if (opexpr->opfuncid == InvalidOid)
+		opexpr->opfuncid = get_opcode(opexpr->opno);
+}
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 8b04066133c6eb7a86db4dad1ab613caac4d10ab..54f2d7bd69b2a4c0e330ac539e8e6ce80cd250f7 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.141 2003/06/25 21:30:30 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.142 2003/06/29 00:33:43 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -25,8 +25,8 @@
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
-#include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
 #include "optimizer/var.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
@@ -461,6 +461,8 @@ expression_returns_set_walker(Node *node, void *context)
 		return false;
 	if (IsA(node, DistinctExpr))
 		return false;
+	if (IsA(node, ScalarArrayOpExpr))
+		return false;
 	if (IsA(node, BoolExpr))
 		return false;
 	if (IsA(node, SubLink))
@@ -563,6 +565,14 @@ contain_mutable_functions_walker(Node *node, void *context)
 			return true;
 		/* else fall through to check args */
 	}
+	if (IsA(node, ScalarArrayOpExpr))
+	{
+		ScalarArrayOpExpr   *expr = (ScalarArrayOpExpr *) node;
+
+		if (op_volatile(expr->opno) != PROVOLATILE_IMMUTABLE)
+			return true;
+		/* else fall through to check args */
+	}
 	if (IsA(node, NullIfExpr))
 	{
 		NullIfExpr   *expr = (NullIfExpr *) node;
@@ -638,6 +648,14 @@ contain_volatile_functions_walker(Node *node, void *context)
 			return true;
 		/* else fall through to check args */
 	}
+	if (IsA(node, ScalarArrayOpExpr))
+	{
+		ScalarArrayOpExpr   *expr = (ScalarArrayOpExpr *) node;
+
+		if (op_volatile(expr->opno) == PROVOLATILE_VOLATILE)
+			return true;
+		/* else fall through to check args */
+	}
 	if (IsA(node, NullIfExpr))
 	{
 		NullIfExpr   *expr = (NullIfExpr *) node;
@@ -711,6 +729,11 @@ contain_nonstrict_functions_walker(Node *node, void *context)
 		/* IS DISTINCT FROM is inherently non-strict */
 		return true;
 	}
+	if (IsA(node, ScalarArrayOpExpr))
+	{
+		/* inherently non-strict, consider null scalar and empty array */
+		return true;
+	}
 	if (IsA(node, BoolExpr))
 	{
 		BoolExpr   *expr = (BoolExpr *) node;
@@ -2152,6 +2175,15 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_ScalarArrayOpExpr:
+			{
+				ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
+
+				if (expression_tree_walker((Node *) expr->args,
+										   walker, context))
+					return true;
+			}
+			break;
 		case T_BoolExpr:
 			{
 				BoolExpr   *expr = (BoolExpr *) node;
@@ -2510,6 +2542,16 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_ScalarArrayOpExpr:
+			{
+				ScalarArrayOpExpr   *expr = (ScalarArrayOpExpr *) node;
+				ScalarArrayOpExpr   *newnode;
+
+				FLATCOPY(newnode, expr, ScalarArrayOpExpr);
+				MUTATE(newnode->args, expr->args, List *);
+				return (Node *) newnode;
+			}
+			break;
 		case T_BoolExpr:
 			{
 				BoolExpr   *expr = (BoolExpr *) node;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d3d9b4a5e30b5ee944805f92491913de381d3d4f..a8df7c65e9dfe376d82464e09cc0c41ff7dc4426 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.422 2003/06/27 14:45:28 petere Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.423 2003/06/29 00:33:43 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -6051,6 +6051,13 @@ a_expr:		c_expr									{ $$ = $1; }
 					n->subselect = $4;
 					$$ = (Node *)n;
 				}
+			| a_expr qual_all_Op sub_type '(' a_expr ')' %prec Op
+				{
+					if ($3 == ANY_SUBLINK)
+						$$ = (Node *) makeA_Expr(AEXPR_OP_ANY, $2, $1, $5);
+					else
+						$$ = (Node *) makeA_Expr(AEXPR_OP_ALL, $2, $1, $5);
+				}
 			| UNIQUE select_with_parens %prec Op
 				{
 					/* Not sure how to get rid of the parentheses
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 6a90ab9197d04b7288c890f2b5cbafd2fb3b714f..b985b190bac285bca72f5289bdb40767aaa43ea1 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.153 2003/06/27 17:04:53 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.154 2003/06/29 00:33:43 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -300,6 +300,34 @@ transformExpr(ParseState *pstate, Node *expr)
 														   makeList1(rexpr));
 						}
 						break;
+					case AEXPR_OP_ANY:
+						{
+							Node	   *lexpr = transformExpr(pstate,
+															  a->lexpr);
+							Node	   *rexpr = transformExpr(pstate,
+															  a->rexpr);
+
+							result = (Node *) make_scalar_array_op(pstate,
+																   a->name,
+																   true,
+																   lexpr,
+																   rexpr);
+						}
+						break;
+					case AEXPR_OP_ALL:
+						{
+							Node	   *lexpr = transformExpr(pstate,
+															  a->lexpr);
+							Node	   *rexpr = transformExpr(pstate,
+															  a->rexpr);
+
+							result = (Node *) make_scalar_array_op(pstate,
+																   a->name,
+																   false,
+																   lexpr,
+																   rexpr);
+						}
+						break;
 					case AEXPR_DISTINCT:
 						{
 							Node	   *lexpr = transformExpr(pstate,
@@ -879,6 +907,7 @@ transformExpr(ParseState *pstate, Node *expr)
 		case T_FuncExpr:
 		case T_OpExpr:
 		case T_DistinctExpr:
+		case T_ScalarArrayOpExpr:
 		case T_NullIfExpr:
 		case T_BoolExpr:
 		case T_FieldSelect:
@@ -1155,6 +1184,9 @@ exprType(Node *expr)
 		case T_DistinctExpr:
 			type = ((DistinctExpr *) expr)->opresulttype;
 			break;
+		case T_ScalarArrayOpExpr:
+			type = BOOLOID;
+			break;
 		case T_BoolExpr:
 			type = BOOLOID;
 			break;
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index 983041eb45bfc806813356a5a1c97374febf01d7..72327243310244d4fdc4fb23172d5e56d3790f3d 100644
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_oper.c,v 1.67 2003/06/27 00:33:25 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_oper.c,v 1.68 2003/06/29 00:33:43 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -737,6 +737,96 @@ make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree)
 	return result;
 }
 
+/*
+ * make_scalar_array_op()
+ *		Build expression tree for "scalar op ANY/ALL (array)" construct.
+ */
+Expr *
+make_scalar_array_op(ParseState *pstate, List *opname,
+					 bool useOr,
+					 Node *ltree, Node *rtree)
+{
+	Oid			ltypeId,
+				rtypeId,
+				atypeId,
+				res_atypeId;
+	Operator	tup;
+	Form_pg_operator opform;
+	Oid			actual_arg_types[2];
+	Oid			declared_arg_types[2];
+	List	   *args;
+	Oid			rettype;
+	ScalarArrayOpExpr *result;
+
+	ltypeId = exprType(ltree);
+	atypeId = exprType(rtree);
+	/*
+	 * The right-hand input of the operator will be the element type of
+	 * the array.  However, if we currently have just an untyped literal
+	 * on the right, stay with that and hope we can resolve the operator.
+	 */
+	if (atypeId == UNKNOWNOID)
+		rtypeId = UNKNOWNOID;
+	else
+	{
+		rtypeId = get_element_type(atypeId);
+		if (!OidIsValid(rtypeId))
+			elog(ERROR, "op ANY/ALL (array) requires array on right side");
+	}
+
+	/* Now resolve the operator */
+	tup = oper(opname, ltypeId, rtypeId, false);
+	opform = (Form_pg_operator) GETSTRUCT(tup);
+
+	args = makeList2(ltree, rtree);
+	actual_arg_types[0] = ltypeId;
+	actual_arg_types[1] = rtypeId;
+	declared_arg_types[0] = opform->oprleft;
+	declared_arg_types[1] = opform->oprright;
+
+	/*
+	 * enforce consistency with ANYARRAY and ANYELEMENT argument and
+	 * return types, possibly adjusting return type or declared_arg_types
+	 * (which will be used as the cast destination by make_fn_arguments)
+	 */
+	rettype = enforce_generic_type_consistency(actual_arg_types,
+											   declared_arg_types,
+											   2,
+											   opform->oprresult);
+
+	/*
+	 * Check that operator result is boolean
+	 */
+	if (rettype != BOOLOID)
+		elog(ERROR, "op ANY/ALL (array) requires operator to yield boolean");
+	if (get_func_retset(opform->oprcode))
+		elog(ERROR, "op ANY/ALL (array) requires operator not to return a set");
+
+	/*
+	 * Now switch back to the array type on the right, arranging for
+	 * any needed cast to be applied.
+	 */
+	res_atypeId = get_array_type(declared_arg_types[1]);
+	if (!OidIsValid(res_atypeId))
+		elog(ERROR, "unable to find datatype for array of %s",
+			 format_type_be(declared_arg_types[1]));
+	actual_arg_types[1] = atypeId;
+	declared_arg_types[1] = res_atypeId;
+
+	/* perform the necessary typecasting of arguments */
+	make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);
+
+	/* and build the expression node */
+	result = makeNode(ScalarArrayOpExpr);
+	result->opno = oprid(tup);
+	result->opfuncid = InvalidOid;
+	result->useOr = useOr;
+	result->args = args;
+
+	ReleaseSysCache(tup);
+
+	return (Expr *) result;
+}
 
 /*
  * make_op_expr()
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 27259044c1944395b2e0e51feb23720444932376..63b174a39cf9183eedbc1f39099927356647c7a1 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -3,7 +3,7 @@
  *				back to source text
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.142 2003/06/25 03:56:30 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.143 2003/06/29 00:33:44 tgl Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -2292,19 +2292,33 @@ get_rule_expr(Node *node, deparse_context *context,
 			{
 				DistinctExpr *expr = (DistinctExpr *) node;
 				List	   *args = expr->args;
+				Node	   *arg1 = (Node *) lfirst(args);
+				Node	   *arg2 = (Node *) lsecond(args);
 
-				Assert(length(args) == 2);
-				{
-					/* binary operator */
-					Node	   *arg1 = (Node *) lfirst(args);
-					Node	   *arg2 = (Node *) lsecond(args);
+				appendStringInfoChar(buf, '(');
+				get_rule_expr(arg1, context, true);
+				appendStringInfo(buf, " IS DISTINCT FROM ");
+				get_rule_expr(arg2, context, true);
+				appendStringInfoChar(buf, ')');
+			}
+			break;
 
-					appendStringInfoChar(buf, '(');
-					get_rule_expr(arg1, context, true);
-					appendStringInfo(buf, " IS DISTINCT FROM ");
-					get_rule_expr(arg2, context, true);
-					appendStringInfoChar(buf, ')');
-				}
+		case T_ScalarArrayOpExpr:
+			{
+				ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
+				List	   *args = expr->args;
+				Node	   *arg1 = (Node *) lfirst(args);
+				Node	   *arg2 = (Node *) lsecond(args);
+
+				appendStringInfoChar(buf, '(');
+				get_rule_expr(arg1, context, true);
+				appendStringInfo(buf, " %s %s (",
+								 generate_operator_name(expr->opno,
+														exprType(arg1),
+										get_element_type(exprType(arg2))),
+								 expr->useOr ? "ANY" : "ALL");
+				get_rule_expr(arg2, context, true);
+				appendStringInfo(buf, "))");
 			}
 			break;
 
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index e161bd1e59365f2292d0ac14d2085ee8e5d91c08..0d69ac1083e600dcf5c35b24e376ec567ac86678 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/fmgr/fmgr.c,v 1.70 2003/06/25 21:30:32 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/fmgr/fmgr.c,v 1.71 2003/06/29 00:33:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1651,6 +1651,7 @@ get_fn_expr_argtype(FunctionCallInfo fcinfo, int argnum)
 {
 	Node   *expr;
 	List   *args;
+	Oid		argtype;
 
 	/*
 	 * can't return anything useful if we have no FmgrInfo or if
@@ -1665,11 +1666,27 @@ get_fn_expr_argtype(FunctionCallInfo fcinfo, int argnum)
 		args = ((FuncExpr *) expr)->args;
 	else if (IsA(expr, OpExpr))
 		args = ((OpExpr *) expr)->args;
+	else if (IsA(expr, DistinctExpr))
+		args = ((DistinctExpr *) expr)->args;
+	else if (IsA(expr, ScalarArrayOpExpr))
+		args = ((ScalarArrayOpExpr *) expr)->args;
+	else if (IsA(expr, NullIfExpr))
+		args = ((NullIfExpr *) expr)->args;
 	else
 		return InvalidOid;
 
 	if (argnum < 0 || argnum >= length(args))
 		return InvalidOid;
 
-	return exprType((Node *) nth(argnum, args));
+	argtype = exprType((Node *) nth(argnum, args));
+
+	/*
+	 * special hack for ScalarArrayOpExpr: what the underlying function
+	 * will actually get passed is the element type of the array.
+	 */
+	if (IsA(expr, ScalarArrayOpExpr) &&
+		argnum == 1)
+		argtype = get_element_type(argtype);
+
+	return argtype;
 }
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index e8ce5d131a5b6e9b6d374b9d120247bd18294b02..7d73598d55baaff5e34be8ce444812b233982ad4 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: catversion.h,v 1.201 2003/06/27 00:33:25 tgl Exp $
+ * $Id: catversion.h,v 1.202 2003/06/29 00:33:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200306261
+#define CATALOG_VERSION_NO	200306281
 
 #endif
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 47879296c0eb8e1b16c4f5c17bcadf51c77e71ba..c6f1342343816b3ef488e5402aae725f55f64cfc 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: execnodes.h,v 1.99 2003/06/22 22:04:55 tgl Exp $
+ * $Id: execnodes.h,v 1.100 2003/06/29 00:33:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -488,6 +488,22 @@ typedef struct FuncExprState
 	FunctionCallInfoData setArgs;
 } FuncExprState;
 
+/* ----------------
+ *		ScalarArrayOpExprState node
+ *
+ * This is a FuncExprState plus some additional data.
+ * ----------------
+ */
+typedef struct ScalarArrayOpExprState
+{
+	FuncExprState	fxprstate;
+	/* Cached info about array element type */
+	Oid				element_type;
+	int16			typlen;
+	bool			typbyval;
+	char			typalign;
+} ScalarArrayOpExprState;
+
 /* ----------------
  *		BoolExprState node
  * ----------------
diff --git a/src/include/nodes/nodeFuncs.h b/src/include/nodes/nodeFuncs.h
index 99e1a62542c965a453b9c0406ee0614795b3051e..f0805bfa1f9c6ce73de7c56854e7aae928ac0010 100644
--- a/src/include/nodes/nodeFuncs.h
+++ b/src/include/nodes/nodeFuncs.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: nodeFuncs.h,v 1.18 2002/12/12 15:49:40 tgl Exp $
+ * $Id: nodeFuncs.h,v 1.19 2003/06/29 00:33:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -19,6 +19,5 @@
 extern bool single_node(Node *node);
 extern bool var_is_outer(Var *var);
 extern bool var_is_rel(Var *var);
-extern void set_opfuncid(OpExpr *opexpr);
 
 #endif   /* NODEFUNCS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index af87482e42a3539d33cbada6108fa90df4e4e236..2592270c25848d673ebb7148ec343f571ec6c1ec 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: nodes.h,v 1.142 2003/06/25 04:19:24 momjian Exp $
+ * $Id: nodes.h,v 1.143 2003/06/29 00:33:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -105,6 +105,7 @@ typedef enum NodeTag
 	T_FuncExpr,
 	T_OpExpr,
 	T_DistinctExpr,
+	T_ScalarArrayOpExpr,
 	T_BoolExpr,
 	T_SubLink,
 	T_SubPlan,
@@ -135,6 +136,7 @@ typedef enum NodeTag
 	T_AggrefExprState,
 	T_ArrayRefExprState,
 	T_FuncExprState,
+	T_ScalarArrayOpExprState,
 	T_BoolExprState,
 	T_SubPlanState,
 	T_CaseExprState,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 527c56b9edae26767fad5a6ba25328232ea6c08d..9101ba1e1776d29a3f6453d45f81f84d300ccff5 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parsenodes.h,v 1.241 2003/06/27 14:45:31 petere Exp $
+ * $Id: parsenodes.h,v 1.242 2003/06/29 00:33:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -170,6 +170,8 @@ typedef enum A_Expr_Kind
 	AEXPR_AND,					/* booleans - name field is unused */
 	AEXPR_OR,
 	AEXPR_NOT,
+	AEXPR_OP_ANY,				/* scalar op ANY (array) */
+	AEXPR_OP_ALL,				/* scalar op ALL (array) */
 	AEXPR_DISTINCT,				/* IS DISTINCT FROM - name must be "=" */
 	AEXPR_NULLIF,				/* NULLIF - name must be "=" */
 	AEXPR_OF					/* IS (not) OF - name must be "=" or "!=" */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 23dff8ed622e06307ba507280b1c42a6445f0c43..57827ee76fe83e20770ba9704f419899c8809583 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -10,7 +10,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: primnodes.h,v 1.85 2003/06/25 21:30:33 momjian Exp $
+ * $Id: primnodes.h,v 1.86 2003/06/29 00:33:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -334,6 +334,25 @@ typedef struct OpExpr
  */
 typedef OpExpr DistinctExpr;
 
+/*
+ * ScalarArrayOpExpr - expression node for "scalar op ANY/ALL (array)"
+ *
+ * The operator must yield boolean.  It is applied to the left operand
+ * and each element of the righthand array, and the results are combined
+ * with OR or AND (for ANY or ALL respectively).  The node representation
+ * is almost the same as for the underlying operator, but we need a useOr
+ * flag to remember whether it's ANY or ALL, and we don't have to store
+ * the result type because it must be boolean.
+ */
+typedef struct ScalarArrayOpExpr
+{
+	Expr		xpr;
+	Oid			opno;			/* PG_OPERATOR OID of the operator */
+	Oid			opfuncid;		/* PG_PROC OID of underlying function */
+	bool		useOr;			/* true for ANY, false for ALL */
+	List	   *args;			/* the scalar and array operands */
+} ScalarArrayOpExpr;
+
 /*
  * BoolExpr - expression node for the basic Boolean operators AND, OR, NOT
  *
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 35a85e311abf325185210153c7283bea5b116964..106e9d1ce4b549cc4fe7ef873660dabe2bb59b6d 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: planmain.h,v 1.70 2003/05/06 00:20:33 tgl Exp $
+ * $Id: planmain.h,v 1.71 2003/06/29 00:33:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -69,5 +69,6 @@ extern void process_implied_equality(Query *root,
  */
 extern void set_plan_references(Plan *plan, List *rtable);
 extern void fix_opfuncids(Node *node);
+extern void set_opfuncid(OpExpr *opexpr);
 
 #endif   /* PLANMAIN_H */
diff --git a/src/include/parser/parse_oper.h b/src/include/parser/parse_oper.h
index 66e1258364522c272e7872f5770a46f25b6e331f..3fd5eea2a8a8f60197b3660bfd8148d758d58b76 100644
--- a/src/include/parser/parse_oper.h
+++ b/src/include/parser/parse_oper.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parse_oper.h,v 1.28 2003/06/27 00:33:26 tgl Exp $
+ * $Id: parse_oper.h,v 1.29 2003/06/29 00:33:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -54,6 +54,9 @@ extern Oid	oprfuncid(Operator op);
 /* Build expression tree for an operator invocation */
 extern Expr *make_op(ParseState *pstate, List *opname,
 					 Node *ltree, Node *rtree);
+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);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index efb423541a9d64ae77bf51de3b38153a46f0088b..54f2b3a88037bc29e756fb44c2cdbf3439a69d2e 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -3,7 +3,7 @@
  *			  procedural language
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.86 2003/04/24 21:16:44 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.87 2003/06/29 00:33:44 tgl Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -3520,6 +3520,16 @@ exec_simple_check_node(Node *node)
 				return TRUE;
 			}
 
+		case T_ScalarArrayOpExpr:
+			{
+				ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
+
+				if (!exec_simple_check_node((Node *) expr->args))
+					return FALSE;
+
+				return TRUE;
+			}
+
 		case T_BoolExpr:
 			{
 				BoolExpr   *expr = (BoolExpr *) node;
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 9822294d19af9710131f64beaaaf579bfc12b8ff..a18d345475ba286fcc2eb41cf254a4d05c9ca057 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -294,6 +294,68 @@ SELECT CAST(ARRAY[[[[[['a','bb','ccc']]]]]] as text[]) as "{{{{{{a,bb,ccc}}}}}}"
  {{{{{{a,bb,ccc}}}}}}
 (1 row)
 
+-- scalar op any/all (array)
+select 33 = any ('{1,2,3}');
+ ?column? 
+----------
+ f
+(1 row)
+
+select 33 = any ('{1,2,33}');
+ ?column? 
+----------
+ t
+(1 row)
+
+select 33 = all ('{1,2,33}');
+ ?column? 
+----------
+ f
+(1 row)
+
+select 33 >= all ('{1,2,33}');
+ ?column? 
+----------
+ t
+(1 row)
+
+-- boundary cases
+select null::int >= all ('{1,2,33}');
+ ?column? 
+----------
+ 
+(1 row)
+
+select null::int >= all ('{}');
+ ?column? 
+----------
+ t
+(1 row)
+
+select null::int >= any ('{}');
+ ?column? 
+----------
+ f
+(1 row)
+
+-- cross-datatype
+select 33.4 = any (array[1,2,3]);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 33.4 > all (array[1,2,3]);
+ ?column? 
+----------
+ t
+(1 row)
+
+-- errors
+select 33 * any ('{1,2,3}');
+ERROR:  op ANY/ALL (array) requires operator to yield boolean
+select 33 * any (44);
+ERROR:  op ANY/ALL (array) requires array on right side
 -- test indexes on arrays
 create temp table arr_tbl (f1 int[] unique);
 NOTICE:  CREATE TABLE / UNIQUE will create implicit index 'arr_tbl_f1_key' for table 'arr_tbl'
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 8b18ffb9eb83bc46df5c6876cb395b009313225c..c3bcdd5e3a66e6441ca0c3ac87519aeb105942c3 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -154,6 +154,22 @@ SELECT ARRAY[['a','bc'],['def','hijk']]::text[]::varchar[] AS "{{a,bc},{def,hijk
 SELECT ARRAY[['a','bc'],['def','hijk']]::text[]::varchar[] is of (varchar[]) as "TRUE";
 SELECT CAST(ARRAY[[[[[['a','bb','ccc']]]]]] as text[]) as "{{{{{{a,bb,ccc}}}}}}";
 
+-- scalar op any/all (array)
+select 33 = any ('{1,2,3}');
+select 33 = any ('{1,2,33}');
+select 33 = all ('{1,2,33}');
+select 33 >= all ('{1,2,33}');
+-- boundary cases
+select null::int >= all ('{1,2,33}');
+select null::int >= all ('{}');
+select null::int >= any ('{}');
+-- cross-datatype
+select 33.4 = any (array[1,2,3]);
+select 33.4 > all (array[1,2,3]);
+-- errors
+select 33 * any ('{1,2,3}');
+select 33 * any (44);
+
 -- test indexes on arrays
 create temp table arr_tbl (f1 int[] unique);
 insert into arr_tbl values ('{1,2,3}');