From 27cb66fdfe862f395cefa0d498b681ce142f59d8 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Fri, 11 Jul 2008 21:06:29 +0000
Subject: [PATCH] Multi-column GIN indexes.  Teodor Sigaev

---
 doc/src/sgml/indices.sgml                  |  37 +++--
 doc/src/sgml/ref/create_index.sgml         |   6 +-
 src/backend/access/gin/ginbulk.c           |  38 ++---
 src/backend/access/gin/ginentrypage.c      |  60 ++++----
 src/backend/access/gin/ginget.c            |  33 +++--
 src/backend/access/gin/gininsert.c         |  59 ++++----
 src/backend/access/gin/ginscan.c           |  24 +--
 src/backend/access/gin/ginutil.c           | 161 ++++++++++++++++-----
 src/backend/access/gin/ginvacuum.c         |  11 +-
 src/backend/access/gin/ginxlog.c           |   4 +-
 src/include/access/gin.h                   |  57 +++++---
 src/include/catalog/catversion.h           |   4 +-
 src/include/catalog/pg_am.h                |   4 +-
 src/test/regress/expected/create_index.out | 126 +++++++++++++++-
 src/test/regress/sql/create_index.sql      |  29 +++-
 15 files changed, 468 insertions(+), 185 deletions(-)

diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 3de05419226..2ab713c39be 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/indices.sgml,v 1.73 2008/05/27 00:13:08 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/indices.sgml,v 1.74 2008/07/11 21:06:28 tgl Exp $ -->
 
 <chapter id="indexes">
  <title id="indexes-title">Indexes</title>
@@ -198,7 +198,7 @@ CREATE INDEX <replaceable>name</replaceable> ON <replaceable>table</replaceable>
     after a database crash.
     For these reasons, hash index use is presently discouraged.
    </para>
-  </note>  
+  </note>
 
   <para>
    <indexterm>
@@ -250,9 +250,9 @@ CREATE INDEX <replaceable>name</replaceable> ON <replaceable>table</replaceable>
    </indexterm>
    GIN indexes are inverted indexes which can handle values that contain more
    than one key, arrays for example. Like GiST, GIN can support
-   many different user-defined indexing strategies and the particular 
-   operators with which a GIN index can be used vary depending on the 
-   indexing strategy.  
+   many different user-defined indexing strategies and the particular
+   operators with which a GIN index can be used vary depending on the
+   indexing strategy.
    As an example, the standard distribution of
    <productname>PostgreSQL</productname> includes GIN operator classes
    for one-dimensional arrays, which support indexed
@@ -306,7 +306,7 @@ CREATE INDEX test2_mm_idx ON test2 (major, minor);
   </para>
 
   <para>
-   Currently, only the B-tree and GiST index types support multicolumn
+   Currently, only the B-tree, GiST and GIN index types support multicolumn
    indexes.  Up to 32 columns can be specified.  (This limit can be
    altered when building <productname>PostgreSQL</productname>; see the
    file <filename>pg_config_manual.h</filename>.)
@@ -336,14 +336,21 @@ CREATE INDEX test2_mm_idx ON test2 (major, minor);
 
   <para>
    A multicolumn GiST index can be used with query conditions that
-   involve any subset of the index's columns. Conditions on additional 
-   columns restrict the entries returned by the index, but the condition on 
-   the first column is the most important one for determining how much of 
-   the index needs to be scanned.  A GiST index will be relatively 
-   ineffective if its first column has only a few distinct values, even if 
+   involve any subset of the index's columns. Conditions on additional
+   columns restrict the entries returned by the index, but the condition on
+   the first column is the most important one for determining how much of
+   the index needs to be scanned.  A GiST index will be relatively
+   ineffective if its first column has only a few distinct values, even if
    there are many distinct values in additional columns.
   </para>
 
+  <para>
+   A multicolumn GIN index can be used with query conditions that
+   involve any subset of the index's columns. Unlike B-tree or GiST,
+   index search effectiveness is the same regardless of which index column(s)
+   the query conditions use.
+  </para>
+
   <para>
    Of course, each column must be used with operators appropriate to the index
    type; clauses that involve other operators will not be considered.
@@ -551,7 +558,7 @@ CREATE UNIQUE INDEX <replaceable>name</replaceable> ON <replaceable>table</repla
   <para>
    <productname>PostgreSQL</productname> automatically creates a unique
    index when a unique constraint or a primary key is defined for a table.
-   The index covers the columns that make up the primary key or unique 
+   The index covers the columns that make up the primary key or unique
    columns (a multicolumn index, if appropriate), and is the mechanism
    that enforces the constraint.
   </para>
@@ -798,9 +805,9 @@ SELECT * FROM orders WHERE order_nr = 3501;
    or the index will not be recognized to be usable. Matching takes
    place at query planning time, not at run time. As a result,
    parameterized query clauses will not work with a partial index. For
-   example a prepared query with a parameter might specify 
-   <quote>x &lt; ?</quote> which will never imply 
-   <quote>x &lt; 2</quote> for all possible values of the parameter. 
+   example a prepared query with a parameter might specify
+   <quote>x &lt; ?</quote> which will never imply
+   <quote>x &lt; 2</quote> for all possible values of the parameter.
   </para>
 
   <para>
diff --git a/doc/src/sgml/ref/create_index.sgml b/doc/src/sgml/ref/create_index.sgml
index 32b7bbebd99..030fa1bb004 100644
--- a/doc/src/sgml/ref/create_index.sgml
+++ b/doc/src/sgml/ref/create_index.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/create_index.sgml,v 1.67 2008/03/16 23:57:51 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/create_index.sgml,v 1.68 2008/07/11 21:06:29 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -394,7 +394,7 @@ Indexes:
   </para>
 
   <para>
-   Currently, only the B-tree and GiST index methods support
+   Currently, only the B-tree, GiST and GIN index methods support
    multicolumn indexes. Up to 32 fields can be specified by default.
    (This limit can be altered when building
    <productname>PostgreSQL</productname>.)  Only B-tree currently
@@ -423,7 +423,7 @@ Indexes:
    the optional clauses <literal>ASC</>, <literal>DESC</>, <literal>NULLS
    FIRST</>, and/or <literal>NULLS LAST</> can be specified to reverse
    the normal sort direction of the index.  Since an ordered index can be
-   scanned either forward or backward, it is not normally useful to create a 
+   scanned either forward or backward, it is not normally useful to create a
    single-column <literal>DESC</> index &mdash; that sort ordering is already
    available with a regular index.  The value of these options is that
    multicolumn indexes can be created that match the sort ordering requested
diff --git a/src/backend/access/gin/ginbulk.c b/src/backend/access/gin/ginbulk.c
index b17e87cdb11..7f50b52602b 100644
--- a/src/backend/access/gin/ginbulk.c
+++ b/src/backend/access/gin/ginbulk.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *			$PostgreSQL: pgsql/src/backend/access/gin/ginbulk.c,v 1.12 2008/06/29 21:04:01 tgl Exp $
+ *			$PostgreSQL: pgsql/src/backend/access/gin/ginbulk.c,v 1.13 2008/07/11 21:06:29 tgl Exp $
  *-------------------------------------------------------------------------
  */
 
@@ -82,16 +82,16 @@ ginInsertData(BuildAccumulator *accum, EntryAccumulator *entry, ItemPointer heap
  * palloc'd space in accum.
  */
 static Datum
-getDatumCopy(BuildAccumulator *accum, Datum value)
+getDatumCopy(BuildAccumulator *accum, OffsetNumber attnum, Datum value)
 {
-	Form_pg_attribute *att = accum->ginstate->tupdesc->attrs;
+	Form_pg_attribute att = accum->ginstate->origTupdesc->attrs[ attnum - 1 ];
 	Datum		res;
 
-	if (att[0]->attbyval)
+	if (att->attbyval)
 		res = value;
 	else
 	{
-		res = datumCopy(value, false, att[0]->attlen);
+		res = datumCopy(value, false, att->attlen);
 		accum->allocatedMemory += GetMemoryChunkSpace(DatumGetPointer(res));
 	}
 	return res;
@@ -101,7 +101,7 @@ getDatumCopy(BuildAccumulator *accum, Datum value)
  * Find/store one entry from indexed value.
  */
 static void
-ginInsertEntry(BuildAccumulator *accum, ItemPointer heapptr, Datum entry)
+ginInsertEntry(BuildAccumulator *accum, ItemPointer heapptr, OffsetNumber attnum, Datum entry)
 {
 	EntryAccumulator *ea = accum->entries,
 			   *pea = NULL;
@@ -110,7 +110,7 @@ ginInsertEntry(BuildAccumulator *accum, ItemPointer heapptr, Datum entry)
 
 	while (ea)
 	{
-		res = compareEntries(accum->ginstate, entry, ea->value);
+		res = compareAttEntries(accum->ginstate, attnum, entry, ea->attnum, ea->value);
 		if (res == 0)
 			break;				/* found */
 		else
@@ -132,7 +132,8 @@ ginInsertEntry(BuildAccumulator *accum, ItemPointer heapptr, Datum entry)
 		ea = EAAllocate(accum);
 
 		ea->left = ea->right = NULL;
-		ea->value = getDatumCopy(accum, entry);
+		ea->attnum = attnum;
+		ea->value = getDatumCopy(accum, attnum, entry);
 		ea->length = DEF_NPTR;
 		ea->number = 1;
 		ea->shouldSort = FALSE;
@@ -160,7 +161,8 @@ ginInsertEntry(BuildAccumulator *accum, ItemPointer heapptr, Datum entry)
  * then calls itself for each parts
  */
 static void
-ginChooseElem(BuildAccumulator *accum, ItemPointer heapptr, Datum *entries, uint32 nentry,
+ginChooseElem(BuildAccumulator *accum, ItemPointer heapptr, OffsetNumber attnum, 
+										Datum *entries, uint32 nentry,
 			  uint32 low, uint32 high, uint32 offset)
 {
 	uint32		pos;
@@ -168,15 +170,15 @@ ginChooseElem(BuildAccumulator *accum, ItemPointer heapptr, Datum *entries, uint
 
 	pos = (low + middle) >> 1;
 	if (low != middle && pos >= offset && pos - offset < nentry)
-		ginInsertEntry(accum, heapptr, entries[pos - offset]);
+		ginInsertEntry(accum, heapptr, attnum, entries[pos - offset]);
 	pos = (high + middle + 1) >> 1;
 	if (middle + 1 != high && pos >= offset && pos - offset < nentry)
-		ginInsertEntry(accum, heapptr, entries[pos - offset]);
+		ginInsertEntry(accum, heapptr, attnum, entries[pos - offset]);
 
 	if (low != middle)
-		ginChooseElem(accum, heapptr, entries, nentry, low, middle, offset);
+		ginChooseElem(accum, heapptr, attnum, entries, nentry, low, middle, offset);
 	if (high != middle + 1)
-		ginChooseElem(accum, heapptr, entries, nentry, middle + 1, high, offset);
+		ginChooseElem(accum, heapptr, attnum, entries, nentry, middle + 1, high, offset);
 }
 
 /*
@@ -185,7 +187,8 @@ ginChooseElem(BuildAccumulator *accum, ItemPointer heapptr, Datum *entries, uint
  * next middle on left part and middle of right part.
  */
 void
-ginInsertRecordBA(BuildAccumulator *accum, ItemPointer heapptr, Datum *entries, int32 nentry)
+ginInsertRecordBA(BuildAccumulator *accum, ItemPointer heapptr, OffsetNumber attnum, 
+						Datum *entries, int32 nentry)
 {
 	uint32		i,
 				nbit = 0,
@@ -201,8 +204,8 @@ ginInsertRecordBA(BuildAccumulator *accum, ItemPointer heapptr, Datum *entries,
 	nbit = 1 << nbit;
 	offset = (nbit - nentry) / 2;
 
-	ginInsertEntry(accum, heapptr, entries[(nbit >> 1) - offset]);
-	ginChooseElem(accum, heapptr, entries, nentry, 0, nbit, offset);
+	ginInsertEntry(accum, heapptr, attnum, entries[(nbit >> 1) - offset]);
+	ginChooseElem(accum, heapptr, attnum, entries, nentry, 0, nbit, offset);
 }
 
 static int
@@ -259,7 +262,7 @@ walkTree(BuildAccumulator *accum)
 }
 
 ItemPointerData *
-ginGetEntry(BuildAccumulator *accum, Datum *value, uint32 *n)
+ginGetEntry(BuildAccumulator *accum, OffsetNumber *attnum, Datum *value, uint32 *n)
 {
 	EntryAccumulator *entry;
 	ItemPointerData *list;
@@ -299,6 +302,7 @@ ginGetEntry(BuildAccumulator *accum, Datum *value, uint32 *n)
 		return NULL;
 
 	*n = entry->number;
+	*attnum = entry->attnum;
 	*value = entry->value;
 	list = entry->list;
 
diff --git a/src/backend/access/gin/ginentrypage.c b/src/backend/access/gin/ginentrypage.c
index 96be4ee94b0..ef9c9ba2a40 100644
--- a/src/backend/access/gin/ginentrypage.c
+++ b/src/backend/access/gin/ginentrypage.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *			$PostgreSQL: pgsql/src/backend/access/gin/ginentrypage.c,v 1.16 2008/06/19 00:46:03 alvherre Exp $
+ *			$PostgreSQL: pgsql/src/backend/access/gin/ginentrypage.c,v 1.17 2008/07/11 21:06:29 tgl Exp $
  *-------------------------------------------------------------------------
  */
 
@@ -38,14 +38,27 @@
  *		- ItemPointerGetBlockNumber(&itup->t_tid) contains block number of
  *		  root of posting tree
  *		- ItemPointerGetOffsetNumber(&itup->t_tid) contains magic number GIN_TREE_POSTING
+ *
+ * Storage of attributes of tuple are different for single and multicolumn index.
+ * For single-column index tuple stores only value to be indexed and for
+ * multicolumn variant it stores two attributes: column number of value and value. 
  */
 IndexTuple
-GinFormTuple(GinState *ginstate, Datum key, ItemPointerData *ipd, uint32 nipd)
+GinFormTuple(GinState *ginstate, OffsetNumber attnum, Datum key, ItemPointerData *ipd, uint32 nipd)
 {
-	bool		isnull = FALSE;
+	bool		isnull[2] = {FALSE,FALSE};
 	IndexTuple	itup;
 
-	itup = index_form_tuple(ginstate->tupdesc, &key, &isnull);
+	if ( ginstate->oneCol )
+		itup = index_form_tuple(ginstate->origTupdesc, &key, isnull);
+	else
+	{
+		Datum 	datums[2];
+
+		datums[0] = UInt16GetDatum(attnum);
+		datums[1] = key;
+		itup = index_form_tuple(ginstate->tupdesc[attnum-1], datums, isnull); 
+	}
 
 	GinSetOrigSizePosting(itup, IndexTupleSize(itup));
 
@@ -88,28 +101,20 @@ getRightMostTuple(Page page)
 	return (IndexTuple) PageGetItem(page, PageGetItemId(page, maxoff));
 }
 
-Datum
-ginGetHighKey(GinState *ginstate, Page page)
-{
-	IndexTuple	itup;
-	bool		isnull;
-
-	itup = getRightMostTuple(page);
-
-	return index_getattr(itup, FirstOffsetNumber, ginstate->tupdesc, &isnull);
-}
-
 static bool
 entryIsMoveRight(GinBtree btree, Page page)
 {
-	Datum		highkey;
+	IndexTuple	itup;
 
 	if (GinPageRightMost(page))
 		return FALSE;
 
-	highkey = ginGetHighKey(btree->ginstate, page);
+	itup = getRightMostTuple(page);	
 
-	if (compareEntries(btree->ginstate, btree->entryValue, highkey) > 0)
+	if (compareAttEntries(btree->ginstate,
+					btree->entryAttnum, btree->entryValue,
+					gintuple_get_attrnum(btree->ginstate, itup),
+					gin_index_getattr(btree->ginstate, itup)) > 0)
 		return TRUE;
 
 	return FALSE;
@@ -154,11 +159,11 @@ entryLocateEntry(GinBtree btree, GinBtreeStack *stack)
 			result = -1;
 		else
 		{
-			bool		isnull;
-
 			itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, mid));
-			result = compareEntries(btree->ginstate, btree->entryValue,
-									index_getattr(itup, FirstOffsetNumber, btree->ginstate->tupdesc, &isnull));
+			result = compareAttEntries(btree->ginstate, 
+									btree->entryAttnum, btree->entryValue,
+									gintuple_get_attrnum(btree->ginstate, itup),
+									gin_index_getattr(btree->ginstate, itup));
 		}
 
 		if (result == 0)
@@ -217,13 +222,13 @@ entryLocateLeafEntry(GinBtree btree, GinBtreeStack *stack)
 	while (high > low)
 	{
 		OffsetNumber mid = low + ((high - low) / 2);
-		bool		isnull;
 		int			result;
 
 		itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, mid));
-		result = compareEntries(btree->ginstate, btree->entryValue,
-								index_getattr(itup, FirstOffsetNumber, btree->ginstate->tupdesc, &isnull));
-
+		result = compareAttEntries(btree->ginstate, 
+								btree->entryAttnum, btree->entryValue,
+								gintuple_get_attrnum(btree->ginstate, itup),
+								gin_index_getattr(btree->ginstate, itup));
 		if (result == 0)
 		{
 			stack->off = mid;
@@ -587,7 +592,7 @@ entryFillRoot(GinBtree btree, Buffer root, Buffer lbuf, Buffer rbuf)
 }
 
 void
-prepareEntryScan(GinBtree btree, Relation index, Datum value, GinState *ginstate)
+prepareEntryScan(GinBtree btree, Relation index, OffsetNumber attnum, Datum value, GinState *ginstate)
 {
 	memset(btree, 0, sizeof(GinBtreeData));
 
@@ -603,6 +608,7 @@ prepareEntryScan(GinBtree btree, Relation index, Datum value, GinState *ginstate
 
 	btree->index = index;
 	btree->ginstate = ginstate;
+	btree->entryAttnum = attnum;
 	btree->entryValue = value;
 
 	btree->isDelete = FALSE;
diff --git a/src/backend/access/gin/ginget.c b/src/backend/access/gin/ginget.c
index 76d937740f5..9648d5ea289 100644
--- a/src/backend/access/gin/ginget.c
+++ b/src/backend/access/gin/ginget.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *			$PostgreSQL: pgsql/src/backend/access/gin/ginget.c,v 1.17 2008/06/19 00:46:03 alvherre Exp $
+ *			$PostgreSQL: pgsql/src/backend/access/gin/ginget.c,v 1.18 2008/07/11 21:06:29 tgl Exp $
  *-------------------------------------------------------------------------
  */
 
@@ -138,7 +138,6 @@ computePartialMatchList( GinBtreeData *btree, GinBtreeStack *stack, GinScanEntry
 	Page 		page;
 	IndexTuple  itup;
 	Datum		idatum;
-	bool		isnull;
 	int32		cmp;
 
 	scanEntry->partialMatch = tbm_create( work_mem * 1024L );
@@ -153,8 +152,15 @@ computePartialMatchList( GinBtreeData *btree, GinBtreeStack *stack, GinScanEntry
 
 		page = BufferGetPage(stack->buffer);
 		itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, stack->off));
-		idatum = index_getattr(itup, 1, btree->ginstate->tupdesc, &isnull);
-		Assert(!isnull);
+
+		/*
+		 * If tuple stores another attribute then stop scan
+		 */
+		if ( gintuple_get_attrnum( btree->ginstate, itup ) != scanEntry->attnum )
+			return true;
+
+		idatum = gin_index_getattr( btree->ginstate, itup );
+
 
 		/*----------
 		 * Check of partial match.
@@ -163,7 +169,7 @@ computePartialMatchList( GinBtreeData *btree, GinBtreeStack *stack, GinScanEntry
 		 * case cmp < 0 => not match and continue scan
 		 *----------
 		 */
-    	cmp = DatumGetInt32(FunctionCall3(&btree->ginstate->comparePartialFn,
+    	cmp = DatumGetInt32(FunctionCall3(&btree->ginstate->comparePartialFn[scanEntry->attnum-1],
 										  scanEntry->entry,
 										  idatum,
 										  UInt16GetDatum(scanEntry->strategy)));
@@ -182,8 +188,8 @@ computePartialMatchList( GinBtreeData *btree, GinBtreeStack *stack, GinScanEntry
 			Datum		newDatum,
 						savedDatum = datumCopy (
 										idatum,
-										btree->ginstate->tupdesc->attrs[0]->attbyval,
-										btree->ginstate->tupdesc->attrs[0]->attlen
+										btree->ginstate->origTupdesc->attrs[scanEntry->attnum-1]->attbyval,
+										btree->ginstate->origTupdesc->attrs[scanEntry->attnum-1]->attlen
 									);
 			/*
 			 * We should unlock current page (but not unpin) during
@@ -220,12 +226,15 @@ computePartialMatchList( GinBtreeData *btree, GinBtreeStack *stack, GinScanEntry
 
 				page = BufferGetPage(stack->buffer);
 				itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, stack->off));
-				newDatum = index_getattr(itup, FirstOffsetNumber, btree->ginstate->tupdesc, &isnull);
+				newDatum = gin_index_getattr( btree->ginstate, itup );
+
+				if ( gintuple_get_attrnum( btree->ginstate, itup ) != scanEntry->attnum )
+					elog(ERROR, "lost saved point in index"); /* must not happen !!! */
 
-				if ( compareEntries(btree->ginstate, newDatum, savedDatum) == 0 )
+				if ( compareEntries(btree->ginstate, scanEntry->attnum, newDatum, savedDatum) == 0 )
 				{
 					/* Found!  */
-					if ( btree->ginstate->tupdesc->attrs[0]->attbyval == false )
+					if ( btree->ginstate->origTupdesc->attrs[scanEntry->attnum-1]->attbyval == false )
 						pfree( DatumGetPointer(savedDatum) );
 					break;
 				}
@@ -270,7 +279,7 @@ startScanEntry(Relation index, GinState *ginstate, GinScanEntry entry)
 	 * or just store posting list in memory
 	 */
 
-	prepareEntryScan(&btreeEntry, index, entry->entry, ginstate);
+	prepareEntryScan(&btreeEntry, index, entry->attnum, entry->entry, ginstate);
 	btreeEntry.searchMode = TRUE;
 	stackEntry = ginFindLeafPage(&btreeEntry, NULL);
 	page = BufferGetPage(stackEntry->buffer);
@@ -705,7 +714,7 @@ keyGetItem(Relation index, GinState *ginstate, MemoryContext tempCtx,
 		*keyrecheck = true;
 
 		oldCtx = MemoryContextSwitchTo(tempCtx);
-		res = DatumGetBool(FunctionCall4(&ginstate->consistentFn,
+		res = DatumGetBool(FunctionCall4(&ginstate->consistentFn[key->attnum-1],
 										 PointerGetDatum(key->entryRes),
 										 UInt16GetDatum(key->strategy),
 										 key->query,
diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c
index 654bf137e23..ac35069d7f5 100644
--- a/src/backend/access/gin/gininsert.c
+++ b/src/backend/access/gin/gininsert.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *			$PostgreSQL: pgsql/src/backend/access/gin/gininsert.c,v 1.13 2008/05/16 01:27:06 tgl Exp $
+ *			$PostgreSQL: pgsql/src/backend/access/gin/gininsert.c,v 1.14 2008/07/11 21:06:29 tgl Exp $
  *-------------------------------------------------------------------------
  */
 
@@ -99,9 +99,9 @@ static IndexTuple
 addItemPointersToTuple(Relation index, GinState *ginstate, GinBtreeStack *stack,
 		  IndexTuple old, ItemPointerData *items, uint32 nitem, bool isBuild)
 {
-	bool		isnull;
-	Datum		key = index_getattr(old, FirstOffsetNumber, ginstate->tupdesc, &isnull);
-	IndexTuple	res = GinFormTuple(ginstate, key, NULL, nitem + GinGetNPosting(old));
+	Datum			key = gin_index_getattr(ginstate, old);
+	OffsetNumber	attnum = gintuple_get_attrnum(ginstate, old);
+	IndexTuple		res = GinFormTuple(ginstate, attnum, key, NULL, nitem + GinGetNPosting(old));
 
 	if (res)
 	{
@@ -119,7 +119,7 @@ addItemPointersToTuple(Relation index, GinState *ginstate, GinBtreeStack *stack,
 		GinPostingTreeScan *gdi;
 
 		/* posting list becomes big, so we need to make posting's tree */
-		res = GinFormTuple(ginstate, key, NULL, 0);
+		res = GinFormTuple(ginstate, attnum, key, NULL, 0);
 		postingRoot = createPostingTree(index, GinGetPosting(old), GinGetNPosting(old));
 		GinSetPostingTree(res, postingRoot);
 
@@ -138,14 +138,15 @@ addItemPointersToTuple(Relation index, GinState *ginstate, GinBtreeStack *stack,
  * Inserts only one entry to the index, but it can add more than 1 ItemPointer.
  */
 static void
-ginEntryInsert(Relation index, GinState *ginstate, Datum value, ItemPointerData *items, uint32 nitem, bool isBuild)
+ginEntryInsert(Relation index, GinState *ginstate, OffsetNumber attnum, Datum value, 
+				ItemPointerData *items, uint32 nitem, bool isBuild)
 {
 	GinBtreeData btree;
 	GinBtreeStack *stack;
 	IndexTuple	itup;
 	Page		page;
 
-	prepareEntryScan(&btree, index, value, ginstate);
+	prepareEntryScan(&btree, index, attnum, value, ginstate);
 
 	stack = ginFindLeafPage(&btree, NULL);
 	page = BufferGetPage(stack->buffer);
@@ -180,7 +181,7 @@ ginEntryInsert(Relation index, GinState *ginstate, Datum value, ItemPointerData
 	else
 	{
 		/* We suppose, that tuple can store at list one itempointer */
-		itup = GinFormTuple(ginstate, value, items, 1);
+		itup = GinFormTuple(ginstate, attnum, value, items, 1);
 		if (itup == NULL || IndexTupleSize(itup) >= GinMaxItemSize)
 			elog(ERROR, "huge tuple");
 
@@ -203,21 +204,21 @@ ginEntryInsert(Relation index, GinState *ginstate, Datum value, ItemPointerData
  * Function isn't used during normal insert
  */
 static uint32
-ginHeapTupleBulkInsert(GinBuildState *buildstate, Datum value, ItemPointer heapptr)
+ginHeapTupleBulkInsert(GinBuildState *buildstate, OffsetNumber attnum, Datum value, ItemPointer heapptr)
 {
 	Datum	   *entries;
 	int32		nentries;
 	MemoryContext oldCtx;
 
 	oldCtx = MemoryContextSwitchTo(buildstate->funcCtx);
-	entries = extractEntriesSU(buildstate->accum.ginstate, value, &nentries);
+	entries = extractEntriesSU(buildstate->accum.ginstate, attnum, value, &nentries);
 	MemoryContextSwitchTo(oldCtx);
 
 	if (nentries == 0)
 		/* nothing to insert */
 		return 0;
 
-	ginInsertRecordBA(&buildstate->accum, heapptr, entries, nentries);
+	ginInsertRecordBA(&buildstate->accum, heapptr, attnum, entries, nentries);
 
 	MemoryContextReset(buildstate->funcCtx);
 
@@ -230,13 +231,15 @@ ginBuildCallback(Relation index, HeapTuple htup, Datum *values,
 {
 	GinBuildState *buildstate = (GinBuildState *) state;
 	MemoryContext oldCtx;
-
-	if (*isnull)
-		return;
+	int 		  i;
 
 	oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
 
-	buildstate->indtuples += ginHeapTupleBulkInsert(buildstate, *values, &htup->t_self);
+	for(i=0; i<buildstate->ginstate.origTupdesc->natts;i++)
+		if ( !isnull[i] )
+			buildstate->indtuples += ginHeapTupleBulkInsert(buildstate, 
+														(OffsetNumber)(i+1), values[i], 
+														&htup->t_self);
 
 	/* If we've maxed out our available memory, dump everything to the index */
 	if (buildstate->accum.allocatedMemory >= maintenance_work_mem * 1024L)
@@ -244,12 +247,13 @@ ginBuildCallback(Relation index, HeapTuple htup, Datum *values,
 		ItemPointerData *list;
 		Datum		entry;
 		uint32		nlist;
+		OffsetNumber  attnum;
 
-		while ((list = ginGetEntry(&buildstate->accum, &entry, &nlist)) != NULL)
+		while ((list = ginGetEntry(&buildstate->accum, &attnum, &entry, &nlist)) != NULL)
 		{
 			/* there could be many entries, so be willing to abort here */
 			CHECK_FOR_INTERRUPTS();
-			ginEntryInsert(index, &buildstate->ginstate, entry, list, nlist, TRUE);
+			ginEntryInsert(index, &buildstate->ginstate, attnum, entry, list, nlist, TRUE);
 		}
 
 		MemoryContextReset(buildstate->tmpCtx);
@@ -273,6 +277,7 @@ ginbuild(PG_FUNCTION_ARGS)
 	Datum		entry;
 	uint32		nlist;
 	MemoryContext oldCtx;
+	OffsetNumber attnum;
 
 	if (RelationGetNumberOfBlocks(index) != 0)
 		elog(ERROR, "index \"%s\" already contains data",
@@ -337,11 +342,11 @@ ginbuild(PG_FUNCTION_ARGS)
 
 	/* dump remaining entries to the index */
 	oldCtx = MemoryContextSwitchTo(buildstate.tmpCtx);
-	while ((list = ginGetEntry(&buildstate.accum, &entry, &nlist)) != NULL)
+	while ((list = ginGetEntry(&buildstate.accum, &attnum, &entry, &nlist)) != NULL)
 	{
 		/* there could be many entries, so be willing to abort here */
 		CHECK_FOR_INTERRUPTS();
-		ginEntryInsert(index, &buildstate.ginstate, entry, list, nlist, TRUE);
+		ginEntryInsert(index, &buildstate.ginstate, attnum, entry, list, nlist, TRUE);
 	}
 	MemoryContextSwitchTo(oldCtx);
 
@@ -362,20 +367,20 @@ ginbuild(PG_FUNCTION_ARGS)
  * Inserts value during normal insertion
  */
 static uint32
-ginHeapTupleInsert(Relation index, GinState *ginstate, Datum value, ItemPointer item)
+ginHeapTupleInsert(Relation index, GinState *ginstate, OffsetNumber attnum, Datum value, ItemPointer item)
 {
 	Datum	   *entries;
 	int32		i,
 				nentries;
 
-	entries = extractEntriesSU(ginstate, value, &nentries);
+	entries = extractEntriesSU(ginstate, attnum, value, &nentries);
 
 	if (nentries == 0)
 		/* nothing to insert */
 		return 0;
 
 	for (i = 0; i < nentries; i++)
-		ginEntryInsert(index, ginstate, entries[i], item, 1, FALSE);
+		ginEntryInsert(index, ginstate, attnum, entries[i], item, 1, FALSE);
 
 	return nentries;
 }
@@ -395,10 +400,8 @@ gininsert(PG_FUNCTION_ARGS)
 	GinState	ginstate;
 	MemoryContext oldCtx;
 	MemoryContext insertCtx;
-	uint32		res;
-
-	if (*isnull)
-		PG_RETURN_BOOL(false);
+	uint32		res = 0;
+	int 		i;
 
 	insertCtx = AllocSetContextCreate(CurrentMemoryContext,
 									  "Gin insert temporary context",
@@ -410,7 +413,9 @@ gininsert(PG_FUNCTION_ARGS)
 
 	initGinState(&ginstate, index);
 
-	res = ginHeapTupleInsert(index, &ginstate, *values, ht_ctid);
+	for(i=0; i<ginstate.origTupdesc->natts;i++)
+		if ( !isnull[i] )
+			res += ginHeapTupleInsert(index, &ginstate, (OffsetNumber)(i+1), values[i], ht_ctid);
 
 	MemoryContextSwitchTo(oldCtx);
 	MemoryContextDelete(insertCtx);
diff --git a/src/backend/access/gin/ginscan.c b/src/backend/access/gin/ginscan.c
index 21eb1d8cbbf..b81ba0c12e1 100644
--- a/src/backend/access/gin/ginscan.c
+++ b/src/backend/access/gin/ginscan.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *			$PostgreSQL: pgsql/src/backend/access/gin/ginscan.c,v 1.16 2008/07/04 13:21:18 teodor Exp $
+ *			$PostgreSQL: pgsql/src/backend/access/gin/ginscan.c,v 1.17 2008/07/11 21:06:29 tgl Exp $
  *-------------------------------------------------------------------------
  */
 
@@ -36,7 +36,7 @@ ginbeginscan(PG_FUNCTION_ARGS)
 }
 
 static void
-fillScanKey(GinState *ginstate, GinScanKey key, Datum query,
+fillScanKey(GinState *ginstate, GinScanKey key, OffsetNumber attnum, Datum query,
 			Datum *entryValues, bool *partial_matches, uint32 nEntryValues, 
 			StrategyNumber strategy)
 {
@@ -47,6 +47,7 @@ fillScanKey(GinState *ginstate, GinScanKey key, Datum query,
 	key->entryRes = (bool *) palloc0(sizeof(bool) * nEntryValues);
 	key->scanEntry = (GinScanEntry) palloc(sizeof(GinScanEntryData) * nEntryValues);
 	key->strategy = strategy;
+	key->attnum = attnum;
 	key->query = query;
 	key->firstCall = TRUE;
 	ItemPointerSet(&(key->curItem), InvalidBlockNumber, InvalidOffsetNumber);
@@ -55,19 +56,20 @@ fillScanKey(GinState *ginstate, GinScanKey key, Datum query,
 	{
 		key->scanEntry[i].pval = key->entryRes + i;
 		key->scanEntry[i].entry = entryValues[i];
+		key->scanEntry[i].attnum = attnum;
 		ItemPointerSet(&(key->scanEntry[i].curItem), InvalidBlockNumber, InvalidOffsetNumber);
 		key->scanEntry[i].offset = InvalidOffsetNumber;
 		key->scanEntry[i].buffer = InvalidBuffer;
 		key->scanEntry[i].partialMatch = NULL;
 		key->scanEntry[i].list = NULL;
 		key->scanEntry[i].nlist = 0;
-		key->scanEntry[i].isPartialMatch = ( ginstate->canPartialMatch && partial_matches ) 
+		key->scanEntry[i].isPartialMatch = ( ginstate->canPartialMatch[attnum - 1] && partial_matches ) 
 												? partial_matches[i] : false;
 
 		/* link to the equals entry in current scan key */
 		key->scanEntry[i].master = NULL;
 		for (j = 0; j < i; j++)
-			if (compareEntries(ginstate, entryValues[i], entryValues[j]) == 0)
+			if (compareEntries(ginstate, attnum, entryValues[i], entryValues[j]) == 0)
 			{
 				key->scanEntry[i].master = key->scanEntry + j;
 				break;
@@ -164,19 +166,17 @@ newScanKey(IndexScanDesc scan)
 		int32		nEntryValues;
 		bool		*partial_matches = NULL;
 
-		Assert(scankey[i].sk_attno == 1);
-
 		/* XXX can't we treat nulls by just setting isVoidRes? */
 		/* This would amount to assuming that all GIN operators are strict */
 		if (scankey[i].sk_flags & SK_ISNULL)
 			elog(ERROR, "GIN doesn't support NULL as scan key");
 
 		entryValues = (Datum *) DatumGetPointer(FunctionCall4(
-												&so->ginstate.extractQueryFn,
-													  scankey[i].sk_argument,
-											  PointerGetDatum(&nEntryValues),
-									   UInt16GetDatum(scankey[i].sk_strategy),
-										PointerGetDatum(&partial_matches)));
+												&so->ginstate.extractQueryFn[scankey[i].sk_attno - 1],
+												scankey[i].sk_argument,
+												PointerGetDatum(&nEntryValues),
+												UInt16GetDatum(scankey[i].sk_strategy),
+												PointerGetDatum(&partial_matches)));
 		if (nEntryValues < 0)
 		{
 			/*
@@ -194,7 +194,7 @@ newScanKey(IndexScanDesc scan)
 			/* full scan... */
 			continue;
 
-		fillScanKey(&so->ginstate, &(so->keys[nkeys]), scankey[i].sk_argument,
+		fillScanKey(&so->ginstate, &(so->keys[nkeys]), scankey[i].sk_attno, scankey[i].sk_argument,
 					entryValues, partial_matches, nEntryValues, scankey[i].sk_strategy);
 		nkeys++;
 	}
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index 36105e20d2d..86b2650c753 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *			$PostgreSQL: pgsql/src/backend/access/gin/ginutil.c,v 1.15 2008/05/16 16:31:01 tgl Exp $
+ *			$PostgreSQL: pgsql/src/backend/access/gin/ginutil.c,v 1.16 2008/07/11 21:06:29 tgl Exp $
  *-------------------------------------------------------------------------
  */
 
@@ -16,6 +16,7 @@
 #include "access/genam.h"
 #include "access/gin.h"
 #include "access/reloptions.h"
+#include "catalog/pg_type.h" 
 #include "storage/bufmgr.h"
 #include "storage/freespace.h"
 #include "storage/lmgr.h"
@@ -23,40 +24,116 @@
 void
 initGinState(GinState *state, Relation index)
 {
-	if (index->rd_att->natts != 1)
-		elog(ERROR, "numberOfAttributes %d != 1",
-			 index->rd_att->natts);
-
-	state->tupdesc = index->rd_att;
-
-	fmgr_info_copy(&(state->compareFn),
-				   index_getprocinfo(index, 1, GIN_COMPARE_PROC),
-				   CurrentMemoryContext);
-	fmgr_info_copy(&(state->extractValueFn),
-				   index_getprocinfo(index, 1, GIN_EXTRACTVALUE_PROC),
-				   CurrentMemoryContext);
-	fmgr_info_copy(&(state->extractQueryFn),
-				   index_getprocinfo(index, 1, GIN_EXTRACTQUERY_PROC),
-				   CurrentMemoryContext);
-	fmgr_info_copy(&(state->consistentFn),
-				   index_getprocinfo(index, 1, GIN_CONSISTENT_PROC),
-				   CurrentMemoryContext);
-	
-	/*
-	 * Check opclass capability to do partial match. 
-	 */
-	if ( index_getprocid(index, 1, GIN_COMPARE_PARTIAL_PROC) != InvalidOid )
+	int i;
+
+	state->origTupdesc = index->rd_att;
+
+	state->oneCol = (index->rd_att->natts == 1) ? true : false;
+
+	for(i=0;i<index->rd_att->natts;i++)
+	{
+		state->tupdesc[i] = CreateTemplateTupleDesc(2,false);
+
+		TupleDescInitEntry( state->tupdesc[i], (AttrNumber) 1, NULL,
+							INT2OID, -1, 0);
+		TupleDescInitEntry( state->tupdesc[i], (AttrNumber) 2, NULL,
+							index->rd_att->attrs[i]->atttypid,
+							index->rd_att->attrs[i]->atttypmod,
+							index->rd_att->attrs[i]->attndims
+							);
+
+		fmgr_info_copy(&(state->compareFn[i]),
+						index_getprocinfo(index, i+1, GIN_COMPARE_PROC),
+						CurrentMemoryContext);
+		fmgr_info_copy(&(state->extractValueFn[i]),
+						index_getprocinfo(index, i+1, GIN_EXTRACTVALUE_PROC),
+						CurrentMemoryContext);
+		fmgr_info_copy(&(state->extractQueryFn[i]),
+						index_getprocinfo(index, i+1, GIN_EXTRACTQUERY_PROC),
+						CurrentMemoryContext);
+		fmgr_info_copy(&(state->consistentFn[i]),
+						index_getprocinfo(index, i+1, GIN_CONSISTENT_PROC),
+						CurrentMemoryContext);
+
+		/*
+		 * Check opclass capability to do partial match. 
+		 */
+		if ( index_getprocid(index, i+1, GIN_COMPARE_PARTIAL_PROC) != InvalidOid )
+		{
+			fmgr_info_copy(&(state->comparePartialFn[i]),
+						   index_getprocinfo(index, i+1, GIN_COMPARE_PARTIAL_PROC),
+						   CurrentMemoryContext);
+
+			state->canPartialMatch[i] = true;
+		}
+		else
+		{
+			state->canPartialMatch[i] = false;
+		}
+	}
+}
+
+/*
+ * Extract attribute (column) number of stored entry from GIN tuple
+ */
+OffsetNumber
+gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple)
+{
+	OffsetNumber colN = FirstOffsetNumber;
+
+	if ( !ginstate->oneCol )
 	{
-		fmgr_info_copy(&(state->comparePartialFn),
-					   index_getprocinfo(index, 1, GIN_COMPARE_PARTIAL_PROC),
-					   CurrentMemoryContext);
+		Datum   res;
+		bool    isnull;
+
+		/*
+		 * First attribute is always int16, so we can safely use any 
+		 * tuple descriptor to obtain first attribute of tuple
+		 */
+		res = index_getattr(tuple, FirstOffsetNumber, ginstate->tupdesc[0],
+							&isnull);
+		Assert(!isnull);
 
-		state->canPartialMatch = true;
+		colN = DatumGetUInt16(res);
+		Assert( colN >= FirstOffsetNumber && colN <= ginstate->origTupdesc->natts );
+	}
+
+	return colN;
+}
+
+/*
+ * Extract stored datum from GIN tuple
+ */
+Datum
+gin_index_getattr(GinState *ginstate, IndexTuple tuple)
+{
+	bool    isnull;
+	Datum   res;
+
+	if ( ginstate->oneCol )
+	{
+		/*
+		 * Single column index doesn't store attribute numbers in tuples
+		 */
+		res = index_getattr(tuple, FirstOffsetNumber, ginstate->origTupdesc,
+							&isnull);
 	}
 	else
 	{
-		state->canPartialMatch = false;
+		/*
+		 * Since the datum type depends on which index column it's from,
+		 * we must be careful to use the right tuple descriptor here.
+		 */
+		OffsetNumber colN = gintuple_get_attrnum(ginstate, tuple);
+
+		res = index_getattr(tuple, OffsetNumberNext(FirstOffsetNumber),
+							ginstate->tupdesc[colN - 1],
+							&isnull);
 	}
+
+	Assert(!isnull);
+
+	return res;
 }
 
 /*
@@ -136,16 +213,26 @@ GinInitBuffer(Buffer b, uint32 f)
 }
 
 int
-compareEntries(GinState *ginstate, Datum a, Datum b)
+compareEntries(GinState *ginstate, OffsetNumber attnum, Datum a, Datum b)
 {
 	return DatumGetInt32(
 						 FunctionCall2(
-									   &ginstate->compareFn,
+									   &ginstate->compareFn[attnum-1],
 									   a, b
 									   )
 		);
 }
 
+int
+compareAttEntries(GinState *ginstate, OffsetNumber attnum_a, Datum a,
+									  OffsetNumber attnum_b, Datum b)
+{
+	if ( attnum_a == attnum_b )
+		return compareEntries( ginstate, attnum_a, a, b);
+
+	return ( attnum_a < attnum_b ) ? -1 : 1;
+}
+
 typedef struct
 {
 	FmgrInfo   *cmpDatumFunc;
@@ -165,13 +252,13 @@ cmpEntries(const Datum *a, const Datum *b, cmpEntriesData *arg)
 }
 
 Datum *
-extractEntriesS(GinState *ginstate, Datum value, int32 *nentries,
+extractEntriesS(GinState *ginstate, OffsetNumber attnum, Datum value, int32 *nentries,
 				bool *needUnique)
 {
 	Datum	   *entries;
 
 	entries = (Datum *) DatumGetPointer(FunctionCall2(
-												   &ginstate->extractValueFn,
+												   &ginstate->extractValueFn[attnum-1],
 													  value,
 													PointerGetDatum(nentries)
 													  ));
@@ -184,7 +271,7 @@ extractEntriesS(GinState *ginstate, Datum value, int32 *nentries,
 	{
 		cmpEntriesData arg;
 
-		arg.cmpDatumFunc = &ginstate->compareFn;
+		arg.cmpDatumFunc = &ginstate->compareFn[attnum-1];
 		arg.needUnique = needUnique;
 		qsort_arg(entries, *nentries, sizeof(Datum),
 				  (qsort_arg_comparator) cmpEntries, (void *) &arg);
@@ -195,10 +282,10 @@ extractEntriesS(GinState *ginstate, Datum value, int32 *nentries,
 
 
 Datum *
-extractEntriesSU(GinState *ginstate, Datum value, int32 *nentries)
+extractEntriesSU(GinState *ginstate, OffsetNumber attnum, Datum value, int32 *nentries)
 {
 	bool		needUnique;
-	Datum	   *entries = extractEntriesS(ginstate, value, nentries,
+	Datum	   *entries = extractEntriesS(ginstate, attnum, value, nentries,
 										  &needUnique);
 
 	if (needUnique)
@@ -210,7 +297,7 @@ extractEntriesSU(GinState *ginstate, Datum value, int32 *nentries)
 
 		while (ptr - entries < *nentries)
 		{
-			if (compareEntries(ginstate, *ptr, *res) != 0)
+			if (compareEntries(ginstate, attnum, *ptr, *res) != 0)
 				*(++res) = *ptr++;
 			else
 				ptr++;
diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index b6411f4b9b4..249f612dd10 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *			$PostgreSQL: pgsql/src/backend/access/gin/ginvacuum.c,v 1.20 2008/05/12 00:00:44 alvherre Exp $
+ *			$PostgreSQL: pgsql/src/backend/access/gin/ginvacuum.c,v 1.21 2008/07/11 21:06:29 tgl Exp $
  *-------------------------------------------------------------------------
  */
 
@@ -515,8 +515,8 @@ ginVacuumEntryPage(GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint3
 
 			if (GinGetNPosting(itup) != newN)
 			{
-				bool		isnull;
-				Datum		value;
+				Datum			value;
+				OffsetNumber	attnum;
 
 				/*
 				 * Some ItemPointers was deleted, so we should remake our
@@ -544,8 +544,9 @@ ginVacuumEntryPage(GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint3
 					itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
 				}
 
-				value = index_getattr(itup, FirstOffsetNumber, gvs->ginstate.tupdesc, &isnull);
-				itup = GinFormTuple(&gvs->ginstate, value, GinGetPosting(itup), newN);
+				value = gin_index_getattr(&gvs->ginstate, itup);
+				attnum = gintuple_get_attrnum(&gvs->ginstate, itup);
+				itup = GinFormTuple(&gvs->ginstate, attnum, value, GinGetPosting(itup), newN);
 				PageIndexTupleDelete(tmppage, i);
 
 				if (PageAddItem(tmppage, (Item) itup, IndexTupleSize(itup), i, false, false) != i)
diff --git a/src/backend/access/gin/ginxlog.c b/src/backend/access/gin/ginxlog.c
index 428c956c7c6..b777e56536b 100644
--- a/src/backend/access/gin/ginxlog.c
+++ b/src/backend/access/gin/ginxlog.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *			 $PostgreSQL: pgsql/src/backend/access/gin/ginxlog.c,v 1.14 2008/06/12 09:12:29 heikki Exp $
+ *			 $PostgreSQL: pgsql/src/backend/access/gin/ginxlog.c,v 1.15 2008/07/11 21:06:29 tgl Exp $
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
@@ -549,7 +549,7 @@ ginContinueSplit(ginIncompleteSplit *split)
 
 	if (split->rootBlkno == GIN_ROOT_BLKNO)
 	{
-		prepareEntryScan(&btree, reln, (Datum) 0, NULL);
+		prepareEntryScan(&btree, reln, InvalidOffsetNumber, (Datum) 0, NULL);
 		btree.entry = ginPageGetLinkItup(buffer);
 	}
 	else
diff --git a/src/include/access/gin.h b/src/include/access/gin.h
index d47d7ac2d64..f6a786d3db0 100644
--- a/src/include/access/gin.h
+++ b/src/include/access/gin.h
@@ -4,7 +4,7 @@
  *
  *	Copyright (c) 2006-2008, PostgreSQL Global Development Group
  *
- *	$PostgreSQL: pgsql/src/include/access/gin.h,v 1.22 2008/06/19 00:46:05 alvherre Exp $
+ *	$PostgreSQL: pgsql/src/include/access/gin.h,v 1.23 2008/07/11 21:06:29 tgl Exp $
  *--------------------------------------------------------------------------
  */
 
@@ -140,15 +140,18 @@ typedef struct
 
 typedef struct GinState
 {
-	FmgrInfo	compareFn;
-	FmgrInfo	extractValueFn;
-	FmgrInfo	extractQueryFn;
-	FmgrInfo	consistentFn;
-	FmgrInfo	comparePartialFn;	/* optional method */
-
-	bool		canPartialMatch;	/* can opclass perform partial
-									 * match (prefix search)? */
-	TupleDesc	tupdesc;
+	FmgrInfo	compareFn[INDEX_MAX_KEYS];
+	FmgrInfo	extractValueFn[INDEX_MAX_KEYS];
+	FmgrInfo	extractQueryFn[INDEX_MAX_KEYS];
+	FmgrInfo	consistentFn[INDEX_MAX_KEYS];
+	FmgrInfo	comparePartialFn[INDEX_MAX_KEYS];	/* optional method */
+
+	bool		canPartialMatch[INDEX_MAX_KEYS];	/* can opclass perform partial
+													 * match (prefix search)? */
+
+	TupleDesc   tupdesc[INDEX_MAX_KEYS];
+	TupleDesc   origTupdesc;
+	bool        oneCol;
 } GinState;
 
 /* XLog stuff */
@@ -235,12 +238,16 @@ extern void initGinState(GinState *state, Relation index);
 extern Buffer GinNewBuffer(Relation index);
 extern void GinInitBuffer(Buffer b, uint32 f);
 extern void GinInitPage(Page page, uint32 f, Size pageSize);
-extern int	compareEntries(GinState *ginstate, Datum a, Datum b);
-extern Datum *extractEntriesS(GinState *ginstate, Datum value,
+extern int	compareEntries(GinState *ginstate, OffsetNumber attnum, Datum a, Datum b);
+extern int	compareAttEntries(GinState *ginstate, OffsetNumber attnum_a, Datum a, 
+												  OffsetNumber attnum_b, Datum b);
+extern Datum *extractEntriesS(GinState *ginstate, OffsetNumber attnum, Datum value,
 				int32 *nentries, bool *needUnique);
-extern Datum *extractEntriesSU(GinState *ginstate, Datum value, int32 *nentries);
+extern Datum *extractEntriesSU(GinState *ginstate, OffsetNumber attnum, Datum value, int32 *nentries);
 extern Page GinPageGetCopyPage(Page page);
 
+extern Datum gin_index_getattr(GinState *ginstate, IndexTuple tuple);
+extern OffsetNumber gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple);
 /* gininsert.c */
 extern Datum ginbuild(PG_FUNCTION_ARGS);
 extern Datum gininsert(PG_FUNCTION_ARGS);
@@ -291,6 +298,7 @@ typedef struct GinBtreeData
 	BlockNumber rightblkno;
 
 	/* Entry options */
+	OffsetNumber	entryAttnum;
 	Datum		entryValue;
 	IndexTuple	entry;
 	bool		isDelete;
@@ -310,9 +318,10 @@ extern void ginInsertValue(GinBtree btree, GinBtreeStack *stack);
 extern void findParents(GinBtree btree, GinBtreeStack *stack, BlockNumber rootBlkno);
 
 /* ginentrypage.c */
-extern IndexTuple GinFormTuple(GinState *ginstate, Datum key, ItemPointerData *ipd, uint32 nipd);
-extern Datum ginGetHighKey(GinState *ginstate, Page page);
-extern void prepareEntryScan(GinBtree btree, Relation index, Datum value, GinState *ginstate);
+extern IndexTuple GinFormTuple(GinState *ginstate, OffsetNumber attnum, Datum key, 
+										ItemPointerData *ipd, uint32 nipd);
+extern void prepareEntryScan(GinBtree btree, Relation index, OffsetNumber attnum,
+								Datum value, GinState *ginstate);
 extern void entryFillRoot(GinBtree btree, Buffer root, Buffer lbuf, Buffer rbuf);
 extern IndexTuple ginPageGetLinkItup(Buffer buf);
 
@@ -359,6 +368,7 @@ typedef struct GinScanEntryData
 
 	/* entry, got from extractQueryFn */
 	Datum		entry;
+	OffsetNumber	attnum;
 
 	/* Current page in posting tree */
 	Buffer		buffer;
@@ -396,6 +406,7 @@ typedef struct GinScanKeyData
 	/* for calling consistentFn(GinScanKey->entryRes, strategy, query) */
 	StrategyNumber strategy;
 	Datum		query;
+	OffsetNumber	attnum;
 
 	ItemPointerData curItem;
 	bool		firstCall;
@@ -450,11 +461,12 @@ extern Datum ginarrayconsistent(PG_FUNCTION_ARGS);
 /* ginbulk.c */
 typedef struct EntryAccumulator
 {
-	Datum		value;
-	uint32		length;
-	uint32		number;
+	OffsetNumber	attnum;
+	Datum			value;
+	uint32			length;
+	uint32			number;
 	ItemPointerData *list;
-	bool		shouldSort;
+	bool			shouldSort;
 	struct EntryAccumulator *left;
 	struct EntryAccumulator *right;
 } EntryAccumulator;
@@ -474,7 +486,8 @@ typedef struct
 
 extern void ginInitBA(BuildAccumulator *accum);
 extern void ginInsertRecordBA(BuildAccumulator *accum,
-				  ItemPointer heapptr, Datum *entries, int32 nentry);
-extern ItemPointerData *ginGetEntry(BuildAccumulator *accum, Datum *entry, uint32 *n);
+				  ItemPointer heapptr, 
+				  OffsetNumber attnum, Datum *entries, int32 nentry);
+extern ItemPointerData *ginGetEntry(BuildAccumulator *accum, OffsetNumber *attnum, Datum *entry, uint32 *n);
 
 #endif
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index e7ba550c484..d74a5eb5826 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.465 2008/07/03 20:58:46 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.466 2008/07/11 21:06:29 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200807031
+#define CATALOG_VERSION_NO	200807111
 
 #endif
diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h
index 0fe5d05e7c6..712a409633d 100644
--- a/src/include/catalog/pg_am.h
+++ b/src/include/catalog/pg_am.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_am.h,v 1.56 2008/05/16 16:31:01 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_am.h,v 1.57 2008/07/11 21:06:29 tgl Exp $
  *
  * NOTES
  *		the genbki.sh script reads this file and generates .bki
@@ -114,7 +114,7 @@ DESCR("hash index access method");
 DATA(insert OID = 783 (  gist	0 7 f f t t t t t t gistinsert gistbeginscan gistgettuple gistgetbitmap gistrescan gistendscan gistmarkpos gistrestrpos gistbuild gistbulkdelete gistvacuumcleanup gistcostestimate gistoptions ));
 DESCR("GiST index access method");
 #define GIST_AM_OID 783
-DATA(insert OID = 2742 (  gin	0 5 f f f f f f t f gininsert ginbeginscan gingettuple gingetbitmap ginrescan ginendscan ginmarkpos ginrestrpos ginbuild ginbulkdelete ginvacuumcleanup gincostestimate ginoptions ));
+DATA(insert OID = 2742 (  gin	0 5 f f t t f f t f gininsert ginbeginscan gingettuple gingetbitmap ginrescan ginendscan ginmarkpos ginrestrpos ginbuild ginbulkdelete ginvacuumcleanup gincostestimate ginoptions ));
 DESCR("GIN index access method");
 #define GIN_AM_OID 2742
 
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index 52ba630a560..e43ee6aa53d 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -170,7 +170,7 @@ RESET enable_seqscan;
 RESET enable_indexscan;
 RESET enable_bitmapscan;
 --
--- GIN over int[]
+-- GIN over int[] and text[]
 --
 SET enable_seqscan = OFF;
 SET enable_indexscan = ON;
@@ -416,6 +416,130 @@ SELECT * FROM array_index_op_test WHERE i = '{47,77}' ORDER BY seqno;
     95 | {47,77} | {AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA74076,AAAAAAAAAA18107,AAAAA40681,AAAAAAAAAAAAAAA35875,AAAAA60038,AAAAAAA56483}
 (1 row)
 
+-- And try it with a multicolumn GIN index
+DROP INDEX intarrayidx, textarrayidx;
+CREATE INDEX botharrayidx ON array_index_op_test USING gin (i, t);
+SET enable_seqscan = OFF;
+SET enable_indexscan = ON;
+SET enable_bitmapscan = OFF;
+SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno;
+ seqno |                i                |                                                                 t                                                                  
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+     6 | {39,35,5,94,17,92,60,32}        | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+    74 | {32}                            | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+    77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+    89 | {40,32,17,6,30,88}              | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+    98 | {38,34,32,89}                   | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+   100 | {85,32,57,39,49,84,32,3,30}     | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_index_op_test WHERE i && '{32}' ORDER BY seqno;
+ seqno |                i                |                                                                 t                                                                  
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+     6 | {39,35,5,94,17,92,60,32}        | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+    74 | {32}                            | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+    77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+    89 | {40,32,17,6,30,88}              | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+    98 | {38,34,32,89}                   | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+   100 | {85,32,57,39,49,84,32,3,30}     | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAA80240}' ORDER BY seqno;
+ seqno |               i                |                                                                              t                                                                              
+-------+--------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------
+    19 | {52,82,17,74,23,46,69,51,75}   | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+    30 | {26,81,47,91,34}               | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240}
+    64 | {26,19,34,24,81,78}            | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240}
+    82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104}
+    88 | {41,90,77,24,6,24}             | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433}
+    97 | {54,2,86,65}                   | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643}
+   100 | {85,32,57,39,49,84,32,3,30}    | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(7 rows)
+
+SELECT * FROM array_index_op_test WHERE t && '{AAAAAAA80240}' ORDER BY seqno;
+ seqno |               i                |                                                                              t                                                                              
+-------+--------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------
+    19 | {52,82,17,74,23,46,69,51,75}   | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+    30 | {26,81,47,91,34}               | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240}
+    64 | {26,19,34,24,81,78}            | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240}
+    82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104}
+    88 | {41,90,77,24,6,24}             | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433}
+    97 | {54,2,86,65}                   | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643}
+   100 | {85,32,57,39,49,84,32,3,30}    | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(7 rows)
+
+SELECT * FROM array_index_op_test WHERE i @> '{32}' AND t && '{AAAAAAA80240}' ORDER BY seqno;
+ seqno |              i              |                                      t                                       
+-------+-----------------------------+------------------------------------------------------------------------------
+   100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(1 row)
+
+SELECT * FROM array_index_op_test WHERE i && '{32}' AND t @> '{AAAAAAA80240}' ORDER BY seqno;
+ seqno |              i              |                                      t                                       
+-------+-----------------------------+------------------------------------------------------------------------------
+   100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(1 row)
+
+SET enable_indexscan = OFF;
+SET enable_bitmapscan = ON;
+SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno;
+ seqno |                i                |                                                                 t                                                                  
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+     6 | {39,35,5,94,17,92,60,32}        | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+    74 | {32}                            | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+    77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+    89 | {40,32,17,6,30,88}              | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+    98 | {38,34,32,89}                   | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+   100 | {85,32,57,39,49,84,32,3,30}     | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_index_op_test WHERE i && '{32}' ORDER BY seqno;
+ seqno |                i                |                                                                 t                                                                  
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+     6 | {39,35,5,94,17,92,60,32}        | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+    74 | {32}                            | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+    77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+    89 | {40,32,17,6,30,88}              | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+    98 | {38,34,32,89}                   | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+   100 | {85,32,57,39,49,84,32,3,30}     | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAA80240}' ORDER BY seqno;
+ seqno |               i                |                                                                              t                                                                              
+-------+--------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------
+    19 | {52,82,17,74,23,46,69,51,75}   | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+    30 | {26,81,47,91,34}               | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240}
+    64 | {26,19,34,24,81,78}            | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240}
+    82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104}
+    88 | {41,90,77,24,6,24}             | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433}
+    97 | {54,2,86,65}                   | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643}
+   100 | {85,32,57,39,49,84,32,3,30}    | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(7 rows)
+
+SELECT * FROM array_index_op_test WHERE t && '{AAAAAAA80240}' ORDER BY seqno;
+ seqno |               i                |                                                                              t                                                                              
+-------+--------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------
+    19 | {52,82,17,74,23,46,69,51,75}   | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+    30 | {26,81,47,91,34}               | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240}
+    64 | {26,19,34,24,81,78}            | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240}
+    82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104}
+    88 | {41,90,77,24,6,24}             | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433}
+    97 | {54,2,86,65}                   | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643}
+   100 | {85,32,57,39,49,84,32,3,30}    | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(7 rows)
+
+SELECT * FROM array_index_op_test WHERE i @> '{32}' AND t && '{AAAAAAA80240}' ORDER BY seqno;
+ seqno |              i              |                                      t                                       
+-------+-----------------------------+------------------------------------------------------------------------------
+   100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(1 row)
+
+SELECT * FROM array_index_op_test WHERE i && '{32}' AND t @> '{AAAAAAA80240}' ORDER BY seqno;
+ seqno |              i              |                                      t                                       
+-------+-----------------------------+------------------------------------------------------------------------------
+   100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(1 row)
+
 RESET enable_seqscan;
 RESET enable_indexscan;
 RESET enable_bitmapscan;
diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql
index 447c5df37ac..9638b23312e 100644
--- a/src/test/regress/sql/create_index.sql
+++ b/src/test/regress/sql/create_index.sql
@@ -138,7 +138,7 @@ RESET enable_indexscan;
 RESET enable_bitmapscan;
 
 --
--- GIN over int[]
+-- GIN over int[] and text[]
 --
 
 SET enable_seqscan = OFF;
@@ -180,6 +180,33 @@ SELECT * FROM array_index_op_test WHERE i && '{32,17}' ORDER BY seqno;
 SELECT * FROM array_index_op_test WHERE i <@ '{38,34,32,89}' ORDER BY seqno;
 SELECT * FROM array_index_op_test WHERE i = '{47,77}' ORDER BY seqno;
 
+-- And try it with a multicolumn GIN index
+
+DROP INDEX intarrayidx, textarrayidx;
+
+CREATE INDEX botharrayidx ON array_index_op_test USING gin (i, t);
+
+SET enable_seqscan = OFF;
+SET enable_indexscan = ON;
+SET enable_bitmapscan = OFF;
+
+SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i && '{32}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAA80240}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t && '{AAAAAAA80240}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i @> '{32}' AND t && '{AAAAAAA80240}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i && '{32}' AND t @> '{AAAAAAA80240}' ORDER BY seqno;
+
+SET enable_indexscan = OFF;
+SET enable_bitmapscan = ON;
+
+SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i && '{32}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAA80240}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t && '{AAAAAAA80240}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i @> '{32}' AND t && '{AAAAAAA80240}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i && '{32}' AND t @> '{AAAAAAA80240}' ORDER BY seqno;
+
 RESET enable_seqscan;
 RESET enable_indexscan;
 RESET enable_bitmapscan;
-- 
GitLab