diff --git a/src/backend/executor/nodeBitmapIndexscan.c b/src/backend/executor/nodeBitmapIndexscan.c
index 902eeb35a438d71032877fa0f263823bc86d3a40..46e8c14652176677b7ea77797a2de700384d5bc2 100644
--- a/src/backend/executor/nodeBitmapIndexscan.c
+++ b/src/backend/executor/nodeBitmapIndexscan.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/nodeBitmapIndexscan.c,v 1.11 2005/11/22 18:17:10 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/nodeBitmapIndexscan.c,v 1.12 2005/11/25 19:47:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -43,6 +43,7 @@ MultiExecBitmapIndexScan(BitmapIndexScanState *node)
 	ItemPointerData tids[MAX_TIDS];
 	int32		ntids;
 	double		nTuples = 0;
+	bool		doscan;
 
 	/* must provide our own instrumentation support */
 	if (node->ss.ps.instrument)
@@ -55,9 +56,18 @@ MultiExecBitmapIndexScan(BitmapIndexScanState *node)
 
 	/*
 	 * If we have runtime keys and they've not already been set up, do it now.
+	 * Array keys are also treated as runtime keys; note that if ExecReScan
+	 * returns with biss_RuntimeKeysReady still false, then there is an
+	 * empty array key so we should do nothing.
 	 */
-	if (node->biss_RuntimeKeyInfo && !node->biss_RuntimeKeysReady)
+	if (!node->biss_RuntimeKeysReady &&
+		(node->biss_NumRuntimeKeys != 0 || node->biss_NumArrayKeys != 0))
+	{
 		ExecReScan((PlanState *) node, NULL);
+		doscan = node->biss_RuntimeKeysReady;
+	}
+	else
+		doscan = true;
 
 	/*
 	 * Prepare the result bitmap.  Normally we just create a new one to pass
@@ -79,7 +89,7 @@ MultiExecBitmapIndexScan(BitmapIndexScanState *node)
 	/*
 	 * Get TIDs from index and insert into bitmap
 	 */
-	for (;;)
+	while (doscan)
 	{
 		bool		more = index_getmulti(scandesc, tids, MAX_TIDS, &ntids);
 
@@ -89,10 +99,15 @@ MultiExecBitmapIndexScan(BitmapIndexScanState *node)
 			nTuples += ntids;
 		}
 
-		if (!more)
-			break;
-
 		CHECK_FOR_INTERRUPTS();
+
+		if (!more)
+		{
+			doscan = ExecIndexAdvanceArrayKeys(node->biss_ArrayKeys,
+											   node->biss_NumArrayKeys);
+			if (doscan)			/* reset index scan */
+				index_rescan(node->biss_ScanDesc, node->biss_ScanKeys);
+		}
 	}
 
 	/* must provide our own instrumentation support */
@@ -113,10 +128,8 @@ void
 ExecBitmapIndexReScan(BitmapIndexScanState *node, ExprContext *exprCtxt)
 {
 	ExprContext *econtext;
-	ExprState **runtimeKeyInfo;
 
 	econtext = node->biss_RuntimeContext;		/* context for runtime keys */
-	runtimeKeyInfo = node->biss_RuntimeKeyInfo;
 
 	if (econtext)
 	{
@@ -137,19 +150,27 @@ ExecBitmapIndexReScan(BitmapIndexScanState *node, ExprContext *exprCtxt)
 
 	/*
 	 * If we are doing runtime key calculations (ie, the index keys depend on
-	 * data from an outer scan), compute the new key values
+	 * data from an outer scan), compute the new key values.
+	 *
+	 * Array keys are also treated as runtime keys; note that if we
+	 * return with biss_RuntimeKeysReady still false, then there is an
+	 * empty array key so no index scan is needed.
 	 */
-	if (runtimeKeyInfo)
-	{
+	if (node->biss_NumRuntimeKeys != 0)
 		ExecIndexEvalRuntimeKeys(econtext,
-								 runtimeKeyInfo,
-								 node->biss_ScanKeys,
-								 node->biss_NumScanKeys);
+								 node->biss_RuntimeKeys,
+								 node->biss_NumRuntimeKeys);
+	if (node->biss_NumArrayKeys != 0)
+		node->biss_RuntimeKeysReady =
+			ExecIndexEvalArrayKeys(econtext,
+								   node->biss_ArrayKeys,
+								   node->biss_NumArrayKeys);
+	else
 		node->biss_RuntimeKeysReady = true;
-	}
 
 	/* reset index scan */
-	index_rescan(node->biss_ScanDesc, node->biss_ScanKeys);
+	if (node->biss_RuntimeKeysReady)
+		index_rescan(node->biss_ScanDesc, node->biss_ScanKeys);
 }
 
 /* ----------------------------------------------------------------
@@ -193,10 +214,6 @@ BitmapIndexScanState *
 ExecInitBitmapIndexScan(BitmapIndexScan *node, EState *estate)
 {
 	BitmapIndexScanState *indexstate;
-	ScanKey		scanKeys;
-	int			numScanKeys;
-	ExprState **runtimeKeyInfo;
-	bool		have_runtime_keys;
 
 	/*
 	 * create state structure
@@ -236,26 +253,25 @@ ExecInitBitmapIndexScan(BitmapIndexScan *node, EState *estate)
 	/*
 	 * build the index scan keys from the index qualification
 	 */
-	have_runtime_keys =
-		ExecIndexBuildScanKeys((PlanState *) indexstate,
-							   node->indexqual,
-							   node->indexstrategy,
-							   node->indexsubtype,
-							   &runtimeKeyInfo,
-							   &scanKeys,
-							   &numScanKeys);
-
-	indexstate->biss_RuntimeKeyInfo = runtimeKeyInfo;
-	indexstate->biss_ScanKeys = scanKeys;
-	indexstate->biss_NumScanKeys = numScanKeys;
+	ExecIndexBuildScanKeys((PlanState *) indexstate,
+						   node->indexqual,
+						   node->indexstrategy,
+						   node->indexsubtype,
+						   &indexstate->biss_ScanKeys,
+						   &indexstate->biss_NumScanKeys,
+						   &indexstate->biss_RuntimeKeys,
+						   &indexstate->biss_NumRuntimeKeys,
+						   &indexstate->biss_ArrayKeys,
+						   &indexstate->biss_NumArrayKeys);
 
 	/*
-	 * If we have runtime keys, we need an ExprContext to evaluate them. We
-	 * could just create a "standard" plan node exprcontext, but to keep the
-	 * code looking similar to nodeIndexscan.c, it seems better to stick with
-	 * the approach of using a separate ExprContext.
+	 * If we have runtime keys or array keys, we need an ExprContext to
+	 * evaluate them. We could just create a "standard" plan node exprcontext,
+	 * but to keep the code looking similar to nodeIndexscan.c, it seems
+	 * better to stick with the approach of using a separate ExprContext.
 	 */
-	if (have_runtime_keys)
+	if (indexstate->biss_NumRuntimeKeys != 0 ||
+		indexstate->biss_NumArrayKeys != 0)
 	{
 		ExprContext *stdecontext = indexstate->ss.ps.ps_ExprContext;
 
@@ -286,8 +302,8 @@ ExecInitBitmapIndexScan(BitmapIndexScan *node, EState *estate)
 	indexstate->biss_ScanDesc =
 		index_beginscan_multi(indexstate->biss_RelationDesc,
 							  estate->es_snapshot,
-							  numScanKeys,
-							  scanKeys);
+							  indexstate->biss_NumScanKeys,
+							  indexstate->biss_ScanKeys);
 
 	/*
 	 * all done.
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 4f6fadfde490fce28df8c8f45964e94f7f470cfe..f34a14c85961d3d9609ea0797fbb9389c226ffc8 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/nodeIndexscan.c,v 1.106 2005/11/25 04:24:48 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/nodeIndexscan.c,v 1.107 2005/11/25 19:47:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -32,6 +32,8 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "parser/parsetree.h"
+#include "utils/array.h"
+#include "utils/lsyscache.h"
 #include "utils/memutils.h"
 
 
@@ -138,7 +140,7 @@ ExecIndexScan(IndexScanState *node)
 	/*
 	 * If we have runtime keys and they've not already been set up, do it now.
 	 */
-	if (node->iss_RuntimeKeyInfo && !node->iss_RuntimeKeysReady)
+	if (node->iss_NumRuntimeKeys != 0 && !node->iss_RuntimeKeysReady)
 		ExecReScan((PlanState *) node, NULL);
 
 	/*
@@ -162,16 +164,10 @@ ExecIndexReScan(IndexScanState *node, ExprContext *exprCtxt)
 {
 	EState	   *estate;
 	ExprContext *econtext;
-	ScanKey		scanKeys;
-	ExprState **runtimeKeyInfo;
-	int			numScanKeys;
 	Index		scanrelid;
 
 	estate = node->ss.ps.state;
 	econtext = node->iss_RuntimeContext;		/* context for runtime keys */
-	scanKeys = node->iss_ScanKeys;
-	runtimeKeyInfo = node->iss_RuntimeKeyInfo;
-	numScanKeys = node->iss_NumScanKeys;
 	scanrelid = ((IndexScan *) node->ss.ps.plan)->scan.scanrelid;
 
 	if (econtext)
@@ -202,14 +198,11 @@ ExecIndexReScan(IndexScanState *node, ExprContext *exprCtxt)
 	 * If we are doing runtime key calculations (ie, the index keys depend on
 	 * data from an outer scan), compute the new key values
 	 */
-	if (runtimeKeyInfo)
-	{
+	if (node->iss_NumRuntimeKeys != 0)
 		ExecIndexEvalRuntimeKeys(econtext,
-								 runtimeKeyInfo,
-								 scanKeys,
-								 numScanKeys);
-		node->iss_RuntimeKeysReady = true;
-	}
+								 node->iss_RuntimeKeys,
+								 node->iss_NumRuntimeKeys);
+	node->iss_RuntimeKeysReady = true;
 
 	/* If this is re-scanning of PlanQual ... */
 	if (estate->es_evTuple != NULL &&
@@ -220,7 +213,7 @@ ExecIndexReScan(IndexScanState *node, ExprContext *exprCtxt)
 	}
 
 	/* reset index scan */
-	index_rescan(node->iss_ScanDesc, scanKeys);
+	index_rescan(node->iss_ScanDesc, node->iss_ScanKeys);
 }
 
 
@@ -230,18 +223,21 @@ ExecIndexReScan(IndexScanState *node, ExprContext *exprCtxt)
  */
 void
 ExecIndexEvalRuntimeKeys(ExprContext *econtext,
-						 ExprState **run_keys,
-						 ScanKey scan_keys,
-						 int n_keys)
+						 IndexRuntimeKeyInfo *runtimeKeys, int numRuntimeKeys)
 {
 	int			j;
 
-	for (j = 0; j < n_keys; j++)
+	for (j = 0; j < numRuntimeKeys; j++)
 	{
+		ScanKey		scan_key = runtimeKeys[j].scan_key;
+		ExprState  *key_expr = runtimeKeys[j].key_expr;
+		Datum		scanvalue;
+		bool		isNull;
+
 		/*
-		 * If we have a run-time key, then extract the run-time expression and
+		 * For each run-time key, extract the run-time expression and
 		 * evaluate it with respect to the current outer tuple.  We then stick
-		 * the result into the scan key.
+		 * the result into the proper scan key.
 		 *
 		 * Note: the result of the eval could be a pass-by-ref value that's
 		 * stored in the outer scan's tuple, not in
@@ -250,24 +246,140 @@ ExecIndexEvalRuntimeKeys(ExprContext *econtext,
 		 * the result into our context explicitly, but I think that's not
 		 * necessary...
 		 */
-		if (run_keys[j] != NULL)
+		scanvalue = ExecEvalExprSwitchContext(key_expr,
+											  econtext,
+											  &isNull,
+											  NULL);
+		scan_key->sk_argument = scanvalue;
+		if (isNull)
+			scan_key->sk_flags |= SK_ISNULL;
+		else
+			scan_key->sk_flags &= ~SK_ISNULL;
+	}
+}
+
+/*
+ * ExecIndexEvalArrayKeys
+ *		Evaluate any array key values, and set up to iterate through arrays.
+ *
+ * Returns TRUE if there are array elements to consider; FALSE means there
+ * is at least one null or empty array, so no match is possible.  On TRUE
+ * result, the scankeys are initialized with the first elements of the arrays.
+ */
+bool
+ExecIndexEvalArrayKeys(ExprContext *econtext,
+					   IndexArrayKeyInfo *arrayKeys, int numArrayKeys)
+{
+	bool		result = true;
+	int			j;
+	MemoryContext oldContext;
+
+	/* We want to keep the arrays in per-tuple memory */
+	oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+
+	for (j = 0; j < numArrayKeys; j++)
+	{
+		ScanKey		scan_key = arrayKeys[j].scan_key;
+		ExprState  *array_expr = arrayKeys[j].array_expr;
+		Datum		arraydatum;
+		bool		isNull;
+		ArrayType  *arrayval;
+		int16		elmlen;
+		bool		elmbyval;
+		char		elmalign;
+		int			num_elems;
+		Datum	   *elem_values;
+		bool	   *elem_nulls;
+
+		/*
+		 * Compute and deconstruct the array expression.
+		 * (Notes in ExecIndexEvalRuntimeKeys() apply here too.)
+		 */
+		arraydatum = ExecEvalExpr(array_expr,
+								  econtext,
+								  &isNull,
+								  NULL);
+		if (isNull)
 		{
-			Datum		scanvalue;
-			bool		isNull;
-
-			scanvalue = ExecEvalExprSwitchContext(run_keys[j],
-												  econtext,
-												  &isNull,
-												  NULL);
-			scan_keys[j].sk_argument = scanvalue;
-			if (isNull)
-				scan_keys[j].sk_flags |= SK_ISNULL;
-			else
-				scan_keys[j].sk_flags &= ~SK_ISNULL;
+			result = false;
+			break;				/* no point in evaluating more */
+		}
+		arrayval = DatumGetArrayTypeP(arraydatum);
+		/* We could cache this data, but not clear it's worth it */
+		get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+							 &elmlen, &elmbyval, &elmalign);
+		deconstruct_array(arrayval,
+						  ARR_ELEMTYPE(arrayval),
+						  elmlen, elmbyval, elmalign,
+						  &elem_values, &elem_nulls, &num_elems);
+		if (num_elems <= 0)
+		{
+			result = false;
+			break;				/* no point in evaluating more */
+		}
+
+		/*
+		 * Note: we expect the previous array data, if any, to be automatically
+		 * freed by resetting the per-tuple context; hence no pfree's here.
+		 */
+		arrayKeys[j].elem_values = elem_values;
+		arrayKeys[j].elem_nulls = elem_nulls;
+		arrayKeys[j].num_elems = num_elems;
+		scan_key->sk_argument = elem_values[0];
+		if (elem_nulls[0])
+			scan_key->sk_flags |= SK_ISNULL;
+		else
+			scan_key->sk_flags &= ~SK_ISNULL;
+		arrayKeys[j].next_elem = 1;
+	}
+
+	MemoryContextSwitchTo(oldContext);
+
+	return result;
+}
+
+/*
+ * ExecIndexAdvanceArrayKeys
+ *		Advance to the next set of array key values, if any.
+ *
+ * Returns TRUE if there is another set of values to consider, FALSE if not.
+ * On TRUE result, the scankeys are initialized with the next set of values.
+ */
+bool
+ExecIndexAdvanceArrayKeys(IndexArrayKeyInfo *arrayKeys, int numArrayKeys)
+{
+	bool		found = false;
+	int			j;
+
+	for (j = 0; j < numArrayKeys; j++)
+	{
+		ScanKey		scan_key = arrayKeys[j].scan_key;
+		int			next_elem = arrayKeys[j].next_elem;
+		int			num_elems = arrayKeys[j].num_elems;
+		Datum	   *elem_values = arrayKeys[j].elem_values;
+		bool	   *elem_nulls = arrayKeys[j].elem_nulls;
+
+		if (next_elem >= num_elems)
+		{
+			next_elem = 0;
+			found = false;		/* need to advance next array key */
 		}
+		else
+			found = true;
+		scan_key->sk_argument = elem_values[next_elem];
+		if (elem_nulls[next_elem])
+			scan_key->sk_flags |= SK_ISNULL;
+		else
+			scan_key->sk_flags &= ~SK_ISNULL;
+		arrayKeys[j].next_elem = next_elem + 1;
+		if (found)
+			break;
 	}
+
+	return found;
 }
 
+
 /* ----------------------------------------------------------------
  *		ExecEndIndexScan
  * ----------------------------------------------------------------
@@ -352,10 +464,6 @@ IndexScanState *
 ExecInitIndexScan(IndexScan *node, EState *estate)
 {
 	IndexScanState *indexstate;
-	ScanKey		scanKeys;
-	int			numScanKeys;
-	ExprState **runtimeKeyInfo;
-	bool		have_runtime_keys;
 	RangeTblEntry *rtentry;
 	Index		relid;
 	Oid			reloid;
@@ -412,18 +520,16 @@ ExecInitIndexScan(IndexScan *node, EState *estate)
 	/*
 	 * build the index scan keys from the index qualification
 	 */
-	have_runtime_keys =
-		ExecIndexBuildScanKeys((PlanState *) indexstate,
-							   node->indexqual,
-							   node->indexstrategy,
-							   node->indexsubtype,
-							   &runtimeKeyInfo,
-							   &scanKeys,
-							   &numScanKeys);
-
-	indexstate->iss_RuntimeKeyInfo = runtimeKeyInfo;
-	indexstate->iss_ScanKeys = scanKeys;
-	indexstate->iss_NumScanKeys = numScanKeys;
+	ExecIndexBuildScanKeys((PlanState *) indexstate,
+						   node->indexqual,
+						   node->indexstrategy,
+						   node->indexsubtype,
+						   &indexstate->iss_ScanKeys,
+						   &indexstate->iss_NumScanKeys,
+						   &indexstate->iss_RuntimeKeys,
+						   &indexstate->iss_NumRuntimeKeys,
+						   NULL,				/* no ArrayKeys */
+						   NULL);
 
 	/*
 	 * If we have runtime keys, we need an ExprContext to evaluate them. The
@@ -431,7 +537,7 @@ ExecInitIndexScan(IndexScan *node, EState *estate)
 	 * for every tuple.  So, build another context just like the other one...
 	 * -tgl 7/11/00
 	 */
-	if (have_runtime_keys)
+	if (indexstate->iss_NumRuntimeKeys != 0)
 	{
 		ExprContext *stdecontext = indexstate->ss.ps.ps_ExprContext;
 
@@ -471,8 +577,8 @@ ExecInitIndexScan(IndexScan *node, EState *estate)
 	indexstate->iss_ScanDesc = index_beginscan(currentRelation,
 											   indexstate->iss_RelationDesc,
 											   estate->es_snapshot,
-											   numScanKeys,
-											   scanKeys);
+											   indexstate->iss_NumScanKeys,
+											   indexstate->iss_ScanKeys);
 
 	/*
 	 * Initialize result tuple type and projection info.
@@ -489,7 +595,26 @@ ExecInitIndexScan(IndexScan *node, EState *estate)
 
 /*
  * ExecIndexBuildScanKeys
- *		Build the index scan keys from the index qualification
+ *		Build the index scan keys from the index qualification expressions
+ *
+ * The index quals are passed to the index AM in the form of a ScanKey array.
+ * This routine sets up the ScanKeys, fills in all constant fields of the
+ * ScanKeys, and prepares information about the keys that have non-constant
+ * comparison values.  We divide index qual expressions into three types:
+ *
+ * 1. Simple operator with constant comparison value ("indexkey op constant").
+ * For these, we just fill in a ScanKey containing the constant value.
+ *
+ * 2. Simple operator with non-constant value ("indexkey op expression").
+ * For these, we create a ScanKey with everything filled in except the
+ * expression value, and set up an IndexRuntimeKeyInfo struct to drive
+ * evaluation of the expression at the right times.
+ *
+ * 3. ScalarArrayOpExpr ("indexkey op ANY (array-expression)").  For these,
+ * we create a ScanKey with everything filled in except the comparison value,
+ * and set up an IndexArrayKeyInfo struct to drive processing of the qual.
+ * (Note that we treat all array-expressions as requiring runtime evaluation,
+ * even if they happen to be constants.)
  *
  * Input params are:
  *
@@ -500,33 +625,43 @@ ExecInitIndexScan(IndexScan *node, EState *estate)
  *
  * Output params are:
  *
- * *runtimeKeyInfo: receives ptr to array of runtime key exprstates
- *		(NULL if no runtime keys)
  * *scanKeys: receives ptr to array of ScanKeys
- * *numScanKeys: receives number of scankeys/runtime keys
+ * *numScanKeys: receives number of scankeys
+ * *runtimeKeys: receives ptr to array of IndexRuntimeKeyInfos, or NULL if none
+ * *numRuntimeKeys: receives number of runtime keys
+ * *arrayKeys: receives ptr to array of IndexArrayKeyInfos, or NULL if none
+ * *numArrayKeys: receives number of array keys
  *
- * Return value is TRUE if any runtime key expressions were found, else FALSE.
+ * Caller may pass NULL for arrayKeys and numArrayKeys to indicate that
+ * ScalarArrayOpExpr quals are not supported.
  */
-bool
+void
 ExecIndexBuildScanKeys(PlanState *planstate, List *quals,
 					   List *strategies, List *subtypes,
-					   ExprState ***runtimeKeyInfo,
-					   ScanKey *scanKeys, int *numScanKeys)
+					   ScanKey *scanKeys, int *numScanKeys,
+					   IndexRuntimeKeyInfo **runtimeKeys, int *numRuntimeKeys,
+					   IndexArrayKeyInfo **arrayKeys, int *numArrayKeys)
 {
-	bool		have_runtime_keys = false;
 	ListCell   *qual_cell;
 	ListCell   *strategy_cell;
 	ListCell   *subtype_cell;
-	int			n_keys;
 	ScanKey		scan_keys;
-	ExprState **run_keys;
+	IndexRuntimeKeyInfo *runtime_keys;
+	IndexArrayKeyInfo *array_keys;
+	int			n_scan_keys;
+	int			n_runtime_keys;
+	int			n_array_keys;
 	int			j;
 
-	n_keys = list_length(quals);
-	scan_keys = (n_keys <= 0) ? NULL :
-		(ScanKey) palloc(n_keys * sizeof(ScanKeyData));
-	run_keys = (n_keys <= 0) ? NULL :
-		(ExprState **) palloc(n_keys * sizeof(ExprState *));
+	n_scan_keys = list_length(quals);
+	scan_keys = (ScanKey) palloc(n_scan_keys * sizeof(ScanKeyData));
+	/* Allocate these arrays as large as they could possibly need to be */
+	runtime_keys = (IndexRuntimeKeyInfo *)
+		palloc(n_scan_keys * sizeof(IndexRuntimeKeyInfo));
+	array_keys = (IndexArrayKeyInfo *)
+		palloc0(n_scan_keys * sizeof(IndexArrayKeyInfo));
+	n_runtime_keys = 0;
+	n_array_keys = 0;
 
 	/*
 	 * for each opclause in the given qual, convert each qual's opclause into
@@ -536,122 +671,171 @@ ExecIndexBuildScanKeys(PlanState *planstate, List *quals,
 	strategy_cell = list_head(strategies);
 	subtype_cell = list_head(subtypes);
 
-	for (j = 0; j < n_keys; j++)
+	for (j = 0; j < n_scan_keys; j++)
 	{
-		OpExpr	   *clause;		/* one clause of index qual */
+		ScanKey		this_scan_key = &scan_keys[j];
+		Expr	   *clause;		/* one clause of index qual */
+		RegProcedure opfuncid;	/* operator proc id used in scan */
+		StrategyNumber strategy;	/* op's strategy number */
+		Oid			subtype;	/* op's strategy subtype */
 		Expr	   *leftop;		/* expr on lhs of operator */
 		Expr	   *rightop;	/* expr on rhs ... */
-		int			flags = 0;
 		AttrNumber	varattno;	/* att number used in scan */
-		StrategyNumber strategy;	/* op's strategy number */
-		Oid			subtype;	/* op's strategy subtype */
-		RegProcedure opfuncid;	/* operator proc id used in scan */
-		Datum		scanvalue;	/* value used in scan (if const) */
 
 		/*
 		 * extract clause information from the qualification
 		 */
-		clause = (OpExpr *) lfirst(qual_cell);
+		clause = (Expr *) lfirst(qual_cell);
 		qual_cell = lnext(qual_cell);
 		strategy = lfirst_int(strategy_cell);
 		strategy_cell = lnext(strategy_cell);
 		subtype = lfirst_oid(subtype_cell);
 		subtype_cell = lnext(subtype_cell);
 
-		if (!IsA(clause, OpExpr))
-			elog(ERROR, "indexqual is not an OpExpr");
+		if (IsA(clause, OpExpr))
+		{
+			/* indexkey op const or indexkey op expression */
+			int			flags = 0;
+			Datum		scanvalue;
 
-		opfuncid = clause->opfuncid;
+			opfuncid = ((OpExpr *) clause)->opfuncid;
 
-		/*
-		 * Here we figure out the contents of the index qual. The usual case
-		 * is (var op const) which means we form a scan key for the attribute
-		 * listed in the var node and use the value of the const as comparison
-		 * data.
-		 *
-		 * If we don't have a const node, it means our scan key is a function
-		 * of information obtained during the execution of the plan, in which
-		 * case we need to recalculate the index scan key at run time.	Hence,
-		 * we set have_runtime_keys to true and place the appropriate
-		 * subexpression in run_keys. The corresponding scan key values are
-		 * recomputed at run time.
-		 */
-		run_keys[j] = NULL;
+			/*
+			 * leftop should be the index key Var, possibly relabeled
+			 */
+			leftop = (Expr *) get_leftop(clause);
 
-		/*
-		 * determine information in leftop
-		 */
-		leftop = (Expr *) get_leftop((Expr *) clause);
+			if (leftop && IsA(leftop, RelabelType))
+				leftop = ((RelabelType *) leftop)->arg;
 
-		if (leftop && IsA(leftop, RelabelType))
-			leftop = ((RelabelType *) leftop)->arg;
+			Assert(leftop != NULL);
 
-		Assert(leftop != NULL);
+			if (!(IsA(leftop, Var) &&
+				  var_is_rel((Var *) leftop)))
+				elog(ERROR, "indexqual doesn't have key on left side");
 
-		if (!(IsA(leftop, Var) &&
-			  var_is_rel((Var *) leftop)))
-			elog(ERROR, "indexqual doesn't have key on left side");
+			varattno = ((Var *) leftop)->varattno;
 
-		varattno = ((Var *) leftop)->varattno;
+			/*
+			 * rightop is the constant or variable comparison value
+			 */
+			rightop = (Expr *) get_rightop(clause);
 
-		/*
-		 * now determine information in rightop
-		 */
-		rightop = (Expr *) get_rightop((Expr *) clause);
+			if (rightop && IsA(rightop, RelabelType))
+				rightop = ((RelabelType *) rightop)->arg;
 
-		if (rightop && IsA(rightop, RelabelType))
-			rightop = ((RelabelType *) rightop)->arg;
+			Assert(rightop != NULL);
 
-		Assert(rightop != NULL);
+			if (IsA(rightop, Const))
+			{
+				/* OK, simple constant comparison value */
+				scanvalue = ((Const *) rightop)->constvalue;
+				if (((Const *) rightop)->constisnull)
+					flags |= SK_ISNULL;
+			}
+			else
+			{
+				/* Need to treat this one as a runtime key */
+				runtime_keys[n_runtime_keys].scan_key = this_scan_key;
+				runtime_keys[n_runtime_keys].key_expr =
+					ExecInitExpr(rightop, planstate);
+				n_runtime_keys++;
+				scanvalue = (Datum) 0;
+			}
 
-		if (IsA(rightop, Const))
-		{
 			/*
-			 * if the rightop is a const node then it means it identifies the
-			 * value to place in our scan key.
+			 * initialize the scan key's fields appropriately
 			 */
-			scanvalue = ((Const *) rightop)->constvalue;
-			if (((Const *) rightop)->constisnull)
-				flags |= SK_ISNULL;
+			ScanKeyEntryInitialize(this_scan_key,
+								   flags,
+								   varattno,	/* attribute number to scan */
+								   strategy,	/* op's strategy */
+								   subtype,		/* strategy subtype */
+								   opfuncid,	/* reg proc to use */
+								   scanvalue);	/* constant */
 		}
-		else
+		else if (IsA(clause, ScalarArrayOpExpr))
 		{
+			/* indexkey op ANY (array-expression) */
+			ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
+
+			Assert(saop->useOr);
+			opfuncid = saop->opfuncid;
+
 			/*
-			 * otherwise, the rightop contains an expression evaluable at
-			 * runtime to figure out the value to place in our scan key.
+			 * leftop should be the index key Var, possibly relabeled
 			 */
-			have_runtime_keys = true;
-			run_keys[j] = ExecInitExpr(rightop, planstate);
-			scanvalue = (Datum) 0;
-		}
+			leftop = (Expr *) linitial(saop->args);
 
-		/*
-		 * initialize the scan key's fields appropriately
-		 */
-		ScanKeyEntryInitialize(&scan_keys[j],
-							   flags,
-							   varattno,		/* attribute number to scan */
-							   strategy,		/* op's strategy */
-							   subtype, /* strategy subtype */
-							   opfuncid,		/* reg proc to use */
-							   scanvalue);		/* constant */
+			if (leftop && IsA(leftop, RelabelType))
+				leftop = ((RelabelType *) leftop)->arg;
+
+			Assert(leftop != NULL);
+
+			if (!(IsA(leftop, Var) &&
+				  var_is_rel((Var *) leftop)))
+				elog(ERROR, "indexqual doesn't have key on left side");
+
+			varattno = ((Var *) leftop)->varattno;
+
+			/*
+			 * rightop is the constant or variable array value
+			 */
+			rightop = (Expr *) lsecond(saop->args);
+
+			if (rightop && IsA(rightop, RelabelType))
+				rightop = ((RelabelType *) rightop)->arg;
+
+			Assert(rightop != NULL);
+
+			array_keys[n_array_keys].scan_key = this_scan_key;
+			array_keys[n_array_keys].array_expr =
+				ExecInitExpr(rightop, planstate);
+			/* the remaining fields were zeroed by palloc0 */
+			n_array_keys++;
+
+			/*
+			 * initialize the scan key's fields appropriately
+			 */
+			ScanKeyEntryInitialize(this_scan_key,
+								   0,			/* flags */
+								   varattno,	/* attribute number to scan */
+								   strategy,	/* op's strategy */
+								   subtype,		/* strategy subtype */
+								   opfuncid,	/* reg proc to use */
+								   (Datum) 0);	/* constant */
+		}
+		else
+			elog(ERROR, "unsupported indexqual type: %d",
+				 (int) nodeTag(clause));
 	}
 
-	/* If no runtime keys, get rid of speculatively-allocated array */
-	if (run_keys && !have_runtime_keys)
+	/* Get rid of any unused arrays */
+	if (n_runtime_keys == 0)
+	{
+		pfree(runtime_keys);
+		runtime_keys = NULL;
+	}
+	if (n_array_keys == 0)
 	{
-		pfree(run_keys);
-		run_keys = NULL;
+		pfree(array_keys);
+		array_keys = NULL;
 	}
 
 	/*
-	 * Return the info to our caller.
+	 * Return info to our caller.
 	 */
-	*numScanKeys = n_keys;
 	*scanKeys = scan_keys;
-	*runtimeKeyInfo = run_keys;
-
-	return have_runtime_keys;
+	*numScanKeys = n_scan_keys;
+	*runtimeKeys = runtime_keys;
+	*numRuntimeKeys = n_runtime_keys;
+	if (arrayKeys)
+	{
+		*arrayKeys = array_keys;
+		*numArrayKeys = n_array_keys;
+	}
+	else if (n_array_keys != 0)
+		elog(ERROR, "ScalarArrayOpExpr index qual found where not allowed");
 }
 
 int
diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index 9a4990898e92fa685b3109b6b1216ec831559148..eba6bf1d148622e815f43cbfc3d8724396de2455 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/path/clausesel.c,v 1.75 2005/10/15 02:49:19 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/path/clausesel.c,v 1.76 2005/11/25 19:47:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -624,12 +624,45 @@ clause_selectivity(PlannerInfo *root,
 		 */
 		s1 = (Selectivity) 0.5;
 	}
-	else if (IsA(clause, DistinctExpr) ||
-			 IsA(clause, ScalarArrayOpExpr))
+	else if (IsA(clause, DistinctExpr))
 	{
 		/* can we do better? */
 		s1 = (Selectivity) 0.5;
 	}
+	else if (IsA(clause, ScalarArrayOpExpr))
+	{
+		/* First, decide if it's a join clause, same as for OpExpr */
+		bool		is_join_clause;
+
+		if (varRelid != 0)
+		{
+			/*
+			 * If we are considering a nestloop join then all clauses are
+			 * restriction clauses, since we are only interested in the one
+			 * relation.
+			 */
+			is_join_clause = false;
+		}
+		else
+		{
+			/*
+			 * Otherwise, it's a join if there's more than one relation used.
+			 * We can optimize this calculation if an rinfo was passed.
+			 */
+			if (rinfo)
+				is_join_clause = (bms_membership(rinfo->clause_relids) ==
+								  BMS_MULTIPLE);
+			else
+				is_join_clause = (NumRelids(clause) > 1);
+		}
+
+		/* Use node specific selectivity calculation function */
+		s1 = scalararraysel(root,
+							(ScalarArrayOpExpr *) clause,
+							is_join_clause,
+							varRelid,
+							jointype);
+	}
 	else if (IsA(clause, NullTest))
 	{
 		/* Use node specific selectivity calculation function */
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 6f3157a23f608dc1fb6b00fdb7f9357a54aad36e..fde89455685e76a5ecd84580f81f32d48ba592a4 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.193 2005/11/22 18:17:12 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.194 2005/11/25 19:47:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -28,6 +28,7 @@
 #include "optimizer/paths.h"
 #include "optimizer/predtest.h"
 #include "optimizer/restrictinfo.h"
+#include "optimizer/var.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -40,9 +41,6 @@
  */
 #define DoneMatchingIndexKeys(classes)	(classes[0] == InvalidOid)
 
-#define is_indexable_operator(clause,opclass,indexkey_on_left) \
-	(indexable_operator(clause,opclass,indexkey_on_left) != InvalidOid)
-
 #define IsBooleanOpclass(opclass) \
 	((opclass) == BOOL_BTREE_OPS_OID || (opclass) == BOOL_HASH_OPS_OID)
 
@@ -50,16 +48,18 @@
 static List *find_usable_indexes(PlannerInfo *root, RelOptInfo *rel,
 					List *clauses, List *outer_clauses,
 					bool istoplevel, bool isjoininner,
-					Relids outer_relids);
+					Relids outer_relids,
+					SaOpControl saop_control);
 static Path *choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel, List *paths);
 static int	bitmap_path_comparator(const void *a, const void *b);
 static Cost bitmap_and_cost_est(PlannerInfo *root, RelOptInfo *rel, List *paths);
 static bool match_clause_to_indexcol(IndexOptInfo *index,
 						 int indexcol, Oid opclass,
 						 RestrictInfo *rinfo,
-						 Relids outer_relids);
-static Oid indexable_operator(Expr *clause, Oid opclass,
-				   bool indexkey_on_left);
+						 Relids outer_relids,
+						 SaOpControl saop_control);
+static bool is_indexable_operator(Oid expr_op, Oid opclass,
+								  bool indexkey_on_left);
 static Relids indexable_outerrelids(RelOptInfo *rel);
 static bool matches_any_index(RestrictInfo *rinfo, RelOptInfo *rel,
 				  Relids outer_relids);
@@ -150,7 +150,7 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel)
 	 */
 	indexpaths = find_usable_indexes(root, rel,
 									 rel->baserestrictinfo, NIL,
-									 true, false, NULL);
+									 true, false, NULL, SAOP_FORBID);
 
 	/*
 	 * We can submit them all to add_path.	(This generates access paths for
@@ -228,6 +228,7 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel)
  *		given clauses are join clauses)
  * 'outer_relids' identifies the outer side of the join (pass NULL
  *		if not isjoininner)
+ * 'saop_control' indicates whether ScalarArrayOpExpr clauses can be used
  *
  * Note: check_partial_indexes() must have been run previously.
  *----------
@@ -236,7 +237,8 @@ static List *
 find_usable_indexes(PlannerInfo *root, RelOptInfo *rel,
 					List *clauses, List *outer_clauses,
 					bool istoplevel, bool isjoininner,
-					Relids outer_relids)
+					Relids outer_relids,
+					SaOpControl saop_control)
 {
 	List	   *result = NIL;
 	List	   *all_clauses = NIL;		/* not computed till needed */
@@ -267,6 +269,10 @@ find_usable_indexes(PlannerInfo *root, RelOptInfo *rel,
 		 * predOK index to an arm of an OR, which would be a legal but
 		 * pointlessly inefficient plan.  (A better plan will be generated by
 		 * just scanning the predOK index alone, no OR.)
+		 *
+		 * If saop_control is SAOP_REQUIRE and istoplevel is false, the caller
+		 * is only interested in indexquals involving ScalarArrayOps, so don't
+		 * set useful_predicate to true.
 		 */
 		useful_predicate = false;
 		if (index->indpred != NIL)
@@ -292,7 +298,8 @@ find_usable_indexes(PlannerInfo *root, RelOptInfo *rel,
 				if (!predicate_implied_by(index->indpred, all_clauses))
 					continue;	/* can't use it at all */
 
-				if (!predicate_implied_by(index->indpred, outer_clauses))
+				if (saop_control != SAOP_REQUIRE &&
+					!predicate_implied_by(index->indpred, outer_clauses))
 					useful_predicate = true;
 			}
 		}
@@ -300,12 +307,14 @@ find_usable_indexes(PlannerInfo *root, RelOptInfo *rel,
 		/*
 		 * 1. Match the index against the available restriction clauses.
 		 * found_clause is set true only if at least one of the current
-		 * clauses was used.
+		 * clauses was used (and, if saop_control is SAOP_REQUIRE, it
+		 * has to have been a ScalarArrayOpExpr clause).
 		 */
 		restrictclauses = group_clauses_by_indexkey(index,
 													clauses,
 													outer_clauses,
 													outer_relids,
+													saop_control,
 													&found_clause);
 
 		/*
@@ -380,9 +389,9 @@ find_usable_indexes(PlannerInfo *root, RelOptInfo *rel,
 
 /*
  * generate_bitmap_or_paths
- *		Look through the list of clauses to find OR clauses, and generate
- *		a BitmapOrPath for each one we can handle that way.  Return a list
- *		of the generated BitmapOrPaths.
+ *		Look through the list of clauses to find OR clauses and
+ *		ScalarArrayOpExpr clauses, and generate a BitmapOrPath for each one
+ *		we can handle that way.  Return a list of the generated BitmapOrPaths.
  *
  * outer_clauses is a list of additional clauses that can be assumed true
  * for the purpose of generating indexquals, but are not to be searched for
@@ -396,6 +405,7 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
 {
 	List	   *result = NIL;
 	List	   *all_clauses;
+	bool		have_saop = false;
 	ListCell   *l;
 
 	/*
@@ -412,9 +422,16 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
 		ListCell   *j;
 
 		Assert(IsA(rinfo, RestrictInfo));
-		/* Ignore RestrictInfos that aren't ORs */
+		/*
+		 * In this loop we ignore RestrictInfos that aren't ORs; but take
+		 * note of ScalarArrayOpExpr for later.
+		 */
 		if (!restriction_is_or_clause(rinfo))
+		{
+			if (IsA(rinfo->clause, ScalarArrayOpExpr))
+				have_saop = true;
 			continue;
+		}
 
 		/*
 		 * We must be able to match at least one index to each of the arms of
@@ -436,7 +453,8 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
 											  all_clauses,
 											  false,
 											  isjoininner,
-											  outer_relids);
+											  outer_relids,
+											  SAOP_ALLOW);
 				/* Recurse in case there are sub-ORs */
 				indlist = list_concat(indlist,
 									  generate_bitmap_or_paths(root, rel,
@@ -454,7 +472,8 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
 											  all_clauses,
 											  false,
 											  isjoininner,
-											  outer_relids);
+											  outer_relids,
+											  SAOP_ALLOW);
 			}
 
 			/*
@@ -486,6 +505,29 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
 		}
 	}
 
+	/*
+	 * If we saw any top-level ScalarArrayOpExpr clauses, see if we can
+	 * generate a bitmap index path that uses those but not any OR clauses.
+	 */
+	if (have_saop)
+	{
+		List	   *pathlist;
+		Path	   *bitmapqual;
+
+		pathlist = find_usable_indexes(root, rel,
+									   clauses,
+									   outer_clauses,
+									   false,
+									   isjoininner,
+									   outer_relids,
+									   SAOP_REQUIRE);
+		if (pathlist != NIL)
+		{
+			bitmapqual = (Path *) create_bitmap_or_path(root, rel, pathlist);
+			result = lappend(result, bitmapqual);
+		}
+	}
+
 	return result;
 }
 
@@ -526,7 +568,8 @@ choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel, List *paths)
 	 *
 	 * We also make some effort to detect directly redundant input paths, as
 	 * can happen if there are multiple possibly usable indexes.  For this we
-	 * look only at plain IndexPath inputs, not at sub-OR clauses. And we
+	 * look only at plain IndexPath and single-element BitmapOrPath inputs
+	 * (the latter can arise in the presence of ScalarArrayOpExpr quals).  We
 	 * consider an index redundant if all its index conditions were already
 	 * used by earlier indexes.  (We could use predicate_implied_by to have a
 	 * more intelligent, but much more expensive, check --- but in most cases
@@ -555,10 +598,17 @@ choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel, List *paths)
 
 	paths = list_make1(patharray[0]);
 	costsofar = bitmap_and_cost_est(root, rel, paths);
+	qualsofar = NIL;
 	if (IsA(patharray[0], IndexPath))
 		qualsofar = list_copy(((IndexPath *) patharray[0])->indexclauses);
-	else
-		qualsofar = NIL;
+	else if (IsA(patharray[0], BitmapOrPath))
+	{
+		List   *orquals = ((BitmapOrPath *) patharray[0])->bitmapquals;
+
+		if (list_length(orquals) == 1 &&
+			IsA(linitial(orquals), IndexPath))
+		qualsofar = list_copy(((IndexPath *) linitial(orquals))->indexclauses);
+	}
 	lastcell = list_head(paths);	/* for quick deletions */
 
 	for (i = 1; i < npaths; i++)
@@ -573,6 +623,16 @@ choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel, List *paths)
 			if (list_difference_ptr(newqual, qualsofar) == NIL)
 				continue;		/* redundant */
 		}
+		else if (IsA(newpath, BitmapOrPath))
+		{
+			List   *orquals = ((BitmapOrPath *) newpath)->bitmapquals;
+
+			if (list_length(orquals) == 1 &&
+				IsA(linitial(orquals), IndexPath))
+				newqual = ((IndexPath *) linitial(orquals))->indexclauses;
+			if (list_difference_ptr(newqual, qualsofar) == NIL)
+				continue;		/* redundant */
+		}
 
 		paths = lappend(paths, newpath);
 		newcost = bitmap_and_cost_est(root, rel, paths);
@@ -665,6 +725,10 @@ bitmap_and_cost_est(PlannerInfo *root, RelOptInfo *rel, List *paths)
  * outer_relids determines what Vars will be allowed on the other side
  * of a possible index qual; see match_clause_to_indexcol().
  *
+ * 'saop_control' indicates whether ScalarArrayOpExpr clauses can be used.
+ * When it's SAOP_REQUIRE, *found_clause is set TRUE only if we used at least
+ * one ScalarArrayOpExpr from the current clauses list.
+ *
  * If the index has amoptionalkey = false, we give up and return NIL when
  * there are no restriction clauses matching the first index key.  Otherwise,
  * we return NIL if there are no restriction clauses matching any index key.
@@ -682,6 +746,7 @@ List *
 group_clauses_by_indexkey(IndexOptInfo *index,
 						  List *clauses, List *outer_clauses,
 						  Relids outer_relids,
+						  SaOpControl saop_control,
 						  bool *found_clause)
 {
 	List	   *clausegroup_list = NIL;
@@ -710,10 +775,13 @@ group_clauses_by_indexkey(IndexOptInfo *index,
 										 indexcol,
 										 curClass,
 										 rinfo,
-										 outer_relids))
+										 outer_relids,
+										 saop_control))
 			{
 				clausegroup = list_append_unique_ptr(clausegroup, rinfo);
-				*found_clause = true;
+				if (saop_control != SAOP_REQUIRE ||
+					IsA(rinfo->clause, ScalarArrayOpExpr))
+					*found_clause = true;
 			}
 		}
 
@@ -727,7 +795,8 @@ group_clauses_by_indexkey(IndexOptInfo *index,
 										 indexcol,
 										 curClass,
 										 rinfo,
-										 outer_relids))
+										 outer_relids,
+										 saop_control))
 			{
 				clausegroup = list_append_unique_ptr(clausegroup, rinfo);
 				found_outer_clause = true;
@@ -785,6 +854,11 @@ group_clauses_by_indexkey(IndexOptInfo *index,
  *	  We do not actually do the commuting here, but we check whether a
  *	  suitable commutator operator is available.
  *
+ *	  It is also possible to match ScalarArrayOpExpr clauses to indexes, when
+ *	  the clause is of the form "indexkey op ANY (arrayconst)".  Since the
+ *	  executor can only handle these in the context of bitmap index scans,
+ *	  our caller specifies whether to allow these or not.
+ *
  *	  For boolean indexes, it is also possible to match the clause directly
  *	  to the indexkey; or perhaps the clause is (NOT indexkey).
  *
@@ -792,6 +866,7 @@ group_clauses_by_indexkey(IndexOptInfo *index,
  * 'indexcol' is a column number of 'index' (counting from 0).
  * 'opclass' is the corresponding operator class.
  * 'rinfo' is the clause to be tested (as a RestrictInfo node).
+ * 'saop_control' indicates whether ScalarArrayOpExpr clauses can be used.
  *
  * Returns true if the clause can be used with this index key.
  *
@@ -803,11 +878,16 @@ match_clause_to_indexcol(IndexOptInfo *index,
 						 int indexcol,
 						 Oid opclass,
 						 RestrictInfo *rinfo,
-						 Relids outer_relids)
+						 Relids outer_relids,
+						 SaOpControl saop_control)
 {
 	Expr	   *clause = rinfo->clause;
 	Node	   *leftop,
 			   *rightop;
+	Relids		left_relids;
+	Relids		right_relids;
+	Oid			expr_op;
+	bool		plain_op;
 
 	/* First check for boolean-index cases. */
 	if (IsBooleanOpclass(opclass))
@@ -816,12 +896,37 @@ match_clause_to_indexcol(IndexOptInfo *index,
 			return true;
 	}
 
-	/* Else clause must be a binary opclause. */
-	if (!is_opclause(clause))
-		return false;
-	leftop = get_leftop(clause);
-	rightop = get_rightop(clause);
-	if (!leftop || !rightop)
+	/*
+	 * Clause must be a binary opclause, or possibly a ScalarArrayOpExpr
+	 * (which is always binary, by definition).
+	 */
+	if (is_opclause(clause))
+	{
+		leftop = get_leftop(clause);
+		rightop = get_rightop(clause);
+		if (!leftop || !rightop)
+			return false;
+		left_relids = rinfo->left_relids;
+		right_relids = rinfo->right_relids;
+		expr_op = ((OpExpr *) clause)->opno;
+		plain_op = true;
+	}
+	else if (saop_control != SAOP_FORBID &&
+			 clause && IsA(clause, ScalarArrayOpExpr))
+	{
+		ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
+
+		/* We only accept ANY clauses, not ALL */
+		if (!saop->useOr)
+			return false;
+		leftop = (Node *) linitial(saop->args);
+		rightop = (Node *) lsecond(saop->args);
+		left_relids = NULL;		/* not actually needed */
+		right_relids = pull_varnos(rightop);
+		expr_op = saop->opno;
+		plain_op = false;
+	}
+	else
 		return false;
 
 	/*
@@ -829,26 +934,28 @@ match_clause_to_indexcol(IndexOptInfo *index,
 	 * (constant operator indexkey).  See above notes about const-ness.
 	 */
 	if (match_index_to_operand(leftop, indexcol, index) &&
-		bms_is_subset(rinfo->right_relids, outer_relids) &&
+		bms_is_subset(right_relids, outer_relids) &&
 		!contain_volatile_functions(rightop))
 	{
-		if (is_indexable_operator(clause, opclass, true))
+		if (is_indexable_operator(expr_op, opclass, true))
 			return true;
 
 		/*
 		 * If we didn't find a member of the index's opclass, see whether it
 		 * is a "special" indexable operator.
 		 */
-		if (match_special_index_operator(clause, opclass, true))
+		if (plain_op &&
+			match_special_index_operator(clause, opclass, true))
 			return true;
 		return false;
 	}
 
-	if (match_index_to_operand(rightop, indexcol, index) &&
-		bms_is_subset(rinfo->left_relids, outer_relids) &&
+	if (plain_op &&
+		match_index_to_operand(rightop, indexcol, index) &&
+		bms_is_subset(left_relids, outer_relids) &&
 		!contain_volatile_functions(leftop))
 	{
-		if (is_indexable_operator(clause, opclass, false))
+		if (is_indexable_operator(expr_op, opclass, false))
 			return true;
 
 		/*
@@ -864,36 +971,26 @@ match_clause_to_indexcol(IndexOptInfo *index,
 }
 
 /*
- * indexable_operator
- *	  Does a binary opclause contain an operator matching the index opclass?
+ * is_indexable_operator
+ *	  Does the operator match the specified index opclass?
  *
  * If the indexkey is on the right, what we actually want to know
  * is whether the operator has a commutator operator that matches
- * the index's opclass.
- *
- * Returns the OID of the matching operator, or InvalidOid if no match.
- * (Formerly, this routine might return a binary-compatible operator
- * rather than the original one, but that kluge is history.)
+ * the opclass.
  */
-static Oid
-indexable_operator(Expr *clause, Oid opclass, bool indexkey_on_left)
+static bool
+is_indexable_operator(Oid expr_op, Oid opclass, bool indexkey_on_left)
 {
-	Oid			expr_op = ((OpExpr *) clause)->opno;
-	Oid			commuted_op;
-
 	/* Get the commuted operator if necessary */
-	if (indexkey_on_left)
-		commuted_op = expr_op;
-	else
-		commuted_op = get_commutator(expr_op);
-	if (commuted_op == InvalidOid)
-		return InvalidOid;
+	if (!indexkey_on_left)
+	{
+		expr_op = get_commutator(expr_op);
+		if (expr_op == InvalidOid)
+			return false;
+	}
 
 	/* OK if the (commuted) operator is a member of the index's opclass */
-	if (op_in_opclass(commuted_op, opclass))
-		return expr_op;
-
-	return InvalidOid;
+	return op_in_opclass(expr_op, opclass);
 }
 
 /****************************************************************************
@@ -1031,7 +1128,8 @@ matches_any_index(RestrictInfo *rinfo, RelOptInfo *rel, Relids outer_relids)
 										 indexcol,
 										 curClass,
 										 rinfo,
-										 outer_relids))
+										 outer_relids,
+										 SAOP_ALLOW))
 				return true;
 
 			indexcol++;
@@ -1137,16 +1235,17 @@ best_inner_indexscan(PlannerInfo *root, RelOptInfo *rel,
 
 	/*
 	 * Find all the index paths that are usable for this join, except for
-	 * stuff involving OR clauses.
+	 * stuff involving OR and ScalarArrayOpExpr clauses.
 	 */
 	indexpaths = find_usable_indexes(root, rel,
 									 clause_list, NIL,
 									 false, true,
-									 outer_relids);
+									 outer_relids,
+									 SAOP_FORBID);
 
 	/*
-	 * Generate BitmapOrPaths for any suitable OR-clauses present in the
-	 * clause list.
+	 * Generate BitmapOrPaths for any suitable OR-clauses or ScalarArrayOpExpr
+	 * clauses present in the clause list.
 	 */
 	bitindexpaths = generate_bitmap_or_paths(root, rel,
 											 clause_list, NIL,
@@ -1384,7 +1483,10 @@ identify_ignorable_ordering_cols(PlannerInfo *root,
 			bool		varonleft;
 			bool		ispc;
 
-			/* We know this clause passed match_clause_to_indexcol */
+			/*
+			 * We know this clause passed match_clause_to_indexcol as a
+			 * toplevel clause; so it's not a ScalarArrayOp.
+			 */
 
 			/* First check for boolean-index cases. */
 			if (IsBooleanOpclass(opclass))
@@ -1923,6 +2025,13 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups)
 				}
 			}
 
+			/* Next check for ScalarArrayOp cases */
+			if (IsA(rinfo->clause, ScalarArrayOpExpr))
+			{
+				resultquals = lappend(resultquals, rinfo);
+				continue;
+			}
+
 			resultquals = list_concat(resultquals,
 									  expand_indexqual_condition(rinfo,
 																 curClass));
@@ -2001,7 +2110,7 @@ expand_boolean_index_clause(Node *clause,
 
 /*
  * expand_indexqual_condition --- expand a single indexqual condition
- *		(other than a boolean-qual case)
+ *		(other than a boolean-qual or ScalarArrayOp case)
  *
  * The input is a single RestrictInfo, the output a list of RestrictInfos
  */
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 978419842e3b9654d784374b41c42e18b151fd5a..3bd760fda3a6714c047ce9dd3d7f265603e5ce08 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.203 2005/11/22 18:17:12 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.204 2005/11/25 19:47:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1069,17 +1069,28 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
 				subindexquals = lappend(subindexquals,
 										make_ands_explicit(subindexqual));
 		}
-		plan = (Plan *) make_bitmap_or(subplans);
-		plan->startup_cost = opath->path.startup_cost;
-		plan->total_cost = opath->path.total_cost;
-		plan->plan_rows =
-			clamp_row_est(opath->bitmapselectivity * opath->path.parent->tuples);
-		plan->plan_width = 0;	/* meaningless */
+		/*
+		 * In the presence of ScalarArrayOpExpr quals, we might have built
+		 * BitmapOrPaths with just one subpath; don't add an OR step.
+		 */
+		if (list_length(subplans) == 1)
+		{
+			plan = (Plan *) linitial(subplans);
+		}
+		else
+		{
+			plan = (Plan *) make_bitmap_or(subplans);
+			plan->startup_cost = opath->path.startup_cost;
+			plan->total_cost = opath->path.total_cost;
+			plan->plan_rows =
+				clamp_row_est(opath->bitmapselectivity * opath->path.parent->tuples);
+			plan->plan_width = 0;	/* meaningless */
+		}
 
 		/*
 		 * If there were constant-TRUE subquals, the OR reduces to constant
 		 * TRUE.  Also, avoid generating one-element ORs, which could happen
-		 * due to redundancy elimination.
+		 * due to redundancy elimination or ScalarArrayOpExpr quals.
 		 */
 		if (const_true_subqual)
 			*qual = NIL;
@@ -1531,18 +1542,14 @@ fix_indexqual_references(List *indexquals, IndexPath *index_path,
 	foreach(l, indexquals)
 	{
 		RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
-		OpExpr	   *clause;
-		OpExpr	   *newclause;
+		Expr	   *clause;
+		Oid			clause_op;
 		Oid			opclass;
 		int			stratno;
 		Oid			stratsubtype;
 		bool		recheck;
 
 		Assert(IsA(rinfo, RestrictInfo));
-		clause = (OpExpr *) rinfo->clause;
-		if (!IsA(clause, OpExpr) ||
-			list_length(clause->args) != 2)
-			elog(ERROR, "indexqual clause is not binary opclause");
 
 		/*
 		 * Make a copy that will become the fixed clause.
@@ -1551,33 +1558,62 @@ fix_indexqual_references(List *indexquals, IndexPath *index_path,
 		 * is a subplan in the arguments of the opclause.  So just do a full
 		 * copy.
 		 */
-		newclause = (OpExpr *) copyObject((Node *) clause);
+		clause = (Expr *) copyObject((Node *) rinfo->clause);
 
-		/*
-		 * Check to see if the indexkey is on the right; if so, commute the
-		 * clause.	The indexkey should be the side that refers to (only) the
-		 * base relation.
-		 */
-		if (!bms_equal(rinfo->left_relids, index->rel->relids))
-			CommuteClause(newclause);
+		if (IsA(clause, OpExpr))
+		{
+			OpExpr *op = (OpExpr *) clause;
 
-		/*
-		 * Now, determine which index attribute this is, change the indexkey
-		 * operand as needed, and get the index opclass.
-		 */
-		linitial(newclause->args) =
-			fix_indexqual_operand(linitial(newclause->args),
-								  index,
-								  &opclass);
+			if (list_length(op->args) != 2)
+				elog(ERROR, "indexqual clause is not binary opclause");
+
+			/*
+			 * Check to see if the indexkey is on the right; if so, commute
+			 * the clause. The indexkey should be the side that refers to
+			 * (only) the base relation.
+			 */
+			if (!bms_equal(rinfo->left_relids, index->rel->relids))
+				CommuteClause(op);
+
+			/*
+			 * Now, determine which index attribute this is, change the
+			 * indexkey operand as needed, and get the index opclass.
+			 */
+			linitial(op->args) = fix_indexqual_operand(linitial(op->args),
+													   index,
+													   &opclass);
+			clause_op = op->opno;
+		}
+		else if (IsA(clause, ScalarArrayOpExpr))
+		{
+			ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
+
+			/* Never need to commute... */
+
+			/*
+			 * Now, determine which index attribute this is, change the
+			 * indexkey operand as needed, and get the index opclass.
+			 */
+			linitial(saop->args) = fix_indexqual_operand(linitial(saop->args),
+														 index,
+														 &opclass);
+			clause_op = saop->opno;
+		}
+		else
+		{
+			elog(ERROR, "unsupported indexqual type: %d",
+				 (int) nodeTag(clause));
+			continue;			/* keep compiler quiet */
+		}
 
-		*fixed_indexquals = lappend(*fixed_indexquals, newclause);
+		*fixed_indexquals = lappend(*fixed_indexquals, clause);
 
 		/*
 		 * Look up the (possibly commuted) operator in the operator class to
 		 * get its strategy numbers and the recheck indicator.	This also
 		 * double-checks that we found an operator matching the index.
 		 */
-		get_op_opclass_properties(newclause->opno, opclass,
+		get_op_opclass_properties(clause_op, opclass,
 								  &stratno, &stratsubtype, &recheck);
 
 		*indexstrategy = lappend_int(*indexstrategy, stratno);
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index f3a49b30063d7eaa6357e7e0c0fa8d2cadedc75f..f4a3c605c35fc9cefcecc339b157b73f1b3fc26d 100644
--- a/src/backend/optimizer/plan/planagg.c
+++ b/src/backend/optimizer/plan/planagg.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.11 2005/11/22 18:17:13 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.12 2005/11/25 19:47:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -347,6 +347,7 @@ build_minmax_path(PlannerInfo *root, RelOptInfo *rel, MinMaxAggInfo *info)
 												index->rel->baserestrictinfo,
 													NIL,
 													NULL,
+													SAOP_FORBID,
 													&found_clause);
 
 		if (list_length(restrictclauses) < indexcol)
diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
index dc135ae9b34d584b59a30988e97122289da3e0c2..a14117032b9004b0ed51e423091d764092d5a58e 100644
--- a/src/backend/optimizer/util/restrictinfo.c
+++ b/src/backend/optimizer/util/restrictinfo.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/util/restrictinfo.c,v 1.44 2005/11/22 18:17:15 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/util/restrictinfo.c,v 1.45 2005/11/25 19:47:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -171,7 +171,7 @@ make_restrictinfo_from_bitmapqual(Path *bitmapqual,
 
 		/*
 		 * Avoid generating one-element ORs, which could happen due to
-		 * redundancy elimination.
+		 * redundancy elimination or ScalarArrayOpExpr quals.
 		 */
 		if (list_length(withris) <= 1)
 			result = withris;
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 9ff98f05cbe61eb73d1cca34532f52cb4528e338..a92ff26260bdf3c3b6b7f6a8f0de1aaaeb4c5202 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -15,7 +15,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/adt/selfuncs.c,v 1.193 2005/11/22 18:17:23 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/selfuncs.c,v 1.194 2005/11/25 19:47:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1299,6 +1299,173 @@ nulltestsel(PlannerInfo *root, NullTestType nulltesttype,
 	return (Selectivity) selec;
 }
 
+/*
+ *		scalararraysel		- Selectivity of ScalarArrayOpExpr Node.
+ */
+Selectivity
+scalararraysel(PlannerInfo *root,
+			   ScalarArrayOpExpr *clause,
+			   bool is_join_clause,
+			   int varRelid, JoinType jointype)
+{
+	Oid			operator = clause->opno;
+	bool		useOr = clause->useOr;
+	Node	   *leftop;
+	Node	   *rightop;
+	RegProcedure oprsel;
+	FmgrInfo	oprselproc;
+	Datum		selarg4;
+	Selectivity	s1;
+
+	/*
+	 * First, look up the underlying operator's selectivity estimator.
+	 * Punt if it hasn't got one.
+	 */
+	if (is_join_clause)
+	{
+		oprsel = get_oprjoin(operator);
+		selarg4 = Int16GetDatum(jointype);
+
+	}
+	else
+	{
+		oprsel = get_oprrest(operator);
+		selarg4 = Int32GetDatum(varRelid);
+	}
+	if (!oprsel)
+		return (Selectivity) 0.5;
+	fmgr_info(oprsel, &oprselproc);
+
+	/*
+	 * We consider three cases:
+	 *
+	 * 1. rightop is an Array constant: deconstruct the array, apply the
+	 * operator's selectivity function for each array element, and merge
+	 * the results in the same way that clausesel.c does for AND/OR
+	 * combinations.
+	 *
+	 * 2. rightop is an ARRAY[] construct: apply the operator's selectivity
+	 * function for each element of the ARRAY[] construct, and merge.
+	 *
+	 * 3. otherwise, make a guess ...
+	 */
+	Assert(list_length(clause->args) == 2);
+	leftop = (Node *) linitial(clause->args);
+	rightop = (Node *) lsecond(clause->args);
+
+	if (rightop && IsA(rightop, Const))
+	{
+		Datum		arraydatum = ((Const *) rightop)->constvalue;
+		bool		arrayisnull = ((Const *) rightop)->constisnull;
+		ArrayType  *arrayval;
+		int16		elmlen;
+		bool		elmbyval;
+		char		elmalign;
+		int			num_elems;
+		Datum	   *elem_values;
+		bool	   *elem_nulls;
+		int			i;
+
+		if (arrayisnull)		/* qual can't succeed if null array */
+			return (Selectivity) 0.0;
+		arrayval = DatumGetArrayTypeP(arraydatum);
+		get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+							 &elmlen, &elmbyval, &elmalign);
+		deconstruct_array(arrayval,
+						  ARR_ELEMTYPE(arrayval),
+						  elmlen, elmbyval, elmalign,
+						  &elem_values, &elem_nulls, &num_elems);
+		s1 = useOr ? 0.0 : 1.0;
+		for (i = 0; i < num_elems; i++)
+		{
+			List	*args;
+			Selectivity s2;
+
+			args = list_make2(leftop,
+							  makeConst(ARR_ELEMTYPE(arrayval),
+										elmlen,
+										elem_values[i],
+										elem_nulls[i],
+										elmbyval));
+			s2 = DatumGetFloat8(FunctionCall4(&oprselproc,
+											  PointerGetDatum(root),
+											  ObjectIdGetDatum(operator),
+											  PointerGetDatum(args),
+											  selarg4));
+			if (useOr)
+				s1 = s1 + s2 - s1 * s2;
+			else
+				s1 = s1 * s2;
+		}
+	}
+	else if (rightop && IsA(rightop, ArrayExpr) &&
+			 !((ArrayExpr *) rightop)->multidims)
+	{
+		ArrayExpr  *arrayexpr = (ArrayExpr *) rightop;
+		int16		elmlen;
+		bool		elmbyval;
+		ListCell   *l;
+
+		get_typlenbyval(arrayexpr->element_typeid,
+						&elmlen, &elmbyval);
+		s1 = useOr ? 0.0 : 1.0;
+		foreach(l, arrayexpr->elements)
+		{
+			List	*args;
+			Selectivity s2;
+
+			args = list_make2(leftop, lfirst(l));
+			s2 = DatumGetFloat8(FunctionCall4(&oprselproc,
+											  PointerGetDatum(root),
+											  ObjectIdGetDatum(operator),
+											  PointerGetDatum(args),
+											  selarg4));
+			if (useOr)
+				s1 = s1 + s2 - s1 * s2;
+			else
+				s1 = s1 * s2;
+		}
+	}
+	else
+	{
+		CaseTestExpr *dummyexpr;
+		List	*args;
+		Selectivity s2;
+		int		i;
+
+		/*
+		 * We need a dummy rightop to pass to the operator selectivity
+		 * routine.  It can be pretty much anything that doesn't look like
+		 * a constant; CaseTestExpr is a convenient choice.
+		 */
+		dummyexpr = makeNode(CaseTestExpr);
+		dummyexpr->typeId = get_element_type(exprType(rightop));
+		dummyexpr->typeMod = -1;
+		args = list_make2(leftop, dummyexpr);
+		s2 = DatumGetFloat8(FunctionCall4(&oprselproc,
+										  PointerGetDatum(root),
+										  ObjectIdGetDatum(operator),
+										  PointerGetDatum(args),
+										  selarg4));
+		s1 = useOr ? 0.0 : 1.0;
+		/*
+		 * Arbitrarily assume 10 elements in the eventual array value
+		 */
+		for (i = 0; i < 10; i++)
+		{
+			if (useOr)
+				s1 = s1 + s2 - s1 * s2;
+			else
+				s1 = s1 * s2;
+		}
+	}
+
+	/* result should be in range, but make sure... */
+	CLAMP_PROBABILITY(s1);
+
+	return s1;
+}
+
 /*
  *		eqjoinsel		- Join selectivity of "="
  */
@@ -4330,6 +4497,7 @@ btcostestimate(PG_FUNCTION_ARGS)
 	List	   *indexBoundQuals;
 	int			indexcol;
 	bool		eqQualHere;
+	bool		found_saop;
 	ListCell   *l;
 
 	/*
@@ -4341,26 +4509,52 @@ btcostestimate(PG_FUNCTION_ARGS)
 	 * for estimating numIndexTuples.  So we must examine the given indexQuals
 	 * to find out which ones count as boundary quals.	We rely on the
 	 * knowledge that they are given in index column order.
+	 *
+	 * If there's a ScalarArrayOpExpr in the quals, we'll actually perform
+	 * N index scans not one, but the ScalarArrayOpExpr's operator can be
+	 * considered to act the same as it normally does.
 	 */
 	indexBoundQuals = NIL;
 	indexcol = 0;
 	eqQualHere = false;
+	found_saop = false;
 	foreach(l, indexQuals)
 	{
 		RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
 		Expr	   *clause;
+		Node	   *leftop,
+				   *rightop;
 		Oid			clause_op;
 		int			op_strategy;
 
 		Assert(IsA(rinfo, RestrictInfo));
 		clause = rinfo->clause;
-		Assert(IsA(clause, OpExpr));
-		clause_op = ((OpExpr *) clause)->opno;
-		if (match_index_to_operand(get_leftop(clause), indexcol, index))
+		if (IsA(clause, OpExpr))
+		{
+			leftop = get_leftop(clause);
+			rightop = get_rightop(clause);
+			clause_op = ((OpExpr *) clause)->opno;
+		}
+		else if (IsA(clause, ScalarArrayOpExpr))
+		{
+			ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
+
+			leftop = (Node *) linitial(saop->args);
+			rightop = (Node *) lsecond(saop->args);
+			clause_op = saop->opno;
+			found_saop = true;
+		}
+		else
+		{
+			elog(ERROR, "unsupported indexqual type: %d",
+				 (int) nodeTag(clause));
+			continue;			/* keep compiler quiet */
+		}
+		if (match_index_to_operand(leftop, indexcol, index))
 		{
 			/* clause_op is correct */
 		}
-		else if (match_index_to_operand(get_rightop(clause), indexcol, index))
+		else if (match_index_to_operand(rightop, indexcol, index))
 		{
 			/* Must flip operator to get the opclass member */
 			clause_op = get_commutator(clause_op);
@@ -4372,12 +4566,11 @@ btcostestimate(PG_FUNCTION_ARGS)
 				break;			/* done if no '=' qual for indexcol */
 			indexcol++;
 			eqQualHere = false;
-			if (match_index_to_operand(get_leftop(clause), indexcol, index))
+			if (match_index_to_operand(leftop, indexcol, index))
 			{
 				/* clause_op is correct */
 			}
-			else if (match_index_to_operand(get_rightop(clause),
-											indexcol, index))
+			else if (match_index_to_operand(rightop, indexcol, index))
 			{
 				/* Must flip operator to get the opclass member */
 				clause_op = get_commutator(clause_op);
@@ -4401,7 +4594,10 @@ btcostestimate(PG_FUNCTION_ARGS)
 	 * just assume numIndexTuples = 1 and skip the expensive
 	 * clauselist_selectivity calculations.
 	 */
-	if (index->unique && indexcol == index->ncolumns - 1 && eqQualHere)
+	if (index->unique &&
+		indexcol == index->ncolumns - 1 &&
+		eqQualHere &&
+		!found_saop)
 		numIndexTuples = 1.0;
 	else
 	{
@@ -4424,7 +4620,14 @@ btcostestimate(PG_FUNCTION_ARGS)
 	 * is that multiple columns dilute the importance of the first column's
 	 * ordering, but don't negate it entirely.  Before 8.0 we divided the
 	 * correlation by the number of columns, but that seems too strong.)
+	 *
+	 * We can skip all this if we found a ScalarArrayOpExpr, because then
+	 * the call must be for a bitmap index scan, and the caller isn't going
+	 * to care what the index correlation is.
 	 */
+	if (found_saop)
+		PG_RETURN_VOID();
+
 	if (index->indexkeys[0] != 0)
 	{
 		/* Simple variable --- look to stats for the underlying table */
diff --git a/src/include/executor/nodeIndexscan.h b/src/include/executor/nodeIndexscan.h
index 7f280c892e18a72b1bcecbfae0d2ae9df2f3269c..21bb254f63e898501092593216b025a39ecbf103 100644
--- a/src/include/executor/nodeIndexscan.h
+++ b/src/include/executor/nodeIndexscan.h
@@ -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/executor/nodeIndexscan.h,v 1.24 2005/10/15 02:49:44 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/executor/nodeIndexscan.h,v 1.25 2005/11/25 19:47:50 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -25,13 +25,15 @@ extern void ExecIndexRestrPos(IndexScanState *node);
 extern void ExecIndexReScan(IndexScanState *node, ExprContext *exprCtxt);
 
 /* routines exported to share code with nodeBitmapIndexscan.c */
-extern bool ExecIndexBuildScanKeys(PlanState *planstate, List *quals,
+extern void ExecIndexBuildScanKeys(PlanState *planstate, List *quals,
 					   List *strategies, List *subtypes,
-					   ExprState ***runtimeKeyInfo,
-					   ScanKey *scanKeys, int *numScanKeys);
+					   ScanKey *scanKeys, int *numScanKeys,
+					   IndexRuntimeKeyInfo **runtimeKeys, int *numRuntimeKeys,
+					   IndexArrayKeyInfo **arrayKeys, int *numArrayKeys);
 extern void ExecIndexEvalRuntimeKeys(ExprContext *econtext,
-						 ExprState **run_keys,
-						 ScanKey scan_keys,
-						 int n_keys);
+						 IndexRuntimeKeyInfo *runtimeKeys, int numRuntimeKeys);
+extern bool ExecIndexEvalArrayKeys(ExprContext *econtext,
+						 IndexArrayKeyInfo *arrayKeys, int numArrayKeys);
+extern bool ExecIndexAdvanceArrayKeys(IndexArrayKeyInfo *arrayKeys, int numArrayKeys);
 
 #endif   /* NODEINDEXSCAN_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 63e864e4636bdcad27ff86b47f2a3309a83f57b6..f70847798e662bbcb16e5898de554cdfafa8ba2a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -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.141 2005/11/22 18:17:30 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.142 2005/11/25 19:47:50 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -871,16 +871,37 @@ typedef struct ScanState
  */
 typedef ScanState SeqScanState;
 
+/*
+ * These structs store information about index quals that don't have simple
+ * constant right-hand sides.  See comments for ExecIndexBuildScanKeys()
+ * for discussion.
+ */
+typedef struct
+{
+	ScanKey		scan_key;		/* scankey to put value into */
+	ExprState  *key_expr;		/* expr to evaluate to get value */
+} IndexRuntimeKeyInfo;
+
+typedef struct
+{
+	ScanKey		scan_key;		/* scankey to put value into */
+	ExprState  *array_expr;		/* expr to evaluate to get array value */
+	int			next_elem;		/* next array element to use */
+	int			num_elems;		/* number of elems in current array value */
+	Datum	   *elem_values;	/* array of num_elems Datums */
+	bool	   *elem_nulls;		/* array of num_elems is-null flags */
+} IndexArrayKeyInfo;
+
 /* ----------------
  *	 IndexScanState information
  *
  *		indexqualorig	   execution state for indexqualorig expressions
  *		ScanKeys		   Skey structures to scan index rel
  *		NumScanKeys		   number of Skey structs
- *		RuntimeKeyInfo	   array of exprstates for Skeys
- *						   that will be evaluated at runtime
- *		RuntimeContext	   expr context for evaling runtime Skeys
+ *		RuntimeKeys		   info about Skeys that must be evaluated at runtime
+ *		NumRuntimeKeys	   number of RuntimeKeys structs
  *		RuntimeKeysReady   true if runtime Skeys have been computed
+ *		RuntimeContext	   expr context for evaling runtime Skeys
  *		RelationDesc	   index relation descriptor
  *		ScanDesc		   index scan descriptor
  * ----------------
@@ -891,9 +912,10 @@ typedef struct IndexScanState
 	List	   *indexqualorig;
 	ScanKey		iss_ScanKeys;
 	int			iss_NumScanKeys;
-	ExprState **iss_RuntimeKeyInfo;
-	ExprContext *iss_RuntimeContext;
+	IndexRuntimeKeyInfo *iss_RuntimeKeys;
+	int			iss_NumRuntimeKeys;
 	bool		iss_RuntimeKeysReady;
+	ExprContext *iss_RuntimeContext;
 	Relation	iss_RelationDesc;
 	IndexScanDesc iss_ScanDesc;
 } IndexScanState;
@@ -904,10 +926,12 @@ typedef struct IndexScanState
  *		result			   bitmap to return output into, or NULL
  *		ScanKeys		   Skey structures to scan index rel
  *		NumScanKeys		   number of Skey structs
- *		RuntimeKeyInfo	   array of exprstates for Skeys
- *						   that will be evaluated at runtime
- *		RuntimeContext	   expr context for evaling runtime Skeys
+ *		RuntimeKeys		   info about Skeys that must be evaluated at runtime
+ *		NumRuntimeKeys	   number of RuntimeKeys structs
+ *		ArrayKeys		   info about Skeys that come from ScalarArrayOpExprs
+ *		NumArrayKeys	   number of ArrayKeys structs
  *		RuntimeKeysReady   true if runtime Skeys have been computed
+ *		RuntimeContext	   expr context for evaling runtime Skeys
  *		RelationDesc	   index relation descriptor
  *		ScanDesc		   index scan descriptor
  * ----------------
@@ -918,9 +942,12 @@ typedef struct BitmapIndexScanState
 	TIDBitmap  *biss_result;
 	ScanKey		biss_ScanKeys;
 	int			biss_NumScanKeys;
-	ExprState **biss_RuntimeKeyInfo;
-	ExprContext *biss_RuntimeContext;
+	IndexRuntimeKeyInfo *biss_RuntimeKeys;
+	int			biss_NumRuntimeKeys;
+	IndexArrayKeyInfo *biss_ArrayKeys;
+	int			biss_NumArrayKeys;
 	bool		biss_RuntimeKeysReady;
+	ExprContext *biss_RuntimeContext;
 	Relation	biss_RelationDesc;
 	IndexScanDesc biss_ScanDesc;
 } BitmapIndexScanState;
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 4020f4bf49a3dede46f49290cd9c0a66b9122aea..eba65c699c057afa05400616a04598fbbdcf5109 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -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/optimizer/paths.h,v 1.88 2005/10/15 02:49:45 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/paths.h,v 1.89 2005/11/25 19:47:50 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -34,6 +34,14 @@ extern void debug_print_rel(PlannerInfo *root, RelOptInfo *rel);
  * indxpath.c
  *	  routines to generate index paths
  */
+typedef enum
+{
+	/* Whether to use ScalarArrayOpExpr to build index qualifications */
+	SAOP_FORBID,				/* Do not use ScalarArrayOpExpr */
+	SAOP_ALLOW,					/* OK to use ScalarArrayOpExpr */
+	SAOP_REQUIRE				/* Require ScalarArrayOpExpr */
+} SaOpControl;
+
 extern void create_index_paths(PlannerInfo *root, RelOptInfo *rel);
 extern List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
 						 List *clauses, List *outer_clauses,
@@ -44,6 +52,7 @@ extern Path *best_inner_indexscan(PlannerInfo *root, RelOptInfo *rel,
 extern List *group_clauses_by_indexkey(IndexOptInfo *index,
 						  List *clauses, List *outer_clauses,
 						  Relids outer_relids,
+						  SaOpControl saop_control,
 						  bool *found_clause);
 extern bool match_index_to_operand(Node *operand, int indexcol,
 					   IndexOptInfo *index);
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
index 7ba2dde1d90c45b0ea4202d3d1f326b534c097c6..2c021ad25022881f64594f190dd36c0592c2d54a 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -8,7 +8,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/selfuncs.h,v 1.25 2005/11/07 17:36:47 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/selfuncs.h,v 1.26 2005/11/25 19:47:50 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -112,6 +112,10 @@ extern Selectivity booltestsel(PlannerInfo *root, BoolTestType booltesttype,
 			Node *arg, int varRelid, JoinType jointype);
 extern Selectivity nulltestsel(PlannerInfo *root, NullTestType nulltesttype,
 			Node *arg, int varRelid);
+extern Selectivity scalararraysel(PlannerInfo *root,
+								  ScalarArrayOpExpr *clause,
+								  bool is_join_clause,
+								  int varRelid, JoinType jointype);
 
 extern void mergejoinscansel(PlannerInfo *root, Node *clause,
 				 Selectivity *leftscan,