diff --git a/src/backend/access/common/indexvalid.c b/src/backend/access/common/indexvalid.c
index df72a69de47425a7164daab29e8980eb3bb57042..aff9af42f8df1de69589e3aeb0b980d168623e7a 100644
--- a/src/backend/access/common/indexvalid.c
+++ b/src/backend/access/common/indexvalid.c
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *    $Header: /cvsroot/pgsql/src/backend/access/common/Attic/indexvalid.c,v 1.13 1997/03/12 20:56:32 scrappy Exp $
+ *    $Header: /cvsroot/pgsql/src/backend/access/common/Attic/indexvalid.c,v 1.14 1997/03/18 18:38:19 scrappy Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -48,7 +48,7 @@ index_keytest(IndexTuple tuple,
     
     while (scanKeySize > 0) {
 	datum = index_getattr(tuple,
-			      1,
+			      key[0].sk_attno,
 			      tupdesc,
 			      &isNull);
 	
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 8317789a94a9566b45f536cbf8fa0b4a8f41d04c..0fe6787c010e6e02a07bde294c9d9d47917c6f6b 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *    $Header: /cvsroot/pgsql/src/backend/access/nbtree/nbtree.c,v 1.15 1997/02/22 10:04:14 vadim Exp $
+ *    $Header: /cvsroot/pgsql/src/backend/access/nbtree/nbtree.c,v 1.16 1997/03/18 18:38:35 scrappy Exp $
  *
  * NOTES
  *    This file contains only the public interface routines.
@@ -423,6 +423,7 @@ btrescan(IndexScanDesc scan, bool fromEnd, ScanKey scankey)
     
     /* reset the scan key */
     so->numberOfKeys = scan->numberOfKeys;
+    so->numberOfFirstKeys = 0;
     so->qual_ok = 1;			/* may be changed by _bt_orderkeys */
     if (scan->numberOfKeys > 0) {
 	memmove(scan->keyData,
@@ -433,7 +434,9 @@ btrescan(IndexScanDesc scan, bool fromEnd, ScanKey scankey)
 		so->numberOfKeys * sizeof(ScanKeyData));
 	/* order the keys in the qualification */
 	if (so->numberOfKeys > 1)
-		_bt_orderkeys(scan->relation, &so->numberOfKeys, so->keyData, &so->qual_ok);
+	    _bt_orderkeys(scan->relation, so);
+	else
+	    so->numberOfFirstKeys = 1;
     }
     
     /* finally, be sure that the scan exploits the tree order */
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 94521ff1c0b233f7591a3b9e25a5e14febbcbf8b..2e802ee852728f0d8b7a6bf92708321e3ae13066 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *    $Header: /cvsroot/pgsql/src/backend/access/nbtree/nbtsearch.c,v 1.14 1997/02/18 17:13:48 momjian Exp $
+ *    $Header: /cvsroot/pgsql/src/backend/access/nbtree/nbtsearch.c,v 1.15 1997/03/18 18:38:41 scrappy Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -562,7 +562,6 @@ _bt_next(IndexScanDesc scan, ScanDirection dir)
     Page page;
     OffsetNumber offnum;
     RetrieveIndexResult res;
-    BlockNumber blkno;
     ItemPointer current;
     BTItem btitem;
     IndexTuple itup;
@@ -584,31 +583,35 @@ _bt_next(IndexScanDesc scan, ScanDirection dir)
     
     /* we still have the buffer pinned and locked */
     buf = so->btso_curbuf;
-    blkno = BufferGetBlockNumber(buf);
     
-    /* step one tuple in the appropriate direction */
-    if (!_bt_step(scan, &buf, dir))
-	return ((RetrieveIndexResult) NULL);
+    do
+    {
+    	/* step one tuple in the appropriate direction */
+    	if (!_bt_step(scan, &buf, dir))
+	    return ((RetrieveIndexResult) NULL);
     
-    /* by here, current is the tuple we want to return */
-    offnum = ItemPointerGetOffsetNumber(current);
-    page = BufferGetPage(buf);
-    btitem = (BTItem) PageGetItem(page, PageGetItemId(page, offnum));
-    itup = &btitem->bti_itup;
+    	/* by here, current is the tuple we want to return */
+    	offnum = ItemPointerGetOffsetNumber(current);
+    	page = BufferGetPage(buf);
+    	btitem = (BTItem) PageGetItem(page, PageGetItemId(page, offnum));
+    	itup = &btitem->bti_itup;
     
-    if (_bt_checkqual(scan, itup)) {
-	res = FormRetrieveIndexResult(current, &(itup->t_tid));
+    	if (_bt_checkqual(scan, itup)) 
+    	{
+	    res = FormRetrieveIndexResult(current, &(itup->t_tid));
 	
-	/* remember which buffer we have pinned and locked */
-	so->btso_curbuf = buf;
-    } else {
-	ItemPointerSetInvalid(current);
-	so->btso_curbuf = InvalidBuffer;
-	_bt_relbuf(rel, buf, BT_READ);
-	res = (RetrieveIndexResult) NULL;
-    }
+	    /* remember which buffer we have pinned and locked */
+	    so->btso_curbuf = buf;
+	    return (res);
+	}
+
+    } while ( _bt_checkforkeys (scan, itup, so->numberOfFirstKeys) );
+
+    ItemPointerSetInvalid(current);
+    so->btso_curbuf = InvalidBuffer;
+    _bt_relbuf(rel, buf, BT_READ);
     
-    return (res);
+    return ((RetrieveIndexResult) NULL);
 }
 
 /*
@@ -660,13 +663,6 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
      *  ordered to take advantage of index ordering) to position ourselves
      *  at the right place in the scan.
      */
-    
-    /*
-     *  XXX -- The attribute number stored in the scan key is the attno
-     *	       in the heap relation.  We need to transmogrify this into
-     *         the index relation attno here.  For the moment, we have
-     *	       hardwired attno == 1.
-     */
     proc = index_getprocid(rel, 1, BTORDER_PROC);
     ScanKeyEntryInitialize(&skdata, so->keyData[0].sk_flags, 1, proc,
 			   so->keyData[0].sk_argument);
@@ -802,12 +798,20 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
     btitem = (BTItem) PageGetItem(page, PageGetItemId(page, offnum));
     itup = &btitem->bti_itup;
     
-    if (_bt_checkqual(scan, itup)) {
+    if ( _bt_checkqual(scan, itup) )
+    {
 	res = FormRetrieveIndexResult(current, &(itup->t_tid));
 	
 	/* remember which buffer we have pinned */
 	so->btso_curbuf = buf;
-    } else {
+    }
+    else if ( _bt_checkforkeys (scan, itup, so->numberOfFirstKeys) )
+    {
+	so->btso_curbuf = buf;
+	return (_bt_next (scan, dir));
+    }
+    else
+    {
 	ItemPointerSetInvalid(current);
 	so->btso_curbuf = InvalidBuffer;
 	_bt_relbuf(rel, buf, BT_READ);
@@ -1224,7 +1228,14 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 	
 	/* remember which buffer we have pinned */
 	so->btso_curbuf = buf;
-    } else {
+    }
+    else if ( _bt_checkforkeys (scan, itup, so->numberOfFirstKeys) )
+    {
+	so->btso_curbuf = buf;
+	return (_bt_next (scan, dir));
+    }
+    else
+    {
 	_bt_relbuf(rel, buf, BT_READ);
 	res = (RetrieveIndexResult) NULL;
     }
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index 703acd62fa27260c80a9cb4b31f4c14750860eaa..6d0a40ef132fbdbdfac92de0676cb6008d4ca501 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *    $Header: /cvsroot/pgsql/src/backend/access/nbtree/nbtutils.c,v 1.7 1996/11/05 10:35:38 scrappy Exp $
+ *    $Header: /cvsroot/pgsql/src/backend/access/nbtree/nbtutils.c,v 1.8 1997/03/18 18:38:46 scrappy Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -80,7 +80,7 @@ _bt_freestack(BTStack stack)
  *	more than one qual clauses using this index.
  */
 void
-_bt_orderkeys(Relation relation, uint16 *numberOfKeys, ScanKey key, uint16 *qual_ok)
+_bt_orderkeys(Relation relation, BTScanOpaque so)
 {
     ScanKey xform;
     ScanKeyData *cur;
@@ -89,42 +89,137 @@ _bt_orderkeys(Relation relation, uint16 *numberOfKeys, ScanKey key, uint16 *qual
     long test;
     int i, j;
     int init[BTMaxStrategyNumber+1];
+    ScanKey key;
+    uint16 numberOfKeys, new_numberOfKeys = 0;
+    AttrNumber attno = 1;
     
-    /* haven't looked at any strategies yet */
-    for (i = 0; i <= BTMaxStrategyNumber; i++)
-	init[i] = 0;
+    numberOfKeys = so->numberOfKeys;
+    key = so->keyData;
+    
+    if ( numberOfKeys <= 1 )
+    	return;
     
     /* get space for the modified array of keys */
     nbytes = BTMaxStrategyNumber * sizeof(ScanKeyData);
     xform = (ScanKey) palloc(nbytes);
-    memset(xform, 0, nbytes); 
     
-    
-    /* get the strategy map for this index/attribute pair */
-    /*
-     *  XXX
-     *  When we support multiple keys in a single index, this is what
-     *  we'll want to do.  At present, the planner is hosed, so we
-     *  hard-wire the attribute number below.  Postgres only does single-
-     *  key indices...
-     * map = IndexStrategyGetStrategyMap(RelationGetIndexStrategy(relation),
-     * 				    BTMaxStrategyNumber,
-     * 				    key->data[0].attributeNumber);
-     */
+    cur = &key[0];
+    if ( cur->sk_attno != 1 )
+	elog (WARN, "_bt_orderkeys: key(s) for attribute 1 missed");
+
+    memset(xform, 0, nbytes); 
     map = IndexStrategyGetStrategyMap(RelationGetIndexStrategy(relation),
 				      BTMaxStrategyNumber,
-				      1 /* XXX */ );
+				      attno);
+    for (j = 0; j <= BTMaxStrategyNumber; j++)
+	init[j] = 0;
     
     /* check each key passed in */
-    for (i = *numberOfKeys; --i >= 0; ) {
-	cur = &key[i];
-	for (j = BTMaxStrategyNumber; --j >= 0; ) {
+    for (i = 0; ; )
+    {
+	if ( i < numberOfKeys )
+	    cur = &key[i];
+	if ( i == numberOfKeys || cur->sk_attno != attno )
+	{
+	    if ( cur->sk_attno != attno + 1 && i < numberOfKeys )
+	    {
+	    	elog (WARN, "_bt_orderkeys: key(s) for attribute %d missed", attno + 1);
+	    }
+	    /* 
+	     * If = has been specified, no other key will be used.
+	     * In case of key < 2 && key == 1 and so on 
+     	     * we have to set qual_ok to 0
+     	     */
+	    if (init[BTEqualStrategyNumber - 1])
+	    {
+		ScanKeyData *eq, *chk;
+
+		eq = &xform[BTEqualStrategyNumber - 1];
+		for (j = BTMaxStrategyNumber; --j >= 0; )
+		{
+		    if ( j == (BTEqualStrategyNumber - 1) || init[j] == 0 )
+			continue;
+		    chk = &xform[j];
+		    test = (long) fmgr(chk->sk_procedure, eq->sk_argument, chk->sk_argument);
+		    if (!test)
+			so->qual_ok = 0;
+		}
+		init[BTLessStrategyNumber - 1] = 0;
+		init[BTLessEqualStrategyNumber - 1] = 0;
+		init[BTGreaterEqualStrategyNumber - 1] = 0;
+		init[BTGreaterStrategyNumber - 1] = 0;
+	    }
+    
+	    /* only one of <, <= */
+	    if (init[BTLessStrategyNumber - 1]
+			&& init[BTLessEqualStrategyNumber - 1])
+	    {
+		ScanKeyData *lt, *le;
+	
+		lt = &xform[BTLessStrategyNumber - 1];
+		le = &xform[BTLessEqualStrategyNumber - 1];
+		/*
+		 *  DO NOT use the cached function stuff here -- this is key
+		 *  ordering, happens only when the user expresses a hokey
+		 *  qualification, and gets executed only once, anyway.  The
+		 *  transform maps are hard-coded, and can't be initialized
+		 *  in the correct way.
+		 */
+		test = (long) fmgr(le->sk_procedure, lt->sk_argument, le->sk_argument);
+		if (test)
+	    	    init[BTLessEqualStrategyNumber - 1] = 0;
+		else
+		    init[BTLessStrategyNumber - 1] = 0;
+    	    }
+    
+	    /* only one of >, >= */
+	    if (init[BTGreaterStrategyNumber - 1]
+			&& init[BTGreaterEqualStrategyNumber - 1])
+	    {
+	    	ScanKeyData *gt, *ge;
+	
+		gt = &xform[BTGreaterStrategyNumber - 1];
+		ge = &xform[BTGreaterEqualStrategyNumber - 1];
+	
+		/* see note above on function cache */
+		test = (long) fmgr(ge->sk_procedure, gt->sk_argument, ge->sk_argument);
+		if (test)
+	    	    init[BTGreaterEqualStrategyNumber - 1] = 0;
+		else
+	    	    init[BTGreaterStrategyNumber - 1] = 0;
+    	    }
+    
+    	    /* okay, reorder and count */
+    	    for (j = BTMaxStrategyNumber; --j >= 0; )
+	    	if (init[j])
+		    key[new_numberOfKeys++] = xform[j];
+    
+    	    if ( attno == 1 )
+    		so->numberOfFirstKeys = new_numberOfKeys;
+    	    
+    	    if ( i == numberOfKeys )
+    	    	break;
+
+	    /* initialization for new attno */    	    
+    	    attno = cur->sk_attno;
+    	    memset(xform, 0, nbytes); 
+	    map = IndexStrategyGetStrategyMap(RelationGetIndexStrategy(relation),
+				      BTMaxStrategyNumber,
+				      attno);
+	    /* haven't looked at any strategies yet */
+	    for (j = 0; j <= BTMaxStrategyNumber; j++)
+		init[j] = 0;
+	}
+
+	for (j = BTMaxStrategyNumber; --j >= 0; )
+	{
 	    if (cur->sk_procedure == map->entry[j].sk_procedure)
-		break;
+	    	break;
 	}
 	
 	/* have we seen one of these before? */
-	if (init[j]) {
+	if (init[j])
+	{
 	    /* yup, use the appropriate value */
 	    test =
 		(long) FMGR_PTR2(cur->sk_func, cur->sk_procedure,
@@ -132,97 +227,18 @@ _bt_orderkeys(Relation relation, uint16 *numberOfKeys, ScanKey key, uint16 *qual
 	    if (test)
 		xform[j].sk_argument = cur->sk_argument;
 	    else if ( j == (BTEqualStrategyNumber - 1) )
-	    	*qual_ok = 0;		/* key == a && key == b, but a != b */
-	} else {
+	    	so->qual_ok = 0;	/* key == a && key == b, but a != b */
+	} else
+	{
 	    /* nope, use this value */
 	    memmove(&xform[j], cur, sizeof(*cur));
-	   
 	    init[j] = 1;
 	}
-    }
-    
-    /* if = has been specified, no other key will be used */
-    /*
-     * XXX
-     * But in case of key < 2 && key == 1 and so on 
-     * we have to set qual_ok to 0
-     */
-    if (init[BTEqualStrategyNumber - 1]) {
-
-	ScanKeyData *eq, *chk;
-
-	eq = &xform[BTEqualStrategyNumber - 1];
-
-	for (j = BTMaxStrategyNumber; --j >= 0; )
-	{
-		if ( j == (BTEqualStrategyNumber - 1) || init[j] == 0 )
-			continue;
-
-		chk = &xform[j];
-
-		test = (long) fmgr(chk->sk_procedure, eq->sk_argument, chk->sk_argument);
 	
-		if (!test)
-			*qual_ok = 0;
-	}
-
-	init[BTLessStrategyNumber - 1] = 0;
-	init[BTLessEqualStrategyNumber - 1] = 0;
-	init[BTGreaterEqualStrategyNumber - 1] = 0;
-	init[BTGreaterStrategyNumber - 1] = 0;
+	i++;
     }
     
-    /* only one of <, <= */
-    if (init[BTLessStrategyNumber - 1]
-	&& init[BTLessEqualStrategyNumber - 1]) {
-	
-	ScanKeyData *lt, *le;
-	
-	lt = &xform[BTLessStrategyNumber - 1];
-	le = &xform[BTLessEqualStrategyNumber - 1];
-	
-	/*
-	 *  DO NOT use the cached function stuff here -- this is key
-	 *  ordering, happens only when the user expresses a hokey
-	 *  qualification, and gets executed only once, anyway.  The
-	 *  transform maps are hard-coded, and can't be initialized
-	 *  in the correct way.
-	 */
-	
-	test = (long) fmgr(le->sk_procedure, lt->sk_argument, le->sk_argument);
-	
-	if (test)
-	    init[BTLessEqualStrategyNumber - 1] = 0;
-	else
-	    init[BTLessStrategyNumber - 1] = 0;
-    }
-    
-    /* only one of >, >= */
-    if (init[BTGreaterStrategyNumber - 1]
-	&& init[BTGreaterEqualStrategyNumber - 1]) {
-	
-	ScanKeyData *gt, *ge;
-	
-	gt = &xform[BTGreaterStrategyNumber - 1];
-	ge = &xform[BTGreaterEqualStrategyNumber - 1];
-	
-	/* see note above on function cache */
-	test = (long) fmgr(ge->sk_procedure, gt->sk_argument, ge->sk_argument);
-	
-	if (test)
-	    init[BTGreaterEqualStrategyNumber - 1] = 0;
-	else
-	    init[BTGreaterStrategyNumber - 1] = 0;
-    }
-    
-    /* okay, reorder and count */
-    j = 0;
-    
-    for (i = BTMaxStrategyNumber; --i >= 0; )
-	if (init[i])
-	    key[j++] = xform[i];
-    
-    *numberOfKeys = j;
+    so->numberOfKeys = new_numberOfKeys;
     
     pfree(xform);
 }
@@ -230,9 +246,25 @@ _bt_orderkeys(Relation relation, uint16 *numberOfKeys, ScanKey key, uint16 *qual
 bool
 _bt_checkqual(IndexScanDesc scan, IndexTuple itup)
 {
-    if (scan->numberOfKeys > 0)
+    BTScanOpaque so;
+    
+    so = (BTScanOpaque) scan->opaque;
+    if (so->numberOfKeys > 0)
+	return (index_keytest(itup, RelationGetTupleDescriptor(scan->relation),
+			      so->numberOfKeys, so->keyData));
+    else
+	return (true);
+}
+
+bool
+_bt_checkforkeys(IndexScanDesc scan, IndexTuple itup, Size keysz)
+{
+    BTScanOpaque so;
+    
+    so = (BTScanOpaque) scan->opaque;
+    if ( keysz > 0 && so->numberOfKeys >= keysz )
 	return (index_keytest(itup, RelationGetTupleDescriptor(scan->relation),
-			      scan->numberOfKeys, scan->keyData));
+			      keysz, so->keyData));
     else
 	return (true);
 }
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 5a86749a66f26e2475c03f44aba4ded7a28ee3fd..db836e9ab17221cb3df80744f2fb724ebcce768e 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *    $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.6 1997/03/12 21:00:17 scrappy Exp $
+ *    $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.7 1997/03/18 18:39:40 scrappy Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,8 +53,9 @@ static bool match_index_to_operand(int indexkey, Expr *operand,
 static List *match_index_orclause(Rel *rel, Rel *index, int indexkey,
     int xclass, List *or_clauses, List *other_matching_indices);
 static List *group_clauses_by_indexkey(Rel *rel, Rel *index,
-    int *indexkeys, Oid *classes, List *clauseinfo_list,
-    bool join);
+    int *indexkeys, Oid *classes, List *clauseinfo_list);
+static List *group_clauses_by_ikey_for_joins(Rel *rel, Rel *index,
+    int *indexkeys, Oid *classes, List *join_cinfo_list, List *restr_cinfo_list);
 static CInfo *match_clause_to_indexkey(Rel *rel, Rel *index, int indexkey,
     int xclass, CInfo *clauseInfo, bool join);
 static bool pred_test(List *predicate_list, List *clauseinfo_list,
@@ -63,7 +64,8 @@ static bool one_pred_test(Expr *predicate, List *clauseinfo_list);
 static bool one_pred_clause_expr_test(Expr *predicate, Node *clause);
 static bool one_pred_clause_test(Expr *predicate, Node *clause);
 static bool clause_pred_clause_test(Expr *predicate, Node *clause);
-static List *indexable_joinclauses(Rel *rel, Rel *index, List *joininfo_list);
+static List *indexable_joinclauses (Rel *rel, Rel *index, 
+		List *joininfo_list, List *clauseinfo_list);
 static List *index_innerjoin(Query *root, Rel *rel, 
 			     List *clausegroup_list, Rel *index);
 static List *create_index_paths(Query *root, Rel *rel, Rel *index,
@@ -114,7 +116,6 @@ find_index_paths (Query *root,
     List *joinclausegroups = NIL;
     List *joinpaths = NIL;
     List *retval = NIL;
-    extern List *add_index_paths();
     
     if(indices == NIL)
 	return(NULL);
@@ -160,8 +161,7 @@ find_index_paths (Query *root,
 						 index,
 						 index->indexkeys,
 						 index->classlist,
-						 clauseinfo_list,
-						 false);
+						 clauseinfo_list);
     
     scanpaths = NIL;
     if (scanclausegroups != NIL)
@@ -178,7 +178,7 @@ find_index_paths (Query *root,
      * useful for a mergejoin, or if the index can possibly be 
      * used for scanning the inner relation of a nestloop join. 
      */
-    joinclausegroups = indexable_joinclauses(rel,index,joininfo_list);
+    joinclausegroups = indexable_joinclauses(rel,index,joininfo_list, clauseinfo_list);
     joinpaths = NIL;
 
     if (joinclausegroups != NIL)
@@ -375,10 +375,8 @@ match_index_orclause(Rel *rel,
  * 	(2) a list of join clauses between 'rel' and a fixed set of
  * 		relations,
  * 	depending on the value of 'join'.
- * 'startlist' is a list of those clause nodes that have matched the keys 
- * 	that have already been checked.
- * 'join' is a flag indicating that the clauses being checked are join
- * 	clauses.
+ *
+ *	NOTE: it works now for restriction clauses only. - vadim 03/18/97
  *    
  * Returns all possible groups of clauses that will match (given that
  * one or more clauses can match any of the remaining keys).
@@ -391,45 +389,144 @@ group_clauses_by_indexkey(Rel *rel,
 			  Rel *index,
 			  int *indexkeys,
 			  Oid *classes,
-			  List *clauseinfo_list,
-			  bool join)
+			  List *clauseinfo_list)
 {
     List *curCinfo		= NIL;
     CInfo *matched_clause	= (CInfo*)NULL;
     List *clausegroup	= NIL;
-
+    int curIndxKey;
+    Oid curClass;
 
     if (clauseinfo_list == NIL)
 	return NIL;
 
-    foreach (curCinfo,clauseinfo_list) {
-	CInfo *temp	= (CInfo*)lfirst(curCinfo);
-	int *curIndxKey	= indexkeys;
-	Oid *curClass	= classes;
+    while ( !DoneMatchingIndexKeys(indexkeys, index) )
+    {
+    	List *tempgroup = NIL;
+    	
+    	curIndxKey = indexkeys[0];
+	curClass = classes[0];
+
+    	foreach (curCinfo,clauseinfo_list) 
+    	{
+	    CInfo *temp	= (CInfo*)lfirst(curCinfo);
+
+	    matched_clause = match_clause_to_indexkey (rel, 
+						       index, 
+						       curIndxKey,
+						       curClass,
+						       temp,
+						       false);
+	    if (!matched_clause)
+		continue;
+
+	    tempgroup = lappend(tempgroup, matched_clause);
+	}
+	if ( tempgroup == NIL )
+	    break;
+
+	clausegroup = nconc (clausegroup, tempgroup);
+	
+	indexkeys++;
+	classes++;
+	
+    }
+
+    /* clausegroup holds all matched clauses ordered by indexkeys */
+
+    if (clausegroup != NIL)
+	return(lcons(clausegroup, NIL));
+    return NIL;
+}
+
+/*    
+ * group-clauses-by-ikey-for-joins--
+ *    special edition of group-clauses-by-indexkey - will
+ *    match join & restriction clauses. See comment in indexable_joinclauses.
+ *	- vadim 03/18/97
+ *    
+ */
+static List *
+group_clauses_by_ikey_for_joins(Rel *rel,
+			  Rel *index,
+			  int *indexkeys,
+			  Oid *classes,
+			  List *join_cinfo_list,
+			  List *restr_cinfo_list)
+{
+    List *curCinfo		= NIL;
+    CInfo *matched_clause	= (CInfo*)NULL;
+    List *clausegroup	= NIL;
+    int curIndxKey;
+    Oid curClass;
+    bool jfound = false;
+
+    if (join_cinfo_list == NIL)
+	return NIL;
+
+    while ( !DoneMatchingIndexKeys(indexkeys, index) )
+    {
+    	List *tempgroup = NIL;
+    	
+    	curIndxKey = indexkeys[0];
+	curClass = classes[0];
+
+    	foreach (curCinfo,join_cinfo_list) 
+    	{
+	    CInfo *temp	= (CInfo*)lfirst(curCinfo);
+
+	    matched_clause = match_clause_to_indexkey (rel, 
+						       index, 
+						       curIndxKey,
+						       curClass,
+						       temp,
+						       true);
+	    if (!matched_clause)
+		continue;
+
+	    tempgroup = lappend(tempgroup, matched_clause);
+	    jfound = true;
+	}
+    	foreach (curCinfo,restr_cinfo_list) 
+    	{
+	    CInfo *temp	= (CInfo*)lfirst(curCinfo);
 
-	do {
-	    /*
-	     * If we can't find any matching clauses for the first of 
-	     * the remaining keys, give up.
-	     */
 	    matched_clause = match_clause_to_indexkey (rel, 
 						       index, 
-						       curIndxKey[0],
-						       curClass[0],
+						       curIndxKey,
+						       curClass,
 						       temp,
-						       join);
+						       false);
 	    if (!matched_clause)
-		break;
+		continue;
 
-	    clausegroup = lcons(matched_clause, clausegroup);
-	    curIndxKey++;
-	    curClass++;
+	    tempgroup = lappend(tempgroup, matched_clause);
+	}
+	if ( tempgroup == NIL )
+	    break;
 
-	} while ( !DoneMatchingIndexKeys(curIndxKey, index) );
+	clausegroup = nconc (clausegroup, tempgroup);
+	
+	indexkeys++;
+	classes++;
+	
     }
 
+    /* clausegroup holds all matched clauses ordered by indexkeys */
+
     if (clausegroup != NIL)
+    {
+	/* 
+	 * if no one join clause was matched then there ain't clauses
+	 * for joins at all.
+	 */
+    	if ( !jfound )
+    	{
+    	    freeList (clausegroup);
+    	    return NIL;
+    	}
 	return(lcons(clausegroup, NIL));
+    }
     return NIL;
 }
 
@@ -482,6 +579,7 @@ match_clause_to_indexkey(Rel *rel,
     Expr *clause = clauseInfo->clause;
     Var *leftop, *rightop;
     Oid join_op = InvalidOid;
+    Oid restrict_op = InvalidOid;
     bool isIndexable = false;
 
     if (or_clause((Node*)clause) ||
@@ -495,90 +593,87 @@ match_clause_to_indexkey(Rel *rel,
      * (operator var/func constant) and (operator constant var/func)
      */
     if (!join) 
-	{
-	    Oid restrict_op = InvalidOid;
-
-	    /*
-	     * Check for standard s-argable clause
-	     */
+    {
+	/*
+	 * Check for standard s-argable clause
+	 */
 #ifdef INDEXSCAN_PATCH
 	    /* Handle also function parameters.  DZ - 27-8-1996 */ 
-	    if ((rightop && IsA(rightop,Const)) ||
+	if ((rightop && IsA(rightop,Const)) ||
 		(rightop && IsA(rightop,Param)))
 #else
-	    if (rightop && IsA(rightop,Const))
+	if (rightop && IsA(rightop,Const))
 #endif
-		{
-		    restrict_op = ((Oper*)((Expr*)clause)->oper)->opno;
-		    isIndexable =
-			( op_class(restrict_op, xclass, index->relam) &&
+	{
+	    restrict_op = ((Oper*)((Expr*)clause)->oper)->opno;
+	    isIndexable =
+		( op_class(restrict_op, xclass, index->relam) &&
 			 IndexScanableOperand(leftop,
 					      indexkey,
 					      rel,
 					      index) );
-		}
+	}
 
-	    /*
-	     * Must try to commute the clause to standard s-arg format.
-	     */
+	/*
+	 * Must try to commute the clause to standard s-arg format.
+	 */
 #ifdef INDEXSCAN_PATCH
 	    /* ...And here...  - vadim 01/22/97 */ 
-	    else if ((leftop && IsA(leftop,Const)) ||
+	else if ((leftop && IsA(leftop,Const)) ||
 			(leftop && IsA(leftop,Param)))
 #else
-	    else if (leftop && IsA(leftop,Const))
+	else if (leftop && IsA(leftop,Const))
 #endif
-		{
-		    restrict_op =
-			get_commutator(((Oper*)((Expr*)clause)->oper)->opno);
+	{
+	    restrict_op =
+		get_commutator(((Oper*)((Expr*)clause)->oper)->opno);
 
-		    if ( (restrict_op != InvalidOid) &&
+	    if ( (restrict_op != InvalidOid) &&
 			op_class(restrict_op, xclass, index->relam) &&
 			IndexScanableOperand(rightop,
 					     indexkey,rel,index) )
-			{
-			    isIndexable = true;
-			    /*
-			     * In place list modification.
-			     * (op const var/func) -> (op var/func const)
-			     */
-			    /* BUG!  Old version:
-			       CommuteClause(clause, restrict_op);
-			       */
-			    CommuteClause((Node*)clause);
-			}
-		}
-	} 
+	    {
+		isIndexable = true;
+		/*
+		 * In place list modification.
+		 * (op const var/func) -> (op var/func const)
+		 */
+		CommuteClause((Node*)clause);
+	    }
+	}
+    } 
     /*
      * Check for an indexable scan on one of the join relations.
      * clause is of the form (operator var/func var/func)
      */
     else
+    {
+	if (rightop
+		&& match_index_to_operand(indexkey,(Expr*)rightop,rel,index))
 	{
-	    if (rightop
-		&& match_index_to_operand(indexkey,(Expr*)rightop,rel,index)) {
 					
 		join_op = get_commutator(((Oper*)((Expr*)clause)->oper)->opno);
 
-	    } else if (leftop
+	} else if (leftop
 		       && match_index_to_operand(indexkey,
-						 (Expr*)leftop,rel,index)) {
+						 (Expr*)leftop,rel,index))
+	{
 		join_op = ((Oper*)((Expr*)clause)->oper)->opno;
-	    }
+	}
 
-	    if ( join_op && op_class(join_op,xclass,index->relam) &&
+	if ( join_op && op_class(join_op,xclass,index->relam) &&
 		join_clause_p((Node*)clause))
-		{
-		    isIndexable = true;
-
-		    /*
-		     * If we're using the operand's commutator we must
-		     * commute the clause.
-		     */
-		    if (join_op != ((Oper*)((Expr*)clause)->oper)->opno)
+	{
+	    isIndexable = true;
+
+	    /*
+	     * If we're using the operand's commutator we must
+	     * commute the clause.
+	     */
+	    if (join_op != ((Oper*)((Expr*)clause)->oper)->opno)
 			CommuteClause((Node*)clause);
-		}
 	}
+    }
 
     if (isIndexable)
 	return(clauseInfo);
@@ -955,10 +1050,15 @@ clause_pred_clause_test(Expr *predicate, Node *clause)
  *    in the join clause as its outer join relation.
  *    
  * Returns a list of these clause groups.
+ *
+ *    Added: clauseinfo_list - list of restriction CInfos. It's to
+ *	support multi-column indices in joins and for cases
+ *	when a key is in both join & restriction clauses. - vadim 03/18/97
  *    
  */
 static List *
-indexable_joinclauses(Rel *rel, Rel *index, List *joininfo_list)
+indexable_joinclauses(Rel *rel, Rel *index, 
+			List *joininfo_list, List *clauseinfo_list)
 {
     JInfo *joininfo = (JInfo*)NULL;
     List *cg_list = NIL;
@@ -967,13 +1067,16 @@ indexable_joinclauses(Rel *rel, Rel *index, List *joininfo_list)
 
     foreach(i,joininfo_list) { 
 	joininfo = (JInfo*)lfirst(i);
+	
+	if ( joininfo->jinfoclauseinfo == NIL )
+	    continue;
 	clausegroups = 
-	    group_clauses_by_indexkey (rel,
+	    group_clauses_by_ikey_for_joins (rel,
 				       index,
 				       index->indexkeys,
 				       index->classlist,
 				       joininfo->jinfoclauseinfo,
-				       true);
+				       clauseinfo_list);
 
 	if (clausegroups != NIL) {
 	    List *clauses = lfirst(clausegroups);
@@ -1056,6 +1159,7 @@ index_innerjoin(Query *root, Rel *rel, List *clausegroup_list, Rel *index)
 	pathnode->path.pathtype = T_IndexScan;
 	pathnode->path.parent = rel;
 	pathnode->indexid = index->relids;
+	pathnode->indexkeys = index->indexkeys;
 	pathnode->indexqual = clausegroup;
 
 	pathnode->path.joinid = ((CInfo*)lfirst(clausegroup))->cinfojoinid;
@@ -1130,7 +1234,7 @@ create_index_paths(Query *root,
 		temp = false;
 	    }
 	}
-	  
+
 	if (!join || temp) {	/* restriction, ordering scan */
 	    temp_path = create_index_path (root, rel,index,clausegroup,join);
 	    temp_node = 
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 463cc2448e3ae1311cdb3c559d7ceca85f537be3..2783e3917f42523e1d864218c0f7d0d207cb75f2 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *    $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.8 1997/03/12 21:05:56 scrappy Exp $
+ *    $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.9 1997/03/18 18:40:05 scrappy Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -417,22 +417,35 @@ create_nestloop_node(JoinPath *best_path,
     NestLoop *join_node = (NestLoop*)NULL;
 
     if (IsA(inner_node,IndexScan)) {
-	/*  An index is being used to reduce the number of tuples scanned in 
-	 *    the inner relation.
-	 * There will never be more than one index used in the inner 
-	 * scan path, so we need only consider the first set of 
-	 *    qualifications in indxqual. 
+	/* An index is being used to reduce the number of tuples scanned in 
+	 * the inner relation. There will never be more than one index used 
+	 * in the inner scan path, so we need only consider the first set of 
+	 * qualifications in indxqual. 
+	 *
+	 * But there may be more than one clauses in this "first set" 
+	 * in the case of multi-column indices. - vadim 03/18/97
 	 */
 
 	List *inner_indxqual = lfirst(((IndexScan*)inner_node)->indxqual);
-	List *inner_qual = (inner_indxqual == NULL)? NULL:lfirst(inner_indxqual);
+	List *inner_qual;
+	bool found = false;
+
+	foreach (inner_qual, inner_indxqual)
+	{
+	    if ( !(qual_clause_p ((Node*)inner_qual)) )
+	    {
+	    	found = true;
+	    	break;
+	    }
+	}
 
 	/* If we have in fact found a join index qualification, remove these
 	 * index clauses from the nestloop's join clauses and reset the 
 	 * inner(index) scan's qualification so that the var nodes refer to
 	 * the proper outer join relation attributes.
 	 */
-	if  (!(qual_clause_p((Node*)inner_qual))) {
+	if  ( found )
+	{
 	    List *new_inner_qual = NIL;
 	    
 	    clauses = set_difference(clauses,inner_indxqual);
@@ -613,7 +626,7 @@ fix_indxqual_references(Node *clause, Path *index_path)
 	if (lfirsti(index_path->parent->relids) == ((Var*)clause)->varno) {
 	    int pos = 0;
 	    int varatt = ((Var*)clause)->varattno;
-	    int *indexkeys = index_path->parent->indexkeys;
+	    int *indexkeys = ((IndexPath*)index_path)->indexkeys;
 	    
 	    if (indexkeys) {
 		while (indexkeys[pos] != 0) {
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 728ac9b422e5c9bc5c1e81b987f9f0fbb2f08228..3362367dace37f848ac5586f5adff64dfe8c04b1 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *    $Header: /cvsroot/pgsql/src/backend/optimizer/util/pathnode.c,v 1.1.1.1 1996/07/09 06:21:38 scrappy Exp $
+ *    $Header: /cvsroot/pgsql/src/backend/optimizer/util/pathnode.c,v 1.2 1997/03/18 18:40:40 scrappy Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -248,10 +248,11 @@ create_index_path(Query *root,
     
     pathnode->path.pathtype = T_IndexScan;
     pathnode->path.parent = rel;
-    pathnode->indexid = index->relids;
-
     pathnode->path.p_ordering.ordtype = SORTOP_ORDER;
     pathnode->path.p_ordering.ord.sortop = index->ordering;
+
+    pathnode->indexid = index->relids;
+    pathnode->indexkeys = index->indexkeys;
     pathnode->indexqual = NIL;
 
     /* copy clauseinfo list into path for expensive function processing 
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 5b602705d5cc4a9f7f3deb83bc7b180c8143fbea..ab32fbd82387ddaa354a559bebaca57e12edc5e6 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -6,7 +6,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- * $Id: nbtree.h,v 1.9 1997/02/22 10:08:27 vadim Exp $
+ * $Id: nbtree.h,v 1.10 1997/03/18 18:41:16 scrappy Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -68,6 +68,7 @@ typedef struct BTScanOpaqueData {
     Buffer	btso_mrkbuf;
     uint16	qual_ok;		/* 0 for quals like key == 1 && key > 2 */
     uint16	numberOfKeys;		/* number of key attributes */
+    uint16	numberOfFirstKeys;	/* number of first key attributes */
     ScanKey	keyData;		/* key descriptor */
 } BTScanOpaqueData;
 
@@ -270,9 +271,9 @@ extern bool _bt_invokestrat(Relation rel, AttrNumber attno,
 extern ScanKey  _bt_mkscankey(Relation rel, IndexTuple itup);
 extern void _bt_freeskey(ScanKey skey);
 extern void _bt_freestack(BTStack stack);
-extern void _bt_orderkeys(Relation relation, uint16 *numberOfKeys,
-			  ScanKey key, uint16 *qual_ok);
+extern void _bt_orderkeys(Relation relation, BTScanOpaque so);
 extern bool _bt_checkqual(IndexScanDesc scan, IndexTuple itup);
+extern bool _bt_checkforkeys(IndexScanDesc scan, IndexTuple itup, Size keysz);
 extern BTItem _bt_formitem(IndexTuple itup);
 
 /*
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 7c0bce12e2cc89873028466e0fc93b5c99b73e75..16c7dd299556c97323bbc3067ad9bab3b7b53f35 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -6,7 +6,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- * $Id: relation.h,v 1.3 1996/11/06 07:44:18 scrappy Exp $
+ * $Id: relation.h,v 1.4 1997/03/18 18:41:37 scrappy Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -150,6 +150,7 @@ typedef struct IndexPath {
     Path	path;
     List	*indexid;
     List	*indexqual;
+    int		*indexkeys;	/* to transform heap attnos into index ones */
 } IndexPath;
 
 typedef struct JoinPath {