diff --git a/contrib/tsearch2/Makefile b/contrib/tsearch2/Makefile
index b7e4915ce76609cb00d285560e9d3ea0dd9bb203..3e322bb85c668af98e0a2f821915c3b35bed2852 100644
--- a/contrib/tsearch2/Makefile
+++ b/contrib/tsearch2/Makefile
@@ -1,4 +1,4 @@
-# $PostgreSQL: pgsql/contrib/tsearch2/Makefile,v 1.13 2006/01/27 16:32:31 teodor Exp $
+# $PostgreSQL: pgsql/contrib/tsearch2/Makefile,v 1.14 2006/05/02 11:28:54 teodor Exp $
 
 MODULE_big = tsearch2
 OBJS = dict_ex.o dict.o snmap.o stopword.o common.o prs_dcfg.o \
@@ -7,7 +7,7 @@ OBJS = dict_ex.o dict.o snmap.o stopword.o common.o prs_dcfg.o \
        ts_cfg.o tsvector.o query_cleanup.o crc32.o query.o gistidx.o \
        tsvector_op.o rank.o ts_stat.o \
        query_util.o query_support.o query_rewrite.o query_gist.o \
-       ts_locale.o
+       ts_locale.o ginidx.o
 
 SUBDIRS     := snowball ispell wordparser
 SUBDIROBJS  := $(SUBDIRS:%=%/SUBSYS.o)
diff --git a/contrib/tsearch2/expected/tsearch2.out b/contrib/tsearch2/expected/tsearch2.out
index 2a60e317cd652dfe067259498efd890d94e4e075..39a95b2f70adc2830561e05cdc5075b428836e2f 100644
--- a/contrib/tsearch2/expected/tsearch2.out
+++ b/contrib/tsearch2/expected/tsearch2.out
@@ -3001,3 +3001,42 @@ select a is null, a from test_tsvector order by a;
  t        |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
 (514 rows)
 
+drop index wowidx;
+create index wowidx on test_tsvector using gin (a);
+set enable_seqscan=off;
+SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh';
+ count 
+-------
+   158
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'wr&qh';
+ count 
+-------
+    17
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'eq&yt';
+ count 
+-------
+     6
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'eq|yt';
+ count 
+-------
+    98
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '(eq&yt)|(wr&qh)';
+ count 
+-------
+    23
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '(eq|yt)&(wr|qh)';
+ count 
+-------
+    39
+(1 row)
+
diff --git a/contrib/tsearch2/ginidx.c b/contrib/tsearch2/ginidx.c
new file mode 100644
index 0000000000000000000000000000000000000000..0a4e04e64a915764b6b4d996fbdfdeca7fc061c8
--- /dev/null
+++ b/contrib/tsearch2/ginidx.c
@@ -0,0 +1,145 @@
+#include "postgres.h"
+
+#include <float.h>
+
+#include "access/gist.h"
+#include "access/itup.h"
+#include "access/tuptoaster.h"
+#include "storage/bufpage.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+
+#include "tsvector.h"
+#include "query.h"
+#include "query_cleanup.h"
+
+PG_FUNCTION_INFO_V1(gin_extract_tsvector);
+Datum       gin_extract_tsvector(PG_FUNCTION_ARGS);
+
+Datum
+gin_extract_tsvector(PG_FUNCTION_ARGS) {
+	tsvector 	*vector = (tsvector *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
+	uint32  	*nentries = (uint32*)PG_GETARG_POINTER(1);
+	Datum		*entries = NULL;
+
+	*nentries = 0;
+	if ( vector->size > 0 ) {
+		int i;
+		WordEntry	*we = ARRPTR( vector );
+
+		*nentries = (uint32)vector->size;
+		entries = (Datum*)palloc( sizeof(Datum) * vector->size );
+
+		for(i=0;i<vector->size;i++) {
+			text 	*txt = (text*)palloc( VARHDRSZ + we->len );
+
+			VARATT_SIZEP(txt) = VARHDRSZ + we->len; 
+			memcpy( VARDATA(txt), STRPTR( vector ) + we->pos, we->len ); 
+
+			entries[i] = PointerGetDatum( txt );
+
+			we++;
+		}
+	}
+
+	PG_FREE_IF_COPY(vector, 0);
+	PG_RETURN_POINTER(entries);
+}
+
+
+PG_FUNCTION_INFO_V1(gin_extract_tsquery);
+Datum       gin_extract_tsquery(PG_FUNCTION_ARGS);
+
+Datum
+gin_extract_tsquery(PG_FUNCTION_ARGS) {
+	QUERYTYPE *query = (QUERYTYPE*) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
+	uint32      *nentries = (uint32*)PG_GETARG_POINTER(1);
+	StrategyNumber	strategy = DatumGetUInt16( PG_GETARG_DATUM(2) ); 
+	Datum       *entries = NULL;
+
+	*nentries = 0;
+	if ( query->size > 0 ) {
+		int4 i, j=0, len;
+		ITEM *item;
+
+		item = clean_NOT_v2(GETQUERY(query), &len);
+		if ( !item )
+			elog(ERROR,"Query requires full scan, GIN doesn't support it");
+
+		item = GETQUERY(query);
+
+		for(i=0; i<query->size; i++)
+			if ( item[i].type == VAL )
+				(*nentries)++;
+
+		entries = (Datum*)palloc( sizeof(Datum) * (*nentries) );
+
+		for(i=0; i<query->size; i++)
+			if ( item[i].type == VAL ) {
+				text 	*txt;
+
+				txt = (text*)palloc( VARHDRSZ + item[i].length );
+
+				VARATT_SIZEP(txt) = VARHDRSZ + item[i].length; 
+				memcpy( VARDATA(txt), GETOPERAND( query ) + item[i].distance, item[i].length );
+
+				entries[j++] = PointerGetDatum( txt );
+
+				if ( strategy == 1 && item[i].weight != 0 )
+					elog(ERROR,"With class of lexeme restrictions use @@@ operation");
+			}
+
+	}
+
+	PG_FREE_IF_COPY(query, 0);
+	PG_RETURN_POINTER(entries);
+}
+
+typedef struct {
+	ITEM	*frst;
+	bool	*mapped_check;
+} GinChkVal; 
+
+static bool
+checkcondition_gin(void *checkval, ITEM * val) {
+	GinChkVal	*gcv = (GinChkVal*)checkval;
+
+	return gcv->mapped_check[ val - gcv->frst ];
+}
+
+PG_FUNCTION_INFO_V1(gin_ts_consistent);
+Datum       gin_ts_consistent(PG_FUNCTION_ARGS);
+
+Datum
+gin_ts_consistent(PG_FUNCTION_ARGS) {
+	bool    *check = (bool*)PG_GETARG_POINTER(0);
+	QUERYTYPE *query = (QUERYTYPE*) PG_DETOAST_DATUM(PG_GETARG_DATUM(2));
+	bool	res = FALSE;
+
+	if ( query->size > 0 ) {
+		int4 i, j=0;
+		ITEM *item;
+		GinChkVal	gcv;
+
+		gcv.frst = item = GETQUERY(query); 
+		gcv.mapped_check= (bool*)palloc( sizeof(bool) * query->size );
+
+		for(i=0; i<query->size; i++)
+			if ( item[i].type == VAL )
+				gcv.mapped_check[ i ] = check[ j++ ];
+
+
+		res = TS_execute(
+			GETQUERY(query),
+			&gcv,
+			true,
+			checkcondition_gin
+		);
+
+	}
+
+	PG_FREE_IF_COPY(query, 2);
+	PG_RETURN_BOOL(res);
+}
+
+
diff --git a/contrib/tsearch2/sql/tsearch2.sql b/contrib/tsearch2/sql/tsearch2.sql
index 82a62224df97600161d53362e2277bbafe073b38..c6c149c121a06c40acc54b27b81bcb3151c7b1a8 100644
--- a/contrib/tsearch2/sql/tsearch2.sql
+++ b/contrib/tsearch2/sql/tsearch2.sql
@@ -363,3 +363,14 @@ select * from ts_debug('Tsearch module for PostgreSQL 7.3.3');
 insert into test_tsvector values (null, null);
 select a is null, a from test_tsvector order by a;
 
+drop index wowidx;
+create index wowidx on test_tsvector using gin (a);
+set enable_seqscan=off;
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'wr&qh';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'eq&yt';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'eq|yt';
+SELECT count(*) FROM test_tsvector WHERE a @@ '(eq&yt)|(wr&qh)';
+SELECT count(*) FROM test_tsvector WHERE a @@ '(eq|yt)&(wr|qh)';
+
diff --git a/contrib/tsearch2/tsearch.sql.in b/contrib/tsearch2/tsearch.sql.in
index 0ab0887b4596876904d340c1bec2a6db78311f7f..76b4c5bb9ece812e4720251f203859bdea6fe95a 100644
--- a/contrib/tsearch2/tsearch.sql.in
+++ b/contrib/tsearch2/tsearch.sql.in
@@ -1146,8 +1146,54 @@ AS
         FUNCTION        7       gtsq_same (gtsq, gtsq, internal),
         STORAGE         gtsq;
 
+--GIN support function
+CREATE FUNCTION gin_extract_tsvector(tsvector,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION gin_extract_tsquery(tsquery,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION gin_ts_consistent(internal,internal,tsquery)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C RETURNS NULL ON NULL INPUT;
+
+CREATE OPERATOR @@@ (
+        LEFTARG = tsvector,
+        RIGHTARG = tsquery,
+        PROCEDURE = exectsq,
+        COMMUTATOR = '@@@',
+        RESTRICT = contsel,
+        JOIN = contjoinsel
+);
+CREATE OPERATOR @@@ (
+        LEFTARG = tsquery,
+        RIGHTARG = tsvector,
+        PROCEDURE = rexectsq,
+        COMMUTATOR = '@@@',
+        RESTRICT = contsel,
+        JOIN = contjoinsel
+);
+
+CREATE OPERATOR CLASS gin_tsvector_ops
+DEFAULT FOR TYPE tsvector USING gin
+AS
+        OPERATOR        1       @@ (tsvector, tsquery),
+        OPERATOR        2       @@@ (tsvector, tsquery) RECHECK,
+        FUNCTION        1       bttextcmp(text, text),
+        FUNCTION        2       gin_extract_tsvector(tsvector,internal),
+        FUNCTION        3       gin_extract_tsquery(tsquery,internal,internal),
+        FUNCTION        4       gin_ts_consistent(internal,internal,tsquery),
+        STORAGE         text;
+
+
 --example of ISpell dictionary
 --update pg_ts_dict set dict_initoption='DictFile="/usr/local/share/ispell/russian.dict" ,AffFile ="/usr/local/share/ispell/russian.aff", StopFile="/usr/local/share/ispell/russian.stop"' where dict_name='ispell_template';
 --example of synonym dict
 --update pg_ts_dict set dict_initoption='/usr/local/share/ispell/english.syn' where dict_id=5;
+
 END;
diff --git a/src/backend/access/Makefile b/src/backend/access/Makefile
index 82cd7a90ed5d802bf4e44df92bf1730f2a8cc60c..8807fae4f144fffd7855c3617f17b56093f39b57 100644
--- a/src/backend/access/Makefile
+++ b/src/backend/access/Makefile
@@ -1,14 +1,14 @@
 #
 # Makefile for the access methods module
 #
-# $PostgreSQL: pgsql/src/backend/access/Makefile,v 1.10 2005/11/07 17:36:44 tgl Exp $
+# $PostgreSQL: pgsql/src/backend/access/Makefile,v 1.11 2006/05/02 11:28:54 teodor Exp $
 #
 
 subdir = src/backend/access
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
-SUBDIRS	    := common gist hash heap index nbtree transam
+SUBDIRS	    := common gist hash heap index nbtree transam gin
 SUBDIROBJS  := $(SUBDIRS:%=%/SUBSYS.o)
 
 all: SUBSYS.o
diff --git a/src/backend/access/gin/Makefile b/src/backend/access/gin/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..ac2271e3f554f377ec2bbe5e86f259e566725f28
--- /dev/null
+++ b/src/backend/access/gin/Makefile
@@ -0,0 +1,32 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for access/gin
+#
+# IDENTIFICATION
+#    $PostgreSQL: pgsql/src/backend/access/gin/Makefile,v 1.1 2006/05/02 11:28:54 teodor Exp $
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/access/gin
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = ginutil.o gininsert.o ginxlog.o ginentrypage.o gindatapage.o \
+	ginbtree.o ginscan.o ginget.o ginvacuum.o ginarrayproc.o \
+	ginbulk.o
+
+all: SUBSYS.o
+
+SUBSYS.o: $(OBJS)
+	$(LD) $(LDREL) $(LDOUT) SUBSYS.o $(OBJS)
+
+depend dep:
+	$(CC) -MM $(CFLAGS) *.c >depend
+
+clean: 
+	rm -f SUBSYS.o $(OBJS)
+
+ifeq (depend,$(wildcard depend))
+include depend
+endif
diff --git a/src/backend/access/gin/README b/src/backend/access/gin/README
new file mode 100644
index 0000000000000000000000000000000000000000..2827f672689cdfdf4d40f7fb6382d0d4c5a18da7
--- /dev/null
+++ b/src/backend/access/gin/README
@@ -0,0 +1,153 @@
+Gin for PostgreSQL
+==================
+
+Gin was sponsored by jfg://networks (http://www.jfg-networks.com/)
+
+Gin stands for Generalized Inverted Index and should be considered as a genie,
+not a drink.
+
+Generalized means that the index does not know which operation it accelerates.
+It instead works with custom strategies, defined for specific data types (read 
+"Index Method Strategies" in the PostgreSQL documentation). In that sense, Gin 
+is similar to GiST and differs from btree indices, which have predefined,
+comparison-based operations.
+
+An inverted index is an index structure storing a set of (key, posting list) 
+pairs, where 'posting list' is a set of documents in which the key occurs. 
+(A text document would usually contain many keys.)  The primary goal of 
+Gin indices is support for highly scalable, full-text search in PostgreSQL.
+
+Gin consists of a B-tree index constructed over entries (ET, entries tree),
+where each entry is an element of the indexed value (element of array, lexeme
+for tsvector) and where each tuple in a leaf page is either a pointer to a 
+B-tree over item pointers (PT, posting tree), or a list of item pointers 
+(PL, posting list) if the tuple is small enough.
+
+Note: There is no delete operation for ET. The reason for this is that from
+our experience, a set of unique words over a large collection change very
+rarely.  This greatly simplifies the code and concurrency algorithms.
+
+Gin comes with built-in support for one-dimensional arrays (eg. integer[], 
+text[]), but no support for NULL elements.  The following operations are
+available:
+
+  * contains: value_array @ query_array
+  * overlap: value_array && query_array
+  * contained: value_array ~ query_array
+
+Synopsis
+--------
+
+=# create index txt_idx on aa using gin(a);
+
+Features
+--------
+
+  * Concurrency
+  * Write-Ahead Logging (WAL).  (Recoverability from crashes.)
+  * User-defined opclasses.  (The scheme is similar to GiST.)
+  * Optimized index creation (Makes use of maintenance_work_mem to accumulate
+    postings in memory.)
+  * Tsearch2 support via an opclass
+  * Soft upper limit on the returned results set using a GUC variable:
+    gin_fuzzy_search_limit
+
+Gin Fuzzy Limit
+---------------
+
+There are often situations when a full-text search returns a very large set of
+results.  Since reading tuples from the disk and sorting them could take a
+lot of time, this is unacceptable for production.  (Note that the search 
+itself is very fast.)
+
+Such queries usually contain very frequent lexemes, so the results are not 
+very helpful. To facilitate execution of such queries Gin has a configurable 
+soft upper limit of the size of the returned set, determined by the 
+'gin_fuzzy_search_limit' GUC variable.  This is set to 0 by default (no 
+limit).
+
+If a non-zero search limit is set, then the returned set is a subset of the
+whole result set, chosen at random.
+
+"Soft" means that the actual number of returned results could slightly differ
+from the specified limit, depending on the query and the quality of the 
+system's random number generator.
+
+From experience, a value of 'gin_fuzzy_search_limit' in the thousands
+(eg. 5000-20000) works well.  This means that 'gin_fuzzy_search_limit' will
+have no effect for queries returning a result set with less tuples than this 
+number.
+
+Limitations
+-----------
+
+  * No support for multicolumn indices
+  * Gin doesn't uses scan->kill_prior_tuple & scan->ignore_killed_tuples
+  * Gin searches entries only by equality matching.  This may be improved in
+    future.
+  * Gin doesn't support full scans of indices.
+  * Gin doesn't index NULL values.
+
+Gin Interface
+-------------
+
+Opclass interface pseudocode. An example for a Gin opclass can be found in
+ginarayproc.c.
+
+Datum* extractValue(Datum inputValue, uint32* nentries)
+
+    Returns an array of Datum of entries of the value to be indexed.  nentries 
+    should contain the number of returned entries.
+    
+int compareEntry(Datum a, Datum b)
+
+    Compares two entries (not the indexing values)
+    
+Datum* extractQuery(Datum query, uint32* nentries, StrategyNumber n)
+
+    Returns an array of Datum of entries of the query to be executed.
+    n contains the strategy number of the operation.
+    
+bool consistent(bool[] check, StrategyNumber n, Datum query)
+
+    The size of the check array is the same as sizeof of the array returned by
+    extractQuery. Each element of the check array is true if the indexed value
+    has a corresponding entry in the query.  i.e. if (check[i] == TRUE) then 
+    the i-th entry of the query is present in the indexed value.  The Function
+    should return true if the indexed value matches by StrategyNumber and
+    the query.
+
+Open Items
+----------
+
+We appreciate any comments, help and suggestions.
+
+  * Teach optimizer/executor that GIN is intrinsically clustered. i.e., it
+    always returns ItemPointer in ascending order.
+  * Tweak gincostestimate.
+  * GIN stores several ItemPointer to heap tuple, so VACUUM FULL produces
+    this warning message:
+
+     WARNING:  index "idx" contains 88395 row versions, but table contains 
+     				51812 row versions
+     HINT:  Rebuild the index with REINDEX.
+	**** Workaround added
+
+TODO
+----
+
+Nearest future:
+
+  * Opclasses for all types (no programming, just many catalog changes).
+
+Distant future:
+
+  * Replace B-tree of entries to something like GiST
+  * Add multicolumn support
+  * Optimize insert operations (background index insertion)
+
+Authors
+-------
+
+All work was done by Teodor Sigaev (teodor@sigaev.ru) and Oleg Bartunov 
+(oleg@sai.msu.su).
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
new file mode 100644
index 0000000000000000000000000000000000000000..0fe213d2a8faf3f5dae54190f5e3f4e56bc48d22
--- /dev/null
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -0,0 +1,261 @@
+/*-------------------------------------------------------------------------
+ *
+ * ginvacuum.c
+ *    support function for GIN's indexing of any array
+ *
+ *
+ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *          $PostgreSQL: pgsql/src/backend/access/gin/ginarrayproc.c,v 1.1 2006/05/02 11:28:54 teodor Exp $
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "catalog/index.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "utils/array.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+#include "utils/typcache.h"
+#include "utils/builtins.h"
+#include "access/gin.h"
+
+#define GinOverlapStrategy		1
+#define GinContainsStrategy		2
+#define GinContainedStrategy	3
+
+#define	ARRAYCHECK(x) do { 									\
+	if ( ARR_HASNULL(x) )									\
+		ereport(ERROR, 										\
+			(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), 		\
+			 errmsg("array must not contain nulls"))); 		\
+															\
+	if ( ARR_NDIM(x) != 1 && ARR_NDIM(x) != 0 ) 			\
+		ereport(ERROR, 										\
+			(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), 		\
+			 errmsg("array must be one-dimensional"))); 	\
+} while(0) 
+
+/*
+ * Function used as extractValue and extractQuery both
+ */
+Datum
+ginarrayextract(PG_FUNCTION_ARGS) {
+	ArrayType	*array;
+	uint32	*nentries = (uint32*)PG_GETARG_POINTER(1); 
+	Datum	*entries = NULL;
+	int16 	elmlen;
+	bool	elmbyval;
+	char	elmalign;
+
+	/* we should guarantee that array will not be destroyed during all operation */
+	array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+
+	ARRAYCHECK(array);
+
+	get_typlenbyvalalign(ARR_ELEMTYPE(array),
+		 &elmlen, &elmbyval, &elmalign);
+
+	deconstruct_array(array,
+		ARR_ELEMTYPE(array),
+		elmlen, elmbyval, elmalign,
+		&entries, NULL, (int*)nentries);
+
+	/* we should not free array, entries[i] points into it */
+	PG_RETURN_POINTER(entries);
+}
+
+Datum
+ginarrayconsistent(PG_FUNCTION_ARGS) {
+	bool	*check = (bool*)PG_GETARG_POINTER(0);
+	StrategyNumber 	strategy = PG_GETARG_UINT16(1);
+	ArrayType   *query = PG_GETARG_ARRAYTYPE_P(2);
+	int res=FALSE, i, nentries=ArrayGetNItems(ARR_NDIM(query), ARR_DIMS(query));
+
+	/* we can do not check array carefully, it's done by previous ginarrayextract call */
+
+	switch( strategy ) {
+		case GinOverlapStrategy:
+		case GinContainedStrategy:
+			/* at least one element in check[] is true, so result = true */ 
+			res = TRUE;
+			break;
+		case GinContainsStrategy:
+			res = TRUE;
+			for(i=0;i<nentries;i++)
+				if ( !check[i] ) {
+					res = FALSE;
+					break;
+				}
+			break;
+		default:
+			elog(ERROR, "ginarrayconsistent: unknown strategy number: %d", strategy);
+	}
+
+	PG_RETURN_BOOL(res);
+}
+
+static TypeCacheEntry*
+fillTypeCacheEntry( TypeCacheEntry *typentry, Oid element_type ) {
+	if ( typentry && typentry->type_id == element_type )
+		return typentry;
+
+	typentry = lookup_type_cache(element_type,	TYPECACHE_EQ_OPR_FINFO);
+	if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_FUNCTION),
+				errmsg("could not identify an equality operator for type %s", format_type_be(element_type))));
+
+	return typentry;
+}
+
+static bool
+typeEQ(FunctionCallInfoData *locfcinfo, Datum a, Datum b) {
+	locfcinfo->arg[0] = a;
+	locfcinfo->arg[1] = b;
+	locfcinfo->argnull[0] = false;
+	locfcinfo->argnull[1] = false;
+	locfcinfo->isnull = false;
+
+	return DatumGetBool(FunctionCallInvoke(locfcinfo));
+}
+
+static bool
+ginArrayOverlap(TypeCacheEntry *typentry, ArrayType *a, ArrayType *b) {
+	Datum 	*da, *db;
+	int		na, nb, j, i;
+	FunctionCallInfoData locfcinfo;
+	
+	if ( ARR_ELEMTYPE(a) != ARR_ELEMTYPE(b) )
+		ereport(ERROR,
+			(errcode(ERRCODE_DATATYPE_MISMATCH),
+			errmsg("cannot compare arrays of different element types")));
+
+	ARRAYCHECK(a);
+	ARRAYCHECK(b);
+
+	deconstruct_array(a,
+		ARR_ELEMTYPE(a),
+		typentry->typlen, typentry->typbyval, typentry->typalign,
+		&da, NULL, &na);
+	deconstruct_array(b,
+		ARR_ELEMTYPE(b),
+		typentry->typlen, typentry->typbyval, typentry->typalign,
+		&db, NULL, &nb);
+
+	InitFunctionCallInfoData(locfcinfo, &typentry->eq_opr_finfo, 2,
+		NULL, NULL);
+
+	for(i=0;i<na;i++) {
+		for(j=0;j<nb;j++) {
+			if ( typeEQ(&locfcinfo, da[i], db[j]) ) {
+					pfree( da );
+					pfree( db );
+					return TRUE;
+			}
+		}
+	}
+
+	pfree( da );
+	pfree( db );
+
+	return FALSE;
+}
+
+static bool
+ginArrayContains(TypeCacheEntry *typentry, ArrayType *a, ArrayType *b) {
+	Datum 	*da, *db;
+	int		na, nb, j, i, n = 0;
+	FunctionCallInfoData locfcinfo;
+	
+	if ( ARR_ELEMTYPE(a) != ARR_ELEMTYPE(b) )
+		ereport(ERROR,
+			(errcode(ERRCODE_DATATYPE_MISMATCH),
+			errmsg("cannot compare arrays of different element types")));
+
+	ARRAYCHECK(a);
+	ARRAYCHECK(b);
+
+	deconstruct_array(a,
+		ARR_ELEMTYPE(a),
+		typentry->typlen, typentry->typbyval, typentry->typalign,
+		&da, NULL, &na);
+	deconstruct_array(b,
+		ARR_ELEMTYPE(b),
+		typentry->typlen, typentry->typbyval, typentry->typalign,
+		&db, NULL, &nb);
+
+	InitFunctionCallInfoData(locfcinfo, &typentry->eq_opr_finfo, 2,
+		NULL, NULL);
+
+	for(i=0;i<nb;i++) {
+		for(j=0;j<na;j++) {
+			if ( typeEQ(&locfcinfo, db[i], da[j]) ) {
+				n++;
+				break;
+			}
+		}
+	}
+
+	pfree( da );
+	pfree( db );
+
+	return ( n==nb ) ? TRUE : FALSE;
+}
+
+Datum
+arrayoverlap(PG_FUNCTION_ARGS) {
+	ArrayType   *a = PG_GETARG_ARRAYTYPE_P(0);
+	ArrayType   *b = PG_GETARG_ARRAYTYPE_P(1);
+	TypeCacheEntry *typentry = fillTypeCacheEntry( fcinfo->flinfo->fn_extra, ARR_ELEMTYPE(a) ); 
+	bool res;
+
+	fcinfo->flinfo->fn_extra = (void*)typentry;
+
+	res = ginArrayOverlap( typentry, a, b ); 
+
+	PG_FREE_IF_COPY(a,0);
+	PG_FREE_IF_COPY(b,1);
+
+	PG_RETURN_BOOL(res);
+}
+
+Datum
+arraycontains(PG_FUNCTION_ARGS) {
+	ArrayType   *a = PG_GETARG_ARRAYTYPE_P(0);
+	ArrayType   *b = PG_GETARG_ARRAYTYPE_P(1);
+	TypeCacheEntry *typentry = fillTypeCacheEntry( fcinfo->flinfo->fn_extra, ARR_ELEMTYPE(a) ); 
+	bool res;
+
+	fcinfo->flinfo->fn_extra = (void*)typentry;
+
+	res = ginArrayContains( typentry, a, b ); 
+
+	PG_FREE_IF_COPY(a,0);
+	PG_FREE_IF_COPY(b,1);
+
+	PG_RETURN_BOOL(res);
+}
+
+Datum
+arraycontained(PG_FUNCTION_ARGS) {
+	ArrayType   *a = PG_GETARG_ARRAYTYPE_P(0);
+	ArrayType   *b = PG_GETARG_ARRAYTYPE_P(1);
+	TypeCacheEntry *typentry = fillTypeCacheEntry( fcinfo->flinfo->fn_extra, ARR_ELEMTYPE(a) ); 
+	bool res;
+
+	fcinfo->flinfo->fn_extra = (void*)typentry;
+
+	res = ginArrayContains( typentry, b, a ); 
+
+	PG_FREE_IF_COPY(a,0);
+	PG_FREE_IF_COPY(b,1);
+
+	PG_RETURN_BOOL(res);
+}
+
diff --git a/src/backend/access/gin/ginbtree.c b/src/backend/access/gin/ginbtree.c
new file mode 100644
index 0000000000000000000000000000000000000000..821822c8a945f80d9f05937da5499a6bca856df7
--- /dev/null
+++ b/src/backend/access/gin/ginbtree.c
@@ -0,0 +1,394 @@
+/*-------------------------------------------------------------------------
+ *
+ * ginbtree.c
+ *    page utilities routines for the postgres inverted index access method.
+ *
+ *
+ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *          $PostgreSQL: pgsql/src/backend/access/gin/ginbtree.c,v 1.1 2006/05/02 11:28:54 teodor Exp $
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/gin.h"
+#include "access/heapam.h"
+#include "catalog/index.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+
+/*
+ * Locks buffer by needed method for search.
+ */
+static int
+ginTraverseLock(Buffer buffer, bool searchMode) {
+	Page	page;
+	int access=GIN_SHARE;
+
+	LockBuffer(buffer, GIN_SHARE);
+	page = BufferGetPage( buffer );
+	if ( GinPageIsLeaf(page) ) {
+		if ( searchMode == FALSE ) {
+			/* we should relock our page */
+			LockBuffer(buffer, GIN_UNLOCK);
+			LockBuffer(buffer, GIN_EXCLUSIVE);
+
+			/* But root can become non-leaf during relock */
+			if ( !GinPageIsLeaf(page) ) {
+				/* resore old lock type (very rare) */ 
+				LockBuffer(buffer, GIN_UNLOCK);
+				LockBuffer(buffer, GIN_SHARE);
+			} else
+				access = GIN_EXCLUSIVE;
+		}
+	}
+
+	return access;
+}
+
+GinBtreeStack*
+ginPrepareFindLeafPage(GinBtree btree, BlockNumber blkno) {
+	GinBtreeStack	*stack = (GinBtreeStack*)palloc(sizeof(GinBtreeStack));
+
+	stack->blkno = blkno;
+	stack->buffer = ReadBuffer(btree->index, stack->blkno);
+	stack->parent = NULL;
+	stack->predictNumber = 1;
+
+	ginTraverseLock(stack->buffer, btree->searchMode);
+
+	return stack;
+}
+
+/*
+ * Locates leaf page contained tuple
+ */
+GinBtreeStack* 
+ginFindLeafPage(GinBtree btree, GinBtreeStack *stack) {
+	bool isfirst=TRUE;
+	BlockNumber rootBlkno;
+
+	if ( !stack ) 
+		stack = ginPrepareFindLeafPage(btree, GIN_ROOT_BLKNO);
+	rootBlkno = stack->blkno;
+
+	for(;;) {
+		Page page;
+		BlockNumber child;
+		int access=GIN_SHARE;
+
+		stack->off = InvalidOffsetNumber;
+	
+		page = BufferGetPage( stack->buffer );
+
+		if ( isfirst ) {
+			if ( GinPageIsLeaf(page) && !btree->searchMode )
+				access = GIN_EXCLUSIVE;
+			isfirst = FALSE;
+		} else
+			access = ginTraverseLock(stack->buffer, btree->searchMode);
+
+		/* ok, page is correctly locked, we should check to move right ..,
+			root never has a right link, so small optimization */
+		while( btree->fullScan==FALSE && stack->blkno != rootBlkno && btree->isMoveRight(btree, page) ) {
+			BlockNumber rightlink = GinPageGetOpaque(page)->rightlink;
+
+			if ( rightlink==InvalidBlockNumber )
+				/* rightmost page */
+				break;
+
+			stack->blkno = rightlink;
+			LockBuffer(stack->buffer, GIN_UNLOCK);
+			stack->buffer = ReleaseAndReadBuffer(stack->buffer, btree->index, stack->blkno);
+			LockBuffer(stack->buffer, access);	
+			page = BufferGetPage( stack->buffer );
+		}
+
+		if ( GinPageIsLeaf(page) ) /* we found, return locked page */ 
+			return stack;
+
+		/* now we have correct buffer, try to find child */
+		child = btree->findChildPage(btree, stack);
+
+		LockBuffer(stack->buffer, GIN_UNLOCK);
+		Assert( child != InvalidBlockNumber ); 
+		Assert( stack->blkno != child );
+
+		if ( btree->searchMode ) { 
+			/* in search mode we may forget path to leaf */
+			stack->blkno = child;
+			stack->buffer = ReleaseAndReadBuffer( stack->buffer, btree->index, stack->blkno );
+		} else {
+			GinBtreeStack   *ptr = (GinBtreeStack*)palloc(sizeof(GinBtreeStack));
+
+			ptr->parent = stack;
+			stack = ptr;
+			stack->blkno = child;
+			stack->buffer = ReadBuffer(btree->index, stack->blkno);
+			stack->predictNumber = 1;
+		}
+	}
+
+	/* keep compiler happy */
+	return NULL;
+}
+
+void
+freeGinBtreeStack( GinBtreeStack *stack ) {
+	while(stack) {
+		GinBtreeStack	*tmp = stack->parent;
+		if ( stack->buffer != InvalidBuffer )
+			ReleaseBuffer(stack->buffer);
+
+		pfree( stack );
+		stack = tmp;
+	}
+}
+
+/*
+ * Try to find parent for current stack position, returns correct 
+ * parent and child's offset in  stack->parent.
+ * Function should never release root page to prevent conflicts
+ * with vacuum process
+ */
+void
+findParents( GinBtree btree, GinBtreeStack *stack, 
+			BlockNumber rootBlkno) {
+
+	Page	page;
+	Buffer	buffer;
+	BlockNumber	blkno, leftmostBlkno;
+	OffsetNumber offset;
+	GinBtreeStack	*root = stack->parent;
+	GinBtreeStack	*ptr;
+
+	if ( !root ) {
+		/* XLog mode... */
+		root = (GinBtreeStack*)palloc(sizeof(GinBtreeStack));
+		root->blkno = rootBlkno;
+		root->buffer = ReadBuffer(btree->index, rootBlkno);
+		LockBuffer(root->buffer, GIN_EXCLUSIVE);
+		root->parent = NULL;
+	} else { 
+		/* find root, we should not release root page until update is finished!! */
+		while( root->parent ) {
+			ReleaseBuffer( root->buffer );
+			root = root->parent;
+		}
+
+		Assert( root->blkno == rootBlkno );
+		Assert( BufferGetBlockNumber(root->buffer) == rootBlkno );
+		LockBuffer(root->buffer, GIN_EXCLUSIVE);
+	}
+	root->off = InvalidOffsetNumber;
+
+	page = BufferGetPage(root->buffer);
+	Assert( !GinPageIsLeaf(page) );
+
+	/* check trivial case */
+	if ( (root->off != btree->findChildPtr(btree, page, stack->blkno, InvalidOffsetNumber)) != InvalidBuffer ) {
+		stack->parent = root;
+		return;
+	}
+
+	leftmostBlkno = blkno = btree->getLeftMostPage(btree, page);
+	LockBuffer(root->buffer, GIN_UNLOCK );
+	Assert( blkno!=InvalidBlockNumber );
+
+
+	for(;;) {
+		buffer = ReadBuffer(btree->index, blkno);
+		LockBuffer(buffer, GIN_EXCLUSIVE);
+		page = BufferGetPage(root->buffer);
+		if ( GinPageIsLeaf(page) )
+			elog(ERROR, "Lost path");
+
+		leftmostBlkno = btree->getLeftMostPage(btree, page);
+
+		while( (offset = btree->findChildPtr(btree, page, stack->blkno, InvalidOffsetNumber))==InvalidOffsetNumber ) {
+			blkno = GinPageGetOpaque(page)->rightlink;
+			LockBuffer(buffer,GIN_UNLOCK);
+			ReleaseBuffer(buffer);
+			if ( blkno == InvalidBlockNumber )
+				break;
+			buffer = ReadBuffer(btree->index, blkno);
+			LockBuffer(buffer, GIN_EXCLUSIVE);
+			page = BufferGetPage(buffer);
+		}
+
+		if ( blkno != InvalidBlockNumber ) {
+			ptr = (GinBtreeStack*)palloc(sizeof(GinBtreeStack));
+			ptr->blkno = blkno;
+			ptr->buffer = buffer;
+			ptr->parent = root; /* it's may be wrong, but in next call we will correct */
+			stack->parent = ptr;
+			return;
+		}
+
+		blkno = leftmostBlkno;
+	}
+}
+
+/*
+ * Insert value (stored in GinBtree) to tree descibed by stack
+ */
+void
+ginInsertValue(GinBtree btree, GinBtreeStack *stack) {
+	GinBtreeStack	*parent = stack;
+	BlockNumber		rootBlkno = InvalidBuffer;	
+	Page			page, rpage, lpage;
+
+	/* remember root BlockNumber */
+	while( parent ) {
+		rootBlkno = parent->blkno;
+		parent = parent->parent;
+	}
+
+	while( stack ) {
+		XLogRecData *rdata;
+		BlockNumber	savedRightLink;
+
+		page = BufferGetPage( stack->buffer );
+		savedRightLink = GinPageGetOpaque(page)->rightlink;
+
+		if ( btree->isEnoughSpace( btree, stack->buffer, stack->off ) ) {
+			START_CRIT_SECTION();
+			btree->placeToPage( btree, stack->buffer, stack->off, &rdata );
+
+			if (!btree->index->rd_istemp) {
+				XLogRecPtr  recptr;
+
+				recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_INSERT, rdata);
+				PageSetLSN(page, recptr);
+				PageSetTLI(page, ThisTimeLineID);
+			} 
+
+			MarkBufferDirty( stack->buffer );
+			UnlockReleaseBuffer(stack->buffer);
+			END_CRIT_SECTION();
+
+			freeGinBtreeStack(stack->parent);
+			return;
+		} else {
+			Buffer rbuffer = GinNewBuffer(btree->index);
+			Page 	newlpage;
+
+			/* newlpage is a pointer to memory page, it does'nt assosiates with buffer,
+				stack->buffer shoud be untouched */
+			newlpage = btree->splitPage( btree, stack->buffer, rbuffer, stack->off, &rdata );
+
+
+			((ginxlogSplit*)(rdata->data))->rootBlkno = rootBlkno;
+
+			parent = stack->parent;
+
+			if ( parent == NULL ) {
+				/* split root, so we need to allocate new left page and
+			   		place pointer on root to left and right page */
+				Buffer	lbuffer = GinNewBuffer(btree->index);
+
+				((ginxlogSplit*)(rdata->data))->isRootSplit = TRUE;
+				((ginxlogSplit*)(rdata->data))->rrlink = InvalidBlockNumber;
+
+
+				page = BufferGetPage( stack->buffer );
+				lpage = BufferGetPage( lbuffer );
+				rpage = BufferGetPage( rbuffer );
+
+				GinPageGetOpaque(rpage)->rightlink = InvalidBlockNumber;
+				GinPageGetOpaque(newlpage)->rightlink = BufferGetBlockNumber(rbuffer);
+				((ginxlogSplit*)(rdata->data))->lblkno = BufferGetBlockNumber(lbuffer);
+
+				START_CRIT_SECTION();
+
+				GinInitBuffer( stack->buffer, GinPageGetOpaque(newlpage)->flags & ~GIN_LEAF );
+				PageRestoreTempPage( newlpage, lpage );		
+				btree->fillRoot( btree, stack->buffer, lbuffer, rbuffer );
+				if (!btree->index->rd_istemp) {
+					XLogRecPtr  recptr;
+
+					recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_SPLIT, rdata);
+					PageSetLSN(page, recptr);
+					PageSetTLI(page, ThisTimeLineID);
+					PageSetLSN(lpage, recptr);
+					PageSetTLI(lpage, ThisTimeLineID);
+					PageSetLSN(rpage, recptr);
+					PageSetTLI(rpage, ThisTimeLineID);
+				}
+
+				MarkBufferDirty(rbuffer);
+				UnlockReleaseBuffer(rbuffer);
+				MarkBufferDirty(lbuffer);
+				UnlockReleaseBuffer(lbuffer);
+				MarkBufferDirty(stack->buffer);
+				UnlockReleaseBuffer(stack->buffer);
+
+				END_CRIT_SECTION();
+				
+				return;
+			} else {
+				/* split non-root page */
+				((ginxlogSplit*)(rdata->data))->isRootSplit = FALSE;
+				((ginxlogSplit*)(rdata->data))->rrlink = savedRightLink;
+
+				lpage = BufferGetPage( stack->buffer );
+				rpage = BufferGetPage( rbuffer );
+
+				GinPageGetOpaque(rpage)->rightlink = savedRightLink;
+				GinPageGetOpaque(newlpage)->rightlink = BufferGetBlockNumber(rbuffer);
+
+				START_CRIT_SECTION();
+				PageRestoreTempPage( newlpage, lpage );		
+				if (!btree->index->rd_istemp) {
+					XLogRecPtr  recptr;
+
+					recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_SPLIT, rdata);
+					PageSetLSN(lpage, recptr);
+					PageSetTLI(lpage, ThisTimeLineID);
+					PageSetLSN(rpage, recptr);
+					PageSetTLI(rpage, ThisTimeLineID);
+				}
+				MarkBufferDirty(rbuffer);
+				UnlockReleaseBuffer(rbuffer);
+				MarkBufferDirty( stack->buffer );
+				END_CRIT_SECTION();
+			}
+		}
+
+		btree->isDelete = FALSE;
+
+		/* search parent to lock */
+		LockBuffer(parent->buffer, GIN_EXCLUSIVE);
+
+		/* move right if it's needed */
+		page = BufferGetPage( parent->buffer );
+		while( (parent->off=btree->findChildPtr(btree, page, stack->blkno, parent->off)) == InvalidOffsetNumber ) {
+			BlockNumber rightlink = GinPageGetOpaque(page)->rightlink;
+
+			LockBuffer(parent->buffer, GIN_UNLOCK);
+
+			if ( rightlink==InvalidBlockNumber ) { 
+				/* rightmost page, but we don't find parent, we should
+				   use plain search... */
+				findParents(btree, stack, rootBlkno);
+				parent=stack->parent;
+				page = BufferGetPage( parent->buffer );
+				break;
+			}
+
+			parent->blkno = rightlink;
+			parent->buffer = ReleaseAndReadBuffer(parent->buffer, btree->index, parent->blkno);
+			LockBuffer(parent->buffer, GIN_EXCLUSIVE);	
+			page = BufferGetPage( parent->buffer );
+		}
+
+		UnlockReleaseBuffer(stack->buffer);
+		pfree( stack );
+		stack = parent;
+	}
+}
+
+
diff --git a/src/backend/access/gin/ginbulk.c b/src/backend/access/gin/ginbulk.c
new file mode 100644
index 0000000000000000000000000000000000000000..6d73f070ba26c5eec42e4738ad9cd355814feb24
--- /dev/null
+++ b/src/backend/access/gin/ginbulk.c
@@ -0,0 +1,155 @@
+/*-------------------------------------------------------------------------
+ *
+ * ginbulk.c
+ *    routines for fast build of inverted index 
+ *
+ *
+ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *          $PostgreSQL: pgsql/src/backend/access/gin/ginbulk.c,v 1.1 2006/05/02 11:28:54 teodor Exp $
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/gin.h"
+#include "access/heapam.h"
+#include "catalog/index.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "utils/memutils.h"
+#include "access/tuptoaster.h"
+
+#define DEF_NENTRY	128
+#define DEF_NPTR	4
+
+void
+ginInitBA(BuildAccumulator *accum) {
+	
+		accum->number = 0;
+		accum->curget = 0;
+		accum->length = DEF_NENTRY;
+		accum->entries = (EntryAccumulator*)palloc0( sizeof(EntryAccumulator) * DEF_NENTRY );
+		accum->allocatedMemory = sizeof(EntryAccumulator) * DEF_NENTRY;
+}
+
+/*
+ * Stores heap item pointer. For robust, it checks that
+ * item pointer are ordered
+ */
+static void
+ginInsertData(BuildAccumulator *accum, EntryAccumulator *entry, ItemPointer heapptr) {
+	if ( entry->number >= entry->length ) {
+		accum->allocatedMemory += sizeof(ItemPointerData) * entry->length;
+		entry->length *= 2;
+		entry->list    = (ItemPointerData*)repalloc(entry->list,
+											sizeof(ItemPointerData)*entry->length);
+	}
+
+	if ( entry->shouldSort==FALSE ) {  
+		int res = compareItemPointers( entry->list + entry->number - 1, heapptr );
+
+		Assert( res != 0 );
+
+		if ( res > 0 )
+			entry->shouldSort=TRUE;
+	}
+
+	entry->list[ entry->number ] = *heapptr;
+	entry->number++;
+}
+
+/*
+ * Find/store one entry from indexed value.
+ * It supposes, that entry should be located between low and end of array of
+ * entries. Returns position of found/inserted entry
+ */
+static uint32
+ginInsertEntry(BuildAccumulator *accum, ItemPointer heapptr,  Datum entry, uint32 low) {
+	uint32 high = accum->number, mid;
+	int res;
+
+	while(high>low) { 
+		mid = low + ((high - low) / 2);
+
+		res = compareEntries(accum->ginstate, entry, accum->entries[mid].value);
+
+		if ( res == 0 ) {
+			ginInsertData( accum, accum->entries+mid, heapptr );
+			return mid;
+		} else if ( res > 0 )
+			low = mid + 1;
+		else
+			high = mid;
+	}
+	
+	/* did not find an entry, insert */
+	if ( accum->number >= accum->length ) {
+		accum->allocatedMemory += sizeof(EntryAccumulator) * accum->length;
+		accum->length *= 2;
+		accum->entries = (EntryAccumulator*)repalloc( accum->entries, 
+				sizeof(EntryAccumulator) * accum->length );
+	}
+
+	if ( high != accum->number ) 
+		memmove( accum->entries+high+1, accum->entries+high, sizeof(EntryAccumulator) * (accum->number-high) );
+
+	accum->entries[high].value  = entry;
+	accum->entries[high].length = DEF_NPTR;
+	accum->entries[high].number = 1;
+	accum->entries[high].shouldSort = FALSE;
+	accum->entries[high].list   = (ItemPointerData*)palloc(sizeof(ItemPointerData)*DEF_NPTR);
+	accum->entries[high].list[0] = *heapptr;
+
+	accum->allocatedMemory += sizeof(ItemPointerData)*DEF_NPTR;
+	accum->number++;
+
+	return high;
+}
+
+
+/*
+ * Insert one heap pointer. Requires entries to be sorted!
+ */
+void
+ginInsertRecordBA( BuildAccumulator *accum, ItemPointer heapptr, Datum *entries, uint32 nentry ) {
+	uint32 start=0,i;
+
+	for(i=0;i<nentry;i++)
+		start = ginInsertEntry( accum, heapptr, entries[i], start);
+}
+
+static int 
+qsortCompareItemPointers( const void *a, const void *b ) {
+	int res = compareItemPointers( (ItemPointer)a, (ItemPointer)b );
+	Assert( res!=0 );
+	return res;
+}
+
+ItemPointerData*
+ginGetEntry(BuildAccumulator *accum, Datum *value, uint32 *n) {
+	EntryAccumulator	*entry;
+
+	ItemPointerData *list;
+	if ( accum->curget >= accum->number )
+		return NULL;
+	else if ( accum->curget > 0 )
+		pfree( accum->entries[ accum->curget-1 ].list );
+
+	entry = accum->entries + accum->curget;
+	*n 		= entry->number;
+	*value 	= entry->value;
+	list 	= entry->list;
+	accum->curget++;
+
+	if ( entry->shouldSort && entry->number > 1 )
+		qsort(list, *n, sizeof(ItemPointerData), qsortCompareItemPointers);
+
+
+	return list;
+}
+
+
+
diff --git a/src/backend/access/gin/gindatapage.c b/src/backend/access/gin/gindatapage.c
new file mode 100644
index 0000000000000000000000000000000000000000..1069b19b92c2f0fda4954d2335f29b6279993b4c
--- /dev/null
+++ b/src/backend/access/gin/gindatapage.c
@@ -0,0 +1,569 @@
+/*-------------------------------------------------------------------------
+ *
+ * gindatapage.c
+ *    page utilities routines for the postgres inverted index access method.
+ *
+ *
+ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *          $PostgreSQL: pgsql/src/backend/access/gin/gindatapage.c,v 1.1 2006/05/02 11:28:54 teodor Exp $
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/gin.h"
+#include "access/heapam.h"
+#include "catalog/index.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+
+int
+compareItemPointers( ItemPointer a, ItemPointer b ) {
+	if ( GinItemPointerGetBlockNumber(a) == GinItemPointerGetBlockNumber(b) ) {
+		if ( GinItemPointerGetOffsetNumber(a) == GinItemPointerGetOffsetNumber(b) )
+			return 0;
+		return ( GinItemPointerGetOffsetNumber(a) > GinItemPointerGetOffsetNumber(b) ) ? 1 : -1;
+	} 
+
+	return ( GinItemPointerGetBlockNumber(a) > GinItemPointerGetBlockNumber(b) ) ? 1 : -1;
+}
+
+/*
+ * Merge two ordered array of itempointer
+ */
+void 
+MergeItemPointers(ItemPointerData *dst, ItemPointerData *a, uint32 na, ItemPointerData *b, uint32 nb) {
+	ItemPointerData *dptr = dst;
+	ItemPointerData *aptr = a, *bptr = b;
+
+	while( aptr - a < na && bptr - b < nb ) {
+		if ( compareItemPointers(aptr, bptr) > 0 ) 
+			*dptr++ = *bptr++;
+		else
+			*dptr++ = *aptr++;
+	}
+
+	while( aptr - a < na )
+		*dptr++ = *aptr++;
+
+	while( bptr - b < nb )
+		*dptr++ = *bptr++;
+}
+
+/*
+ * Checks, should we move to right link... 
+ * Compares inserting itemp pointer with right bound of current page
+ */
+static bool
+dataIsMoveRight(GinBtree btree, Page page) {
+	ItemPointer	iptr = GinDataPageGetRightBound(page);
+
+    if ( GinPageRightMost(page) )
+			return FALSE;
+
+	return ( compareItemPointers( btree->items + btree->curitem, iptr ) > 0 ) ? TRUE : FALSE;
+}
+
+/*
+ * Find correct PostingItem in non-leaf page. It supposed that
+ * page correctly choosen and searching value SHOULD be on page
+ */
+static BlockNumber
+dataLocateItem(GinBtree btree, GinBtreeStack *stack) {
+	OffsetNumber low, high, maxoff;
+	PostingItem	*pitem=NULL;
+	int result;
+	Page	page = BufferGetPage( stack->buffer );
+
+	Assert( !GinPageIsLeaf(page) );
+	Assert( GinPageIsData(page) );
+
+	if ( btree->fullScan ) {
+		stack->off = FirstOffsetNumber;
+		stack->predictNumber *= GinPageGetOpaque(page)->maxoff;
+		return btree->getLeftMostPage(btree, page);
+	}
+
+	low = FirstOffsetNumber;
+	maxoff = high = GinPageGetOpaque(page)->maxoff; 
+	Assert( high >= low );
+
+	high++;
+
+	while (high > low) {
+		OffsetNumber mid = low + ((high - low) / 2);
+		pitem = (PostingItem*)GinDataPageGetItem(page,mid);	
+
+		if ( mid == maxoff )
+			/* Right infinity, page already correctly choosen
+				with a help of dataIsMoveRight */
+			result = -1;
+		else {
+			pitem = (PostingItem*)GinDataPageGetItem(page,mid);
+			result = compareItemPointers( btree->items + btree->curitem, &( pitem->key ) );
+		}
+
+		if ( result == 0 ) {
+			stack->off = mid;
+			return PostingItemGetBlockNumber(pitem);
+		} else if ( result > 0 )
+			low = mid + 1;
+		else
+			high = mid;
+	}
+
+	Assert( high>=FirstOffsetNumber && high <= maxoff );
+
+	stack->off = high;
+	pitem = (PostingItem*)GinDataPageGetItem(page,high);
+	return PostingItemGetBlockNumber(pitem);
+}
+
+/* 
+ * Searches correct position for value on leaf page.
+ * Page should be corrrectly choosen. 
+ * Returns true if value found on page.
+ */
+static bool
+dataLocateLeafItem(GinBtree btree, GinBtreeStack *stack) {
+	Page page = BufferGetPage( stack->buffer );
+	OffsetNumber low, high;
+	int result;
+
+	Assert( GinPageIsLeaf(page) );
+	Assert( GinPageIsData(page) );
+
+	if ( btree->fullScan ) {
+		stack->off = FirstOffsetNumber;
+		return TRUE;
+	}
+
+	low=FirstOffsetNumber;
+	high = GinPageGetOpaque(page)->maxoff;
+
+	if ( high < low ) {
+		stack->off = FirstOffsetNumber;
+		return false;
+	}
+
+	high++;
+
+	while (high > low) {
+		OffsetNumber mid = low + ((high - low) / 2);
+
+		result = compareItemPointers( btree->items + btree->curitem, (ItemPointer)GinDataPageGetItem(page,mid) );
+
+		if ( result == 0 ) {
+			stack->off = mid;
+			return true;
+		} else if ( result > 0 )
+			low = mid + 1;
+		else
+			high = mid;
+	}
+
+	stack->off = high;
+	return false;
+}
+
+/*
+ * Finds links to blkno on non-leaf page, retuns
+ * offset of PostingItem
+ */
+static OffsetNumber
+dataFindChildPtr(GinBtree btree, Page page, BlockNumber blkno, OffsetNumber storedOff) {
+	OffsetNumber	i, maxoff = GinPageGetOpaque(page)->maxoff;
+	PostingItem *pitem;
+
+	Assert( !GinPageIsLeaf(page) );
+	Assert( GinPageIsData(page) );
+
+	/* if page isn't changed, we returns storedOff */
+	if ( storedOff>= FirstOffsetNumber && storedOff<=maxoff) {
+		pitem = (PostingItem*)GinDataPageGetItem(page, storedOff);
+		if ( PostingItemGetBlockNumber(pitem) == blkno )	
+			return storedOff;
+
+		/* we hope, that needed pointer goes to right. It's true
+			if there wasn't a deletion */
+		for( i=storedOff+1 ; i <= maxoff ; i++ ) {
+			pitem = (PostingItem*)GinDataPageGetItem(page, i);
+			if ( PostingItemGetBlockNumber(pitem) == blkno )
+				return i;
+		}
+
+		maxoff = storedOff-1;
+	}
+
+	/* last chance */
+	for( i=FirstOffsetNumber; i <= maxoff ; i++ ) {
+		pitem = (PostingItem*)GinDataPageGetItem(page, i);
+		if ( PostingItemGetBlockNumber(pitem) == blkno )
+			return i;
+	}
+
+	return InvalidOffsetNumber;
+}
+
+/*
+ * retunrs blkno of lefmost child
+ */
+static BlockNumber
+dataGetLeftMostPage(GinBtree btree, Page page) {
+	PostingItem *pitem;
+
+	Assert( !GinPageIsLeaf(page) );
+	Assert( GinPageIsData(page) );
+	Assert( GinPageGetOpaque(page)->maxoff >= FirstOffsetNumber );
+
+	pitem = (PostingItem*)GinDataPageGetItem(page, FirstOffsetNumber);
+	return PostingItemGetBlockNumber(pitem);
+}
+
+/*
+ * add ItemPointer or PostingItem to page. data should points to
+ * correct value! depending on leaf or non-leaf page
+ */
+void
+GinDataPageAddItem( Page page, void *data, OffsetNumber offset ) {
+	OffsetNumber maxoff = GinPageGetOpaque(page)->maxoff;
+	char *ptr;
+
+	if ( offset == InvalidOffsetNumber ) {
+		ptr = GinDataPageGetItem(page,maxoff+1);
+	} else {
+		ptr = GinDataPageGetItem(page,offset);
+		if ( maxoff+1-offset != 0 )
+			memmove( ptr+GinSizeOfItem(page), ptr, (maxoff-offset+1) * GinSizeOfItem(page) );
+	}
+	memcpy( ptr, data, GinSizeOfItem(page) );
+
+	GinPageGetOpaque(page)->maxoff++;
+}
+
+/*
+ * Deletes posting item from non-leaf page
+ */
+void
+PageDeletePostingItem(Page page, OffsetNumber offset) {
+	OffsetNumber    maxoff = GinPageGetOpaque(page)->maxoff;
+
+	Assert( !GinPageIsLeaf(page) );
+	Assert( offset>=FirstOffsetNumber && offset <= maxoff );
+
+	if ( offset != maxoff )
+		memmove( GinDataPageGetItem(page,offset), GinDataPageGetItem(page,offset+1),
+			sizeof(PostingItem) * (maxoff-offset) );
+
+	GinPageGetOpaque(page)->maxoff--;
+}
+
+/*
+ * checks space to install new value,
+ * item pointer never deletes!
+ */
+static bool
+dataIsEnoughSpace( GinBtree btree, Buffer buf, OffsetNumber off ) {
+	Page page = BufferGetPage(buf);
+
+	Assert( GinPageIsData(page) );
+	Assert( !btree->isDelete );
+
+	if ( GinPageIsLeaf(page) ) {
+		if ( GinPageRightMost(page) && off > GinPageGetOpaque(page)->maxoff ) {
+			if ( (btree->nitem - btree->curitem) * sizeof(ItemPointerData) <= GinDataPageGetFreeSpace(page) )
+				return true;
+		} else if ( sizeof(ItemPointerData) <= GinDataPageGetFreeSpace(page) )
+			return true;
+	} else if ( sizeof(PostingItem) <= GinDataPageGetFreeSpace(page) )
+		return true;
+
+	return false;
+}
+
+/*
+ * In case of previous split update old child blkno to
+ * new right page
+ * item pointer never deletes!
+ */
+static BlockNumber
+dataPrepareData( GinBtree btree, Page page, OffsetNumber off) { 
+	BlockNumber ret = InvalidBlockNumber;
+
+	Assert( GinPageIsData(page) );
+
+	if ( !GinPageIsLeaf(page) && btree->rightblkno != InvalidBlockNumber ) {
+		PostingItem *pitem = (PostingItem*)GinDataPageGetItem(page,off);
+		PostingItemSetBlockNumber( pitem, btree->rightblkno );
+		ret = btree->rightblkno;
+	}
+
+	btree->rightblkno = InvalidBlockNumber;
+
+	return ret;
+}
+
+/* 
+ * Places keys to page and fills WAL record. In case leaf page and
+ * build mode puts all ItemPointers to page.
+ */
+static void
+dataPlaceToPage(GinBtree btree, Buffer buf, OffsetNumber off, XLogRecData **prdata) {
+	Page page = BufferGetPage(buf);
+	static XLogRecData rdata[3];
+	int		sizeofitem =  GinSizeOfItem(page);
+	static  ginxlogInsert   data;
+
+	*prdata = rdata;
+	Assert( GinPageIsData(page) );
+
+	data.updateBlkno = dataPrepareData( btree, page, off );
+
+	data.node = btree->index->rd_node;
+	data.blkno = BufferGetBlockNumber( buf );
+	data.offset = off;
+	data.nitem = 1;
+	data.isDelete = FALSE;
+	data.isData = TRUE;
+	data.isLeaf = GinPageIsLeaf(page) ? TRUE : FALSE;
+
+	rdata[0].buffer = buf;
+	rdata[0].buffer_std = FALSE;
+	rdata[0].data = NULL;
+	rdata[0].len = 0;
+	rdata[0].next = &rdata[1];
+
+	rdata[1].buffer = InvalidBuffer;
+	rdata[1].data = (char *) &data;
+	rdata[1].len = sizeof(ginxlogInsert);
+	rdata[1].next = &rdata[2];
+
+	rdata[2].buffer = InvalidBuffer;
+	rdata[2].data = (GinPageIsLeaf(page)) ? ((char*)(btree->items+btree->curitem)) : ((char*)&(btree->pitem));
+	rdata[2].len = sizeofitem;
+	rdata[2].next = NULL;
+
+	if ( GinPageIsLeaf(page) ) {
+		if ( GinPageRightMost(page) && off > GinPageGetOpaque(page)->maxoff ) {
+			/* usually, create index... */
+			uint32 savedPos = btree->curitem;
+
+			while( btree->curitem < btree->nitem ) {
+				GinDataPageAddItem(page, btree->items+btree->curitem, off);
+				off++;
+				btree->curitem++;
+			}
+			data.nitem = btree->curitem-savedPos;
+			rdata[2].len = sizeofitem * data.nitem;
+		} else {
+			GinDataPageAddItem(page, btree->items+btree->curitem, off);
+			btree->curitem++;
+		}
+	} else
+			GinDataPageAddItem(page, &(btree->pitem), off);
+}
+
+/*
+ * split page and fills WAL record. original buffer(lbuf) leaves untouched,
+ * returns shadow page of lbuf filled new data. In leaf page and build mode puts all 
+ * ItemPointers to pages. Also, in build mode splits data by way to full fulled
+ * left page
+ */
+static Page
+dataSplitPage(GinBtree btree, Buffer lbuf, Buffer rbuf, OffsetNumber off, XLogRecData **prdata) {
+	static ginxlogSplit data;
+	static XLogRecData rdata[4];
+	static char vector[2*BLCKSZ];
+	char *ptr;
+	OffsetNumber separator;
+	ItemPointer	bound;
+	Page    lpage = GinPageGetCopyPage( BufferGetPage( lbuf ) );
+	ItemPointerData	oldbound = *GinDataPageGetRightBound(lpage);
+	int		sizeofitem =  GinSizeOfItem(lpage);
+	OffsetNumber maxoff = GinPageGetOpaque(lpage)->maxoff;
+	Page    rpage = BufferGetPage( rbuf );
+	Size    pageSize = PageGetPageSize( lpage );
+	Size	freeSpace;
+	uint32  nCopied = 1;
+
+	GinInitPage( rpage, GinPageGetOpaque(lpage)->flags, pageSize );
+	freeSpace = GinDataPageGetFreeSpace(rpage);
+
+	*prdata = rdata;
+    data.leftChildBlkno = ( GinPageIsLeaf(lpage) ) ?
+				InvalidOffsetNumber : PostingItemGetBlockNumber( &(btree->pitem) );
+	data.updateBlkno = dataPrepareData( btree, lpage, off );
+
+	memcpy(vector,  GinDataPageGetItem(lpage, FirstOffsetNumber),
+		maxoff*sizeofitem);
+
+	if ( GinPageIsLeaf(lpage) && GinPageRightMost(lpage) && off > GinPageGetOpaque(lpage)->maxoff ) {
+		nCopied = 0;
+		while( btree->curitem < btree->nitem && maxoff*sizeof(ItemPointerData) < 2*(freeSpace - sizeof(ItemPointerData)) ) {
+			memcpy( vector + maxoff*sizeof(ItemPointerData), btree->items+btree->curitem,
+					sizeof(ItemPointerData) );
+			maxoff++;
+			nCopied++;
+			btree->curitem++;
+		}
+	} else {
+		ptr = vector + (off-1)*sizeofitem;
+		if ( maxoff+1-off != 0 ) 
+			memmove( ptr+sizeofitem, ptr, (maxoff-off+1) * sizeofitem );
+		if ( GinPageIsLeaf(lpage) ) { 
+			memcpy(ptr,  btree->items+btree->curitem, sizeofitem );
+			btree->curitem++;
+		} else
+			memcpy(ptr,  &(btree->pitem), sizeofitem );
+	
+		maxoff++;
+	}
+
+	/* we suppose that during index creation table scaned from 
+		begin to end, so ItemPointers are monotonically increased.. */ 
+	if ( btree->isBuild && GinPageRightMost(lpage) )
+		separator=freeSpace/sizeofitem;
+	else
+		separator=maxoff/2;
+
+	GinInitPage( rpage, GinPageGetOpaque(lpage)->flags, pageSize );
+	GinInitPage( lpage, GinPageGetOpaque(rpage)->flags, pageSize );
+
+	memcpy( GinDataPageGetItem(lpage, FirstOffsetNumber), vector, separator * sizeofitem );
+	GinPageGetOpaque(lpage)->maxoff = separator;
+	memcpy( GinDataPageGetItem(rpage, FirstOffsetNumber), 
+			vector + separator * sizeofitem, (maxoff-separator) * sizeofitem );
+	GinPageGetOpaque(rpage)->maxoff = maxoff-separator;
+
+	PostingItemSetBlockNumber( &(btree->pitem), BufferGetBlockNumber(lbuf) );
+	if ( GinPageIsLeaf(lpage) ) 
+		btree->pitem.key = *(ItemPointerData*)GinDataPageGetItem(lpage, 
+				GinPageGetOpaque(lpage)->maxoff);
+	else 
+		btree->pitem.key = ((PostingItem*)GinDataPageGetItem(lpage, 
+				GinPageGetOpaque(lpage)->maxoff))->key;
+	btree->rightblkno = BufferGetBlockNumber(rbuf);
+
+	/* set up right bound for left page */
+	bound = GinDataPageGetRightBound(lpage);
+	*bound = btree->pitem.key;
+
+	/* set up right bound for right page */
+	bound = GinDataPageGetRightBound(rpage);
+	*bound = oldbound;
+
+	data.node = btree->index->rd_node;
+	data.rootBlkno = InvalidBlockNumber;
+	data.lblkno = BufferGetBlockNumber( lbuf );
+	data.rblkno = BufferGetBlockNumber( rbuf );
+	data.separator = separator;
+	data.nitem = maxoff;
+	data.isData = TRUE;
+	data.isLeaf = GinPageIsLeaf(lpage) ? TRUE : FALSE;
+	data.isRootSplit = FALSE;
+	data.rightbound = oldbound;
+
+	rdata[0].buffer = InvalidBuffer;
+	rdata[0].data = (char *) &data;
+	rdata[0].len = sizeof(ginxlogSplit);
+	rdata[0].next = &rdata[1];
+
+	rdata[1].buffer = InvalidBuffer;
+	rdata[1].data = vector;
+	rdata[1].len = MAXALIGN( maxoff * sizeofitem ); 
+	rdata[1].next = NULL;
+
+	return lpage;
+}
+
+/*
+ * Fills new root by right bound values from child. 
+ * Also called from ginxlog, should not use btree
+ */
+void
+dataFillRoot(GinBtree btree, Buffer root, Buffer lbuf, Buffer rbuf) {
+	Page 	page  = BufferGetPage(root),
+			lpage = BufferGetPage(lbuf),
+			rpage = BufferGetPage(rbuf);
+	PostingItem li, ri;
+
+	li.key = *GinDataPageGetRightBound(lpage);
+	PostingItemSetBlockNumber( &li, BufferGetBlockNumber(lbuf) );
+	GinDataPageAddItem(page, &li, InvalidOffsetNumber );
+
+	ri.key = *GinDataPageGetRightBound(rpage);
+	PostingItemSetBlockNumber( &ri, BufferGetBlockNumber(rbuf) );
+	GinDataPageAddItem(page, &ri, InvalidOffsetNumber );
+}
+
+void
+prepareDataScan( GinBtree btree, Relation index) {
+	memset(btree, 0, sizeof(GinBtreeData));
+	btree->index = index;
+	btree->isMoveRight = dataIsMoveRight;
+	btree->findChildPage = dataLocateItem;
+	btree->findItem = dataLocateLeafItem;
+	btree->findChildPtr = dataFindChildPtr;
+	btree->getLeftMostPage = dataGetLeftMostPage;
+	btree->isEnoughSpace = dataIsEnoughSpace;
+	btree->placeToPage = dataPlaceToPage;
+	btree->splitPage = dataSplitPage;
+	btree->fillRoot = dataFillRoot;
+
+	btree->searchMode = FALSE;
+    btree->isDelete = FALSE;
+	btree->fullScan = FALSE;
+	btree->isBuild= FALSE;
+}
+
+GinPostingTreeScan*
+prepareScanPostingTree( Relation index, BlockNumber rootBlkno, bool searchMode) {
+	GinPostingTreeScan	*gdi = (GinPostingTreeScan*)palloc0( sizeof(GinPostingTreeScan) );
+
+	prepareDataScan( &gdi->btree, index );
+	
+	gdi->btree.searchMode = searchMode;
+	gdi->btree.fullScan = searchMode;
+
+	gdi->stack = ginPrepareFindLeafPage( &gdi->btree, rootBlkno );
+
+	return gdi;
+}
+
+/*
+ * Inserts array of item pointers, may execute several tree scan (very rare)
+ */
+void
+insertItemPointer(GinPostingTreeScan *gdi, ItemPointerData *items, uint32 nitem) {
+	BlockNumber rootBlkno = gdi->stack->blkno;
+
+	gdi->btree.items = items;
+	gdi->btree.nitem = nitem;
+	gdi->btree.curitem = 0;
+
+	while( gdi->btree.curitem < gdi->btree.nitem ) {
+		if (!gdi->stack)
+			gdi->stack = ginPrepareFindLeafPage( &gdi->btree, rootBlkno );
+
+		gdi->stack = ginFindLeafPage( &gdi->btree, gdi->stack );	
+
+		if ( gdi->btree.findItem( &(gdi->btree), gdi->stack ) )
+			elog(ERROR,"Item pointer(%d:%d) is already exists", 
+				ItemPointerGetBlockNumber(gdi->btree.items + gdi->btree.curitem), 
+				ItemPointerGetOffsetNumber(gdi->btree.items + gdi->btree.curitem));
+
+		ginInsertValue(&(gdi->btree), gdi->stack);
+
+		gdi->stack=NULL;
+	}
+}
+
+Buffer
+scanBeginPostingTree( GinPostingTreeScan *gdi ) {
+	gdi->stack = ginFindLeafPage( &gdi->btree, gdi->stack );
+	return gdi->stack->buffer;
+}
+
diff --git a/src/backend/access/gin/ginentrypage.c b/src/backend/access/gin/ginentrypage.c
new file mode 100644
index 0000000000000000000000000000000000000000..81c109d6c4d84cf3ab181ec1cc5967bba9b275ed
--- /dev/null
+++ b/src/backend/access/gin/ginentrypage.c
@@ -0,0 +1,532 @@
+/*-------------------------------------------------------------------------
+ *
+ * ginentrypage.c
+ *    page utilities routines for the postgres inverted index access method.
+ *
+ *
+ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *          $PostgreSQL: pgsql/src/backend/access/gin/ginentrypage.c,v 1.1 2006/05/02 11:28:54 teodor Exp $
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/gin.h"
+#include "access/heapam.h"
+#include "catalog/index.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "access/tuptoaster.h"
+
+/*
+ * forms tuple for entry tree. On leaf page, Index tuple has
+ * non-traditional layout. Tuple may contain posting list or
+ * root blocknumber of posting tree. Macros GinIsPostingTre: (itup) / GinSetPostingTree(itup, blkno)
+ * 1) Posting list
+ *		- itup->t_info & INDEX_SIZE_MASK contains size of tuple as usial
+ *		- ItemPointerGetBlockNumber(&itup->t_tid) contains original
+ *		  size of tuple (without posting list). 
+ *		  Macroses: GinGetOrigSizePosting(itup) / GinSetOrigSizePosting(itup,n)
+ *		- ItemPointerGetOffsetNumber(&itup->t_tid) contains number
+ *		  of elements in posting list (number of heap itempointer)
+ *		  Macroses: GinGetNPosting(itup) / GinSetNPosting(itup,n)
+ *      - After usial part of tuple there is a posting list
+ *		  Macros: GinGetPosting(itup)
+ * 2) Posting tree
+ *		- itup->t_info & INDEX_SIZE_MASK contains size of tuple as usial
+ *	 	- ItemPointerGetBlockNumber(&itup->t_tid) contains block number of 
+ *		  root of posting tree
+ *		- ItemPointerGetOffsetNumber(&itup->t_tid) contains magick number GIN_TREE_POSTING
+ */
+IndexTuple
+GinFormTuple(GinState *ginstate, Datum key, ItemPointerData *ipd, uint32 nipd) {
+	bool	isnull=FALSE;
+	IndexTuple	itup;
+
+	itup = index_form_tuple(ginstate->tupdesc, &key, &isnull);	
+
+	GinSetOrigSizePosting( itup, IndexTupleSize(itup) );
+
+	if ( nipd > 0 ) {
+		uint32 newsize = MAXALIGN(SHORTALIGN(IndexTupleSize(itup)) + sizeof(ItemPointerData)*nipd);
+
+		if ( newsize >= INDEX_SIZE_MASK )
+			return NULL;
+
+		if ( newsize > TOAST_INDEX_TARGET && nipd > 1 )
+			return NULL;
+
+		itup = repalloc( itup, newsize );
+
+		/* set new size */
+		itup->t_info &= ~INDEX_SIZE_MASK;  
+		itup->t_info |= newsize;
+
+		if ( ipd )
+			memcpy( GinGetPosting(itup), ipd, sizeof(ItemPointerData)*nipd ); 
+		GinSetNPosting(itup, nipd); 
+	} else {
+		GinSetNPosting(itup, 0); 
+	}
+	return itup;
+}
+
+/*
+ * Entry tree is a "static", ie tuple never deletes from it,
+ * so we don't use right bound, we use rightest key instead.
+ */
+static IndexTuple
+getRightMostTuple(Page page) {
+	OffsetNumber maxoff = PageGetMaxOffsetNumber(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;
+
+	if ( GinPageRightMost(page) )
+		return FALSE;
+
+	highkey = ginGetHighKey(btree->ginstate, page);
+
+	if ( compareEntries(btree->ginstate, btree->entryValue, highkey) > 0 )
+		return TRUE;
+
+	return FALSE;
+}
+
+/*
+ * Find correct tuple in non-leaf page. It supposed that
+ * page correctly choosen and searching value SHOULD be on page
+ */
+static BlockNumber
+entryLocateEntry(GinBtree btree, GinBtreeStack *stack) {
+	OffsetNumber low, high, maxoff;
+	IndexTuple  itup;
+	int result;
+	Page page = BufferGetPage( stack->buffer );
+
+	Assert( !GinPageIsLeaf(page) );
+	Assert( !GinPageIsData(page) );
+
+	if ( btree->fullScan ) {
+		stack->off = FirstOffsetNumber;
+		stack->predictNumber *= PageGetMaxOffsetNumber(page);
+		return btree->getLeftMostPage(btree, page);
+	}
+
+	low = FirstOffsetNumber;
+	maxoff = high = PageGetMaxOffsetNumber(page);
+	Assert( high >= low );
+
+	high++;
+
+	while (high > low) {
+		OffsetNumber mid = low + ((high - low) / 2);
+
+		if ( mid == maxoff && GinPageRightMost(page) )
+			/* Right infinity */
+			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) );
+		}
+
+		if ( result == 0 ) {
+			stack->off = mid;
+			Assert( GinItemPointerGetBlockNumber(&(itup)->t_tid) != GIN_ROOT_BLKNO );
+			return GinItemPointerGetBlockNumber(&(itup)->t_tid);
+		} else if ( result > 0 )
+			low = mid + 1;
+		else
+			high = mid;
+	}
+
+	Assert( high>=FirstOffsetNumber && high <= maxoff );
+
+	stack->off = high;
+	itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, high));
+	Assert( GinItemPointerGetBlockNumber(&(itup)->t_tid) != GIN_ROOT_BLKNO );
+	return GinItemPointerGetBlockNumber(&(itup)->t_tid);
+}
+
+/*
+ * Searches correct position for value on leaf page.
+ * Page should be corrrectly choosen.
+ * Returns true if value found on page.
+ */
+static bool
+entryLocateLeafEntry(GinBtree btree, GinBtreeStack *stack) {
+	Page page = BufferGetPage( stack->buffer );
+	OffsetNumber low, high;
+	IndexTuple  itup;
+
+	Assert( GinPageIsLeaf(page) );
+	Assert( !GinPageIsData(page) );
+
+	if ( btree->fullScan ) {
+		stack->off = FirstOffsetNumber;
+		return TRUE;
+	}
+
+	low = FirstOffsetNumber;
+	high = PageGetMaxOffsetNumber(page);
+
+	if ( high < low ) {
+		stack->off = FirstOffsetNumber;
+		return false;
+	}
+
+	high++;
+
+	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) );
+
+		if ( result == 0 ) {
+			stack->off = mid;
+			return true;
+		} else if ( result > 0 ) 
+			low = mid + 1;
+		else
+			high = mid;
+	}
+
+	stack->off = high;
+	return false;
+}
+
+static OffsetNumber
+entryFindChildPtr(GinBtree btree, Page page, BlockNumber blkno, OffsetNumber storedOff) {
+	OffsetNumber i, maxoff = PageGetMaxOffsetNumber(page);
+	IndexTuple  itup;
+
+	Assert( !GinPageIsLeaf(page) );
+	Assert( !GinPageIsData(page) );
+
+	/* if page isn't changed, we returns storedOff */
+	if ( storedOff>= FirstOffsetNumber && storedOff<=maxoff) {
+		itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, storedOff));
+		if ( GinItemPointerGetBlockNumber(&(itup)->t_tid) == blkno )
+			return storedOff;
+
+		/* we hope, that needed pointer goes to right. It's true
+			if there wasn't a deletion */
+		for( i=storedOff+1 ; i <= maxoff ; i++ ) {
+			itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, i));
+			if ( GinItemPointerGetBlockNumber(&(itup)->t_tid) == blkno )
+				return i;
+		}
+		maxoff = storedOff-1;
+	}
+
+	/* last chance */
+	for( i=FirstOffsetNumber; i <= maxoff ; i++ ) { 
+		itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, i));
+		if ( GinItemPointerGetBlockNumber(&(itup)->t_tid) == blkno )
+			return i;
+	}
+
+	return InvalidOffsetNumber;
+}
+
+static BlockNumber
+entryGetLeftMostPage(GinBtree btree, Page page) {
+	IndexTuple  itup;
+
+	Assert( !GinPageIsLeaf(page) );
+	Assert( !GinPageIsData(page) );
+	Assert( PageGetMaxOffsetNumber(page) >= FirstOffsetNumber );
+
+	itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber));
+	return GinItemPointerGetBlockNumber(&(itup)->t_tid);	
+}
+
+static bool
+entryIsEnoughSpace( GinBtree btree, Buffer buf, OffsetNumber off ) {
+	Size itupsz = 0;
+	Page page = BufferGetPage(buf);
+
+	Assert( btree->entry );
+	Assert( !GinPageIsData(page) );
+
+	if ( btree->isDelete ) {
+		IndexTuple itup = (IndexTuple)PageGetItem(page, PageGetItemId(page, off));
+		itupsz = MAXALIGN( IndexTupleSize( itup ) ) + sizeof(ItemIdData);
+	}
+
+	if (  PageGetFreeSpace(page) + itupsz >=  MAXALIGN(IndexTupleSize(btree->entry)) + sizeof(ItemIdData) )
+		return true;
+
+	return false;
+}
+
+/*
+ * Delete tuple on leaf page if tuples was existed and we
+ * should update it, update old child blkno to new right page
+ * if child split is occured
+ */
+static BlockNumber
+entryPreparePage( GinBtree btree, Page page, OffsetNumber off) {
+	BlockNumber ret = InvalidBlockNumber;
+
+	Assert( btree->entry );
+	Assert( !GinPageIsData(page) );
+
+	if ( btree->isDelete ) {
+		Assert( GinPageIsLeaf(page) );
+		PageIndexTupleDelete(page, off);
+	}
+
+	if ( !GinPageIsLeaf(page) && btree->rightblkno != InvalidBlockNumber )  {
+		IndexTuple itup = (IndexTuple)PageGetItem(page, PageGetItemId(page, off));
+		ItemPointerSet(&itup->t_tid, btree->rightblkno, InvalidOffsetNumber);
+		ret = btree->rightblkno;
+	}
+
+	btree->rightblkno = InvalidBlockNumber;
+
+	return ret;
+}
+
+/*
+ * Place tuple on page and fills WAL record
+ */
+static void
+entryPlaceToPage(GinBtree btree, Buffer buf, OffsetNumber off, XLogRecData **prdata) {
+	Page page = BufferGetPage(buf);
+	static XLogRecData rdata[3];
+	OffsetNumber    placed;
+	static  ginxlogInsert   data;
+
+	*prdata = rdata;
+	data.updateBlkno = entryPreparePage( btree, page, off );
+
+	placed = PageAddItem( page, (Item)btree->entry, IndexTupleSize(btree->entry), off, LP_USED);
+	if ( placed != off )
+		elog(ERROR, "failed to add item to index page in \"%s\"",
+			RelationGetRelationName(btree->index));
+
+	data.node = btree->index->rd_node;
+	data.blkno = BufferGetBlockNumber( buf );
+	data.offset = off;
+	data.nitem = 1;
+	data.isDelete = btree->isDelete;
+	data.isData = false;
+	data.isLeaf = GinPageIsLeaf(page) ? TRUE : FALSE;
+
+	rdata[0].buffer = buf;
+	rdata[0].buffer_std = TRUE;
+	rdata[0].data = NULL;
+	rdata[0].len = 0;
+	rdata[0].next = &rdata[1];
+
+	rdata[1].buffer = InvalidBuffer;
+	rdata[1].data = (char *) &data;
+	rdata[1].len = sizeof(ginxlogInsert);
+	rdata[1].next = &rdata[2];
+
+	rdata[2].buffer = InvalidBuffer;
+	rdata[2].data = (char *) btree->entry;
+	rdata[2].len = IndexTupleSize(btree->entry);
+	rdata[2].next = NULL;
+
+	btree->entry = NULL;
+}
+
+/*
+ * Place tuple and split page, original buffer(lbuf) leaves untouched,
+ * returns shadow page of lbuf filled new data.
+ * Tuples are distributed between pages by equal size on its, not
+ * an equal number!
+ */
+static Page
+entrySplitPage(GinBtree btree, Buffer lbuf, Buffer rbuf, OffsetNumber off, XLogRecData **prdata) {
+	static XLogRecData rdata[2];
+	OffsetNumber	i, maxoff, separator=InvalidOffsetNumber;
+	Size	totalsize=0;
+	Size	lsize = 0, size;
+	static char	tupstore[ 2*BLCKSZ ];
+	char 	*ptr;
+	IndexTuple itup, leftrightmost=NULL;
+	static ginxlogSplit	data;
+	Datum	value;
+	bool	isnull;
+	Page 	page;
+	Page	lpage = GinPageGetCopyPage( BufferGetPage( lbuf ) );
+	Page	rpage = BufferGetPage( rbuf );
+	Size    pageSize = PageGetPageSize( lpage ); 
+
+	*prdata = rdata;
+	data.leftChildBlkno = ( GinPageIsLeaf(lpage) ) ? 
+			InvalidOffsetNumber : GinItemPointerGetBlockNumber( &(btree->entry->t_tid) );
+	data.updateBlkno = entryPreparePage( btree, lpage, off );
+
+	maxoff = PageGetMaxOffsetNumber(lpage);
+	ptr = tupstore;	
+
+	for(i=FirstOffsetNumber; i<=maxoff; i++) {
+		if ( i==off ) {
+			size = MAXALIGN( IndexTupleSize(btree->entry) );
+			memcpy(ptr, btree->entry, size);
+			ptr+=size;
+			totalsize += size + sizeof(ItemIdData);
+		}
+
+		itup = (IndexTuple)PageGetItem(lpage, PageGetItemId(lpage, i));
+		size = MAXALIGN( IndexTupleSize(itup) );
+		memcpy(ptr, itup, size);
+		ptr+=size;
+		totalsize += size + sizeof(ItemIdData);
+	}
+
+	if ( off==maxoff+1 ) {
+		size = MAXALIGN( IndexTupleSize(btree->entry) );
+		memcpy(ptr, btree->entry, size);
+		ptr+=size;
+		totalsize += size + sizeof(ItemIdData);
+	}
+
+	GinInitPage( rpage, GinPageGetOpaque(lpage)->flags, pageSize ); 
+	GinInitPage( lpage, GinPageGetOpaque(rpage)->flags, pageSize ); 
+
+	ptr = tupstore;
+	maxoff++;	
+	lsize = 0;
+
+	page = lpage;
+	for(i=FirstOffsetNumber; i<=maxoff; i++) {
+		itup = (IndexTuple)ptr;
+
+		if ( lsize > totalsize/2 ) {
+			if ( separator==InvalidOffsetNumber )
+				separator = i-1;
+			page = rpage;
+		} else {
+			leftrightmost = itup;
+			lsize += MAXALIGN( IndexTupleSize(itup) ) + sizeof(ItemIdData);
+		}
+
+		if ( PageAddItem( page, (Item)itup, IndexTupleSize(itup), InvalidOffsetNumber, LP_USED) == InvalidOffsetNumber )
+			elog(ERROR, "failed to add item to index page in \"%s\"",
+				RelationGetRelationName(btree->index));
+		ptr += MAXALIGN( IndexTupleSize(itup) );
+	}
+	
+	value = index_getattr(leftrightmost, FirstOffsetNumber, btree->ginstate->tupdesc, &isnull);
+	btree->entry = GinFormTuple( btree->ginstate, value, NULL, 0);
+	ItemPointerSet(&(btree->entry)->t_tid, BufferGetBlockNumber( lbuf ), InvalidOffsetNumber);
+	btree->rightblkno = BufferGetBlockNumber( rbuf );
+		
+	data.node = btree->index->rd_node;
+	data.rootBlkno = InvalidBlockNumber;
+	data.lblkno = BufferGetBlockNumber( lbuf );
+	data.rblkno = BufferGetBlockNumber( rbuf );
+	data.separator = separator;
+	data.nitem = maxoff;
+	data.isData = FALSE;
+	data.isLeaf = GinPageIsLeaf(lpage) ? TRUE : FALSE;
+	data.isRootSplit = FALSE;
+
+	rdata[0].buffer = InvalidBuffer;
+	rdata[0].data = (char *) &data;
+	rdata[0].len = sizeof(ginxlogSplit);
+	rdata[0].next = &rdata[1];
+
+	rdata[1].buffer = InvalidBuffer;
+	rdata[1].data = tupstore;
+	rdata[1].len = MAXALIGN(totalsize);
+	rdata[1].next = NULL;
+
+	return lpage;
+}
+
+/* 
+ * return newly allocate rightmost tuple
+ */
+IndexTuple
+ginPageGetLinkItup(Buffer buf) {
+	IndexTuple itup, nitup;
+	Page	page = BufferGetPage(buf);
+
+	itup = getRightMostTuple( page );
+	if ( GinPageIsLeaf(page) && !GinIsPostingTree(itup) ) {
+		nitup = (IndexTuple)palloc( MAXALIGN(GinGetOrigSizePosting(itup)) );
+		memcpy( nitup, itup, GinGetOrigSizePosting(itup) );
+		nitup->t_info &= ~INDEX_SIZE_MASK;
+		nitup->t_info |= GinGetOrigSizePosting(itup);
+	} else {
+		nitup = (IndexTuple)palloc( MAXALIGN(IndexTupleSize(itup)) );
+		memcpy( nitup, itup, IndexTupleSize(itup) );
+	}
+
+	ItemPointerSet(&nitup->t_tid, BufferGetBlockNumber(buf), InvalidOffsetNumber);
+	return nitup;
+}
+
+/*
+ * Fills new root by rightest values from child.
+ * Also called from ginxlog, should not use btree
+ */
+void
+entryFillRoot(GinBtree btree, Buffer root, Buffer lbuf, Buffer rbuf) {
+	Page page;
+	IndexTuple itup;
+
+	page = BufferGetPage(root);
+
+	itup = ginPageGetLinkItup( lbuf );
+	if ( PageAddItem( page, (Item)itup, IndexTupleSize(itup), InvalidOffsetNumber, LP_USED) == InvalidOffsetNumber )
+		elog(ERROR, "failed to add item to index root page");
+
+	itup = ginPageGetLinkItup( rbuf );
+	if ( PageAddItem( page, (Item)itup, IndexTupleSize(itup), InvalidOffsetNumber, LP_USED) == InvalidOffsetNumber )
+		elog(ERROR, "failed to add item to index root page");
+}
+
+void
+prepareEntryScan( GinBtree btree, Relation index, Datum value, GinState *ginstate) {
+	memset(btree, 0, sizeof(GinBtreeData));
+
+	btree->isMoveRight = entryIsMoveRight;
+	btree->findChildPage = entryLocateEntry;
+	btree->findItem = entryLocateLeafEntry;
+	btree->findChildPtr = entryFindChildPtr;
+	btree->getLeftMostPage = entryGetLeftMostPage;
+	btree->isEnoughSpace = entryIsEnoughSpace;
+	btree->placeToPage = entryPlaceToPage;
+	btree->splitPage = entrySplitPage;
+	btree->fillRoot = entryFillRoot;
+
+	btree->index = index;
+	btree->ginstate = ginstate;
+	btree->entryValue = value;
+
+	btree->isDelete = FALSE;
+	btree->searchMode = FALSE;
+	btree->fullScan = FALSE;
+	btree->isBuild = FALSE;
+}
+
diff --git a/src/backend/access/gin/ginget.c b/src/backend/access/gin/ginget.c
new file mode 100644
index 0000000000000000000000000000000000000000..e160197e0bb6f393ebc22f844276ee47e8448c5e
--- /dev/null
+++ b/src/backend/access/gin/ginget.c
@@ -0,0 +1,412 @@
+/*-------------------------------------------------------------------------
+ *
+ * ginget.c
+ *    fetch tuples from a GIN scan.
+ *
+ *
+ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *          $PostgreSQL: pgsql/src/backend/access/gin/ginget.c,v 1.1 2006/05/02 11:28:54 teodor Exp $
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/gin.h"
+#include "access/heapam.h"
+#include "catalog/index.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "utils/memutils.h"
+
+static OffsetNumber
+findItemInPage( Page page, ItemPointer item, OffsetNumber off ) {
+	OffsetNumber maxoff = GinPageGetOpaque(page)->maxoff;
+	int res;
+
+	for(; off<=maxoff; off++) {
+		res = compareItemPointers( item, (ItemPointer)GinDataPageGetItem(page, off) );
+		Assert( res>= 0 );
+
+		if ( res == 0 )
+			return off;
+	}
+
+	return InvalidOffsetNumber;
+}
+
+/*
+ * Start* functions setup state of searches: find correct buffer and locks it,
+ * Stop* functions unlock buffer (but don't release!)
+ */
+static void
+startScanEntry( Relation index, GinState *ginstate, GinScanEntry entry, bool firstCall ) {
+	if ( entry->master != NULL ) {
+		entry->isFinished = entry->master->isFinished;
+		return;
+	}
+
+	if ( firstCall ) {
+		/* at first call we should find entry, and
+		   begin scan of posting tree or just store posting list in memory */
+		GinBtreeData btreeEntry;
+		GinBtreeStack	*stackEntry;
+		Page page;
+		bool	needUnlock = TRUE;
+
+		prepareEntryScan( &btreeEntry, index, entry->entry, ginstate );
+		btreeEntry.searchMode = TRUE;
+		stackEntry = ginFindLeafPage(&btreeEntry, NULL);
+		page = BufferGetPage( stackEntry->buffer );
+
+		entry->isFinished = TRUE;
+		entry->buffer = InvalidBuffer;
+		entry->offset = InvalidOffsetNumber;
+		entry->list = NULL;
+		entry->nlist = 0;
+		entry->reduceResult = FALSE;
+		entry->predictNumberResult = 0;
+
+		if ( btreeEntry.findItem( &btreeEntry, stackEntry ) ) {
+			IndexTuple itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, stackEntry->off));
+
+			if ( GinIsPostingTree(itup) ) {
+				BlockNumber rootPostingTree = GinGetPostingTree(itup);
+				GinPostingTreeScan *gdi;
+				Page	page;
+
+				LockBuffer(stackEntry->buffer, GIN_UNLOCK);
+				needUnlock	= FALSE;
+				gdi = prepareScanPostingTree( index, rootPostingTree, TRUE );
+
+				entry->buffer = scanBeginPostingTree( gdi );
+				IncrBufferRefCount( entry->buffer );
+
+				page = BufferGetPage( entry->buffer );
+				entry->predictNumberResult = gdi->stack->predictNumber * GinPageGetOpaque(page)->maxoff;  
+
+				freeGinBtreeStack( gdi->stack );
+				pfree( gdi );
+				entry->isFinished = FALSE;
+			} else if ( GinGetNPosting(itup) > 0 ) {
+				entry->nlist = GinGetNPosting(itup);
+				entry->list = (ItemPointerData*)palloc( sizeof(ItemPointerData) * entry->nlist );
+				memcpy( entry->list, GinGetPosting(itup), sizeof(ItemPointerData) * entry->nlist );
+				entry->isFinished = FALSE;
+			}
+		}
+
+		if ( needUnlock )
+			LockBuffer(stackEntry->buffer, GIN_UNLOCK);
+		freeGinBtreeStack( stackEntry );
+	} else if ( entry->buffer != InvalidBuffer ) {
+		/* we should find place were we was stopped */
+		BlockNumber blkno;
+		Page page;
+
+		LockBuffer( entry->buffer, GIN_SHARE );
+
+		if ( !ItemPointerIsValid( &entry->curItem ) )
+			/* start position */
+			return;
+		Assert( entry->offset!=InvalidOffsetNumber );
+
+		page = BufferGetPage( entry->buffer );
+
+		/* try to find curItem in current buffer */
+		if ( (entry->offset=findItemInPage(page , &entry->curItem, entry->offset))!=InvalidOffsetNumber )
+			return;
+
+		/* walk to right */
+		while( (blkno = GinPageGetOpaque( page )->rightlink)!=InvalidBlockNumber ) {
+			LockBuffer( entry->buffer, GIN_UNLOCK );
+			entry->buffer = ReleaseAndReadBuffer( entry->buffer, index, blkno );
+			LockBuffer( entry->buffer, GIN_SHARE );
+			page = BufferGetPage( entry->buffer );
+
+			if ( (entry->offset=findItemInPage(page , &entry->curItem, FirstOffsetNumber))!=InvalidOffsetNumber )
+				return;
+		}
+
+		elog(ERROR,"Logic error: lost previously founded ItemId");
+	}
+}
+
+static void
+stopScanEntry( GinScanEntry entry ) {
+	if ( entry->buffer != InvalidBuffer )
+		LockBuffer( entry->buffer, GIN_UNLOCK );
+}
+
+static void
+startScanKey( Relation index, GinState *ginstate, GinScanKey key ) {
+	uint32 i;
+
+	for(i=0;i<key->nentries;i++)
+		startScanEntry( index, ginstate, key->scanEntry+i, key->firstCall );
+	
+	if ( key->firstCall ) { 
+		memset( key->entryRes, TRUE, sizeof(bool) * key->nentries );
+		key->isFinished = FALSE;
+		key->firstCall = FALSE;
+
+		if ( GinFuzzySearchLimit > 0 ) {
+			/*
+			 * If all of keys more than treshold we will try to reduce
+			 * result, we hope (and only hope, for intersection operation of array
+			 * our supposition isn't true), that total result will not more
+			 * than minimal predictNumberResult.
+			 */
+
+			for(i=0;i<key->nentries;i++)
+				if ( key->scanEntry[i].predictNumberResult <= key->nentries * GinFuzzySearchLimit )
+					return;	
+			
+			for(i=0;i<key->nentries;i++)
+				if ( key->scanEntry[i].predictNumberResult > key->nentries * GinFuzzySearchLimit ) { 
+					key->scanEntry[i].predictNumberResult /= key->nentries;
+					key->scanEntry[i].reduceResult = TRUE;
+				}
+		}
+	}
+}
+
+static void
+stopScanKey( GinScanKey key ) {
+	uint32 i;
+
+	for(i=0;i<key->nentries;i++)
+		stopScanEntry( key->scanEntry+i );
+}
+
+static void
+startScan( IndexScanDesc scan ) {
+	uint32 i;
+	GinScanOpaque   so = (GinScanOpaque) scan->opaque;
+
+	for(i=0; i<so->nkeys; i++) 
+		startScanKey( scan->indexRelation, &so->ginstate, so->keys + i ); 
+}
+
+static void
+stopScan( IndexScanDesc scan ) {
+	uint32 i;
+	GinScanOpaque   so = (GinScanOpaque) scan->opaque;
+
+	for(i=0; i<so->nkeys; i++) 
+		stopScanKey( so->keys + i ); 
+}
+
+
+static void
+entryGetNextItem( Relation index, GinScanEntry entry ) {
+	Page page = BufferGetPage( entry->buffer );
+
+	entry->offset++;
+	if ( entry->offset <= GinPageGetOpaque( page )->maxoff && GinPageGetOpaque( page )->maxoff >= FirstOffsetNumber ) { 
+		entry->curItem = *(ItemPointerData*)GinDataPageGetItem(page, entry->offset);
+	} else {
+		BlockNumber blkno = GinPageGetOpaque( page )->rightlink;
+			
+		LockBuffer( entry->buffer, GIN_UNLOCK );
+		if ( blkno == InvalidBlockNumber ) {
+			ReleaseBuffer( entry->buffer );
+			entry->buffer = InvalidBuffer;
+			entry->isFinished = TRUE;
+		} else {
+			entry->buffer = ReleaseAndReadBuffer( entry->buffer, index, blkno );
+			LockBuffer( entry->buffer, GIN_SHARE );
+			entry->offset = InvalidOffsetNumber;
+			entryGetNextItem(index, entry);
+		}
+	}
+}
+
+#define gin_rand() (((double) random()) / ((double) MAX_RANDOM_VALUE))
+#define dropItem(e)	( gin_rand() > ((double)GinFuzzySearchLimit)/((double)((e)->predictNumberResult)) ) 
+
+/*
+ * Sets entry->curItem to new found heap item pointer for one 
+ * entry of one scan key
+ */
+static bool
+entryGetItem( Relation index, GinScanEntry entry ) {
+	if ( entry->master ) {
+		entry->isFinished = entry->master->isFinished;
+		entry->curItem = entry->master->curItem;
+	} else if ( entry->list ) {
+		entry->offset++;
+		if ( entry->offset <= entry->nlist ) 
+			entry->curItem = entry->list[ entry->offset - 1 ];
+		else {
+			ItemPointerSet( &entry->curItem, InvalidBlockNumber, InvalidOffsetNumber );
+			entry->isFinished = TRUE;
+		}
+	} else {
+		do {
+			entryGetNextItem(index, entry);
+		} while ( entry->isFinished == FALSE && entry->reduceResult == TRUE && dropItem(entry) ); 
+	}
+
+	return entry->isFinished;
+}
+
+/*
+ * Sets key->curItem to new found heap item pointer for one scan key
+ * returns isFinished!
+ */
+static bool
+keyGetItem( Relation index, GinState *ginstate, MemoryContext tempCtx, GinScanKey key ) { 
+	uint32 i;
+	GinScanEntry	entry;
+	bool res;
+	MemoryContext	oldCtx;
+
+	if ( key->isFinished )
+		return TRUE;
+
+	do {
+		/* move forward from previously value and set new curItem,
+		   which is minimal from entries->curItems */
+		ItemPointerSetMax( &key->curItem );
+		for(i=0;i<key->nentries;i++) {
+			entry = key->scanEntry+i;
+	
+			if ( key->entryRes[i] ) {
+				if ( entry->isFinished == FALSE && entryGetItem(index, entry) == FALSE ) {
+					if (compareItemPointers( &entry->curItem, &key->curItem ) < 0)
+						key->curItem = entry->curItem;
+				} else
+					key->entryRes[i] = FALSE;
+			} else if ( entry->isFinished == FALSE ) { 
+				if (compareItemPointers( &entry->curItem, &key->curItem ) < 0)
+					key->curItem = entry->curItem;
+			} 
+		}
+
+		if ( ItemPointerIsMax( &key->curItem ) ) {
+			/* all entries are finished */
+			key->isFinished = TRUE;
+			return TRUE;
+		}
+	
+		if ( key->nentries == 1 ) {
+			/* we can do not call consistentFn !! */
+			key->entryRes[0] = TRUE;
+			return FALSE;
+		}
+
+		/* setting up array for consistentFn */
+		for(i=0;i<key->nentries;i++) {
+			entry = key->scanEntry+i;
+	
+			if ( entry->isFinished == FALSE && compareItemPointers( &entry->curItem, &key->curItem )==0 )
+				key->entryRes[i] = TRUE;
+			else
+				key->entryRes[i] = FALSE;
+		}
+
+		oldCtx = MemoryContextSwitchTo(tempCtx);
+		res = DatumGetBool( FunctionCall3(
+									&ginstate->consistentFn,
+									PointerGetDatum( key->entryRes ),
+									UInt16GetDatum( key->strategy ),
+									key->query
+						));
+		MemoryContextSwitchTo(oldCtx);
+		MemoryContextReset(tempCtx);
+	} while( !res );
+					
+	return FALSE;
+}
+
+/*
+ * Get heap item pointer from scan 
+ * returns true if found 
+ */
+static bool
+scanGetItem( IndexScanDesc scan, ItemPointerData *item ) {
+	uint32 i;
+	GinScanOpaque   so = (GinScanOpaque) scan->opaque;
+
+	ItemPointerSetMin( item ); 
+	for(i=0;i<so->nkeys;i++) {
+		GinScanKey key = so->keys+i;
+
+		if ( keyGetItem( scan->indexRelation, &so->ginstate, so->tempCtx, key )==FALSE ) {
+			if ( compareItemPointers( item, &key->curItem ) < 0 )
+				*item = key->curItem;
+		} else
+			return FALSE; /* finshed one of keys */
+	}
+				
+	for(i=1;i<=so->nkeys;i++) {
+		GinScanKey key = so->keys+i-1;
+
+		for(;;) {
+			int cmp = compareItemPointers( item, &key->curItem );
+
+			if ( cmp == 0 )
+				break;
+			else if ( cmp > 0 ) {
+				if ( keyGetItem( scan->indexRelation, &so->ginstate, so->tempCtx, key )==TRUE )
+					return FALSE; /* finshed one of keys */
+			} else { /* returns to begin */
+				*item = key->curItem;
+				i=0;
+				break;
+			}
+		}
+	}
+
+	return TRUE;	
+}
+
+#define GinIsNewKey(s)		( ((GinScanOpaque) scan->opaque)->keys == NULL )
+
+Datum 
+gingetmulti(PG_FUNCTION_ARGS) {
+    IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
+	ItemPointer tids = (ItemPointer) PG_GETARG_POINTER(1);
+	int32       max_tids = PG_GETARG_INT32(2);
+	int32      *returned_tids = (int32 *) PG_GETARG_POINTER(3);
+
+	if ( GinIsNewKey(scan) )
+		newScanKey( scan );
+
+	startScan( scan );
+
+	*returned_tids = 0;
+
+	do {
+		if ( scanGetItem( scan, tids + *returned_tids ) ) 
+			(*returned_tids)++;
+		else
+			break;
+	} while ( *returned_tids < max_tids );
+
+	stopScan( scan );
+
+	PG_RETURN_BOOL(*returned_tids == max_tids);
+}
+
+Datum
+gingettuple(PG_FUNCTION_ARGS) {
+	IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
+	ScanDirection dir = (ScanDirection) PG_GETARG_INT32(1);
+	bool res;
+
+	if ( dir != ForwardScanDirection )
+		elog(ERROR, "Gin doesn't support other scan directions than forward");
+	
+	if ( GinIsNewKey(scan) )
+		newScanKey( scan );
+
+	startScan( scan );
+	res = scanGetItem(scan, &scan->xs_ctup.t_self);
+	stopScan( scan );
+
+	PG_RETURN_BOOL(res);
+}
diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c
new file mode 100644
index 0000000000000000000000000000000000000000..fd7fc9f823bfe067c10d58c88ae4d30c8ef3d98c
--- /dev/null
+++ b/src/backend/access/gin/gininsert.c
@@ -0,0 +1,374 @@
+/*-------------------------------------------------------------------------
+ *
+ * gininsert.c
+ *    insert routines for the postgres inverted index access method.
+ *
+ *
+ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *          $PostgreSQL: pgsql/src/backend/access/gin/gininsert.c,v 1.1 2006/05/02 11:28:54 teodor Exp $
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/gin.h"
+#include "access/heapam.h"
+#include "catalog/index.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "utils/memutils.h"
+#include "access/tuptoaster.h"
+
+typedef struct {
+	GinState			ginstate;
+	double				indtuples;
+	MemoryContext		tmpCtx;
+	BuildAccumulator	accum;
+} GinBuildState;
+
+/*
+ * Creates posting tree with one page. Function
+ * suppose that items[] fits to page
+ */
+static BlockNumber
+createPostingTree( Relation index, ItemPointerData *items, uint32 nitems ) {
+	BlockNumber blkno;
+	Buffer buffer = GinNewBuffer(index);
+	Page page;
+
+	START_CRIT_SECTION();
+
+	GinInitBuffer( buffer, GIN_DATA|GIN_LEAF );
+	page = BufferGetPage(buffer);
+	blkno = BufferGetBlockNumber(buffer);
+
+	memcpy( GinDataPageGetData(page), items, sizeof(ItemPointerData) * nitems );
+	GinPageGetOpaque(page)->maxoff = nitems;
+
+	if (!index->rd_istemp) {
+		XLogRecPtr  recptr;
+		XLogRecData rdata[2];
+		ginxlogCreatePostingTree	data;
+
+		data.node = index->rd_node;
+		data.blkno = blkno;
+		data.nitem = nitems;
+
+		rdata[0].buffer = InvalidBuffer;
+		rdata[0].data = (char *) &data;
+		rdata[0].len = sizeof(ginxlogCreatePostingTree);
+		rdata[0].next = &rdata[1];
+
+		rdata[1].buffer = InvalidBuffer;
+		rdata[1].data = (char *) items;
+		rdata[1].len = sizeof(ItemPointerData) * nitems;
+		rdata[1].next = NULL;
+
+
+
+		recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_CREATE_PTREE, rdata);
+		PageSetLSN(page, recptr);
+		PageSetTLI(page, ThisTimeLineID);
+
+	} 
+
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	END_CRIT_SECTION();
+
+	return blkno;
+}
+
+
+/*
+ * Adds array of item pointers to tuple's posting list or
+ * creates posting tree and tuple pointed to tree in a case
+ * of not enough space.  Max size of tuple is defined in
+ * GinFormTuple().
+ */
+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));
+
+	if ( res ) {
+		/* good, small enough */
+		MergeItemPointers( GinGetPosting(res),
+			GinGetPosting(old), GinGetNPosting(old),
+			items,	nitem
+		);
+		
+		GinSetNPosting(res, nitem + GinGetNPosting(old));
+	} else {
+		BlockNumber postingRoot;
+		GinPostingTreeScan *gdi;
+
+		/* posting list becomes big, so we need to make posting's tree */
+		res = GinFormTuple(ginstate, key, NULL, 0);
+		postingRoot = createPostingTree(index, GinGetPosting(old), GinGetNPosting(old));
+		GinSetPostingTree(res, postingRoot);
+
+		gdi = prepareScanPostingTree(index, postingRoot, FALSE); 
+		gdi->btree.isBuild = isBuild;
+
+		insertItemPointer(gdi, items, nitem);
+
+		pfree(gdi);
+	}
+
+	return res;
+}
+
+/*
+ * Inserts only one entry to the index, but it can adds more that 1 
+ * ItemPointer. 
+ */
+static void
+ginEntryInsert( Relation index, GinState *ginstate, Datum value, ItemPointerData *items, uint32 nitem, bool isBuild) {
+	GinBtreeData	btree;
+	GinBtreeStack *stack;
+	IndexTuple itup;
+	Page page;
+
+	prepareEntryScan( &btree, index, value, ginstate );
+
+	stack = ginFindLeafPage(&btree, NULL);
+	page = BufferGetPage( stack->buffer );
+
+	if ( btree.findItem( &btree, stack ) ) {
+		/* found entry */
+		itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, stack->off));
+
+		if ( GinIsPostingTree(itup) ) {
+			/* lock root of posting tree */
+			GinPostingTreeScan *gdi;
+			BlockNumber	rootPostingTree = GinGetPostingTree(itup); 
+
+			/* release all stack */
+			LockBuffer(stack->buffer, GIN_UNLOCK);
+			freeGinBtreeStack( stack );
+
+			/* insert into posting tree */
+			gdi = prepareScanPostingTree( index, rootPostingTree, FALSE );
+			gdi->btree.isBuild = isBuild;
+			insertItemPointer(gdi, items, nitem);
+
+			return;
+		}
+
+		itup = addItemPointersToTuple(index, ginstate, stack, itup, items, nitem, isBuild);
+
+		btree.isDelete = TRUE;
+	} else {
+		/* We suppose, that tuple can store at list one itempointer */
+		itup = GinFormTuple( ginstate, value, items, 1);
+		if ( itup==NULL || IndexTupleSize(itup) >= GinMaxItemSize  )
+			elog(ERROR, "huge tuple");
+
+		if ( nitem>1 ) {
+			IndexTuple	previtup = itup;
+
+			itup =  addItemPointersToTuple(index, ginstate, stack, previtup, items+1, nitem-1, isBuild);
+			pfree(previtup);
+		}
+	}
+
+	btree.entry = itup;
+	ginInsertValue(&btree, stack);
+	pfree( itup );
+}
+
+/*
+ * Saves indexed value in memory accumulator during index creation
+ * Function isnt use during normal insert
+ */
+static uint32
+ginHeapTupleBulkInsert(BuildAccumulator *accum, Datum value, ItemPointer heapptr) {
+	Datum	*entries;
+	uint32 nentries;
+
+	entries = extractEntriesSU( accum->ginstate, value, &nentries);
+
+	if ( nentries==0 )
+		/* nothing to insert */
+		return 0;
+
+	ginInsertRecordBA( accum, heapptr, entries, nentries);
+
+	pfree( entries );
+
+	return nentries;
+}
+
+static void 
+ginBuildCallback(Relation index, HeapTuple htup, Datum *values,
+					bool *isnull, bool tupleIsAlive, void *state) {
+
+	GinBuildState	*buildstate = (GinBuildState*)state;
+	MemoryContext oldCtx;
+
+	if ( *isnull )
+		return;
+
+	oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
+
+	buildstate->indtuples += ginHeapTupleBulkInsert(&buildstate->accum, *values, &htup->t_self);
+
+	/* we use only half maintenance_work_mem, because there is some leaks 
+		during insertion and extract values */ 
+	if ( buildstate->accum.allocatedMemory >= maintenance_work_mem*1024L/2L ) { 
+		ItemPointerData	*list;
+		Datum entry;
+		uint32	nlist;
+
+		while( (list=ginGetEntry(&buildstate->accum, &entry, &nlist)) != NULL )
+			ginEntryInsert(index, &buildstate->ginstate, entry, list, nlist, TRUE);
+
+		MemoryContextReset(buildstate->tmpCtx);
+		ginInitBA(&buildstate->accum);
+	}
+
+	MemoryContextSwitchTo(oldCtx);
+}
+
+Datum
+ginbuild(PG_FUNCTION_ARGS) {
+	Relation    heap = (Relation) PG_GETARG_POINTER(0);
+	Relation    index = (Relation) PG_GETARG_POINTER(1);
+	IndexInfo  *indexInfo = (IndexInfo *) PG_GETARG_POINTER(2);
+	double      reltuples;
+	GinBuildState	buildstate;
+	Buffer		buffer;
+	ItemPointerData	*list;
+	Datum entry;
+	uint32	nlist;
+	MemoryContext oldCtx;
+
+	if (RelationGetNumberOfBlocks(index) != 0)
+		elog(ERROR, "index \"%s\" already contains data",
+			RelationGetRelationName(index));
+
+	initGinState(&buildstate.ginstate, index);
+
+	/* initialize the root page */
+	buffer = GinNewBuffer(index);
+	START_CRIT_SECTION();
+	GinInitBuffer(buffer, GIN_LEAF);
+	if (!index->rd_istemp) {
+		XLogRecPtr  recptr;
+		XLogRecData rdata;
+		Page        page;
+
+		rdata.buffer = InvalidBuffer;
+		rdata.data = (char *) &(index->rd_node);
+		rdata.len = sizeof(RelFileNode);
+		rdata.next = NULL;
+
+		page = BufferGetPage(buffer);
+
+
+		recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_CREATE_INDEX, &rdata);
+		PageSetLSN(page, recptr);
+		PageSetTLI(page, ThisTimeLineID);
+
+	} 
+
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+	END_CRIT_SECTION();
+
+	/* build the index */
+	buildstate.indtuples = 0;
+
+	/*
+	 * create a temporary memory context that is reset once for each tuple
+	 * inserted into the index
+	 */
+	buildstate.tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
+												"Gin build temporary context",
+												ALLOCSET_DEFAULT_MINSIZE,
+												ALLOCSET_DEFAULT_INITSIZE,
+												ALLOCSET_DEFAULT_MAXSIZE);
+
+	buildstate.accum.ginstate = &buildstate.ginstate;
+	ginInitBA( &buildstate.accum );
+
+	/* do the heap scan */
+	reltuples = IndexBuildHeapScan(heap, index, indexInfo,
+									ginBuildCallback, (void *) &buildstate);
+
+	oldCtx = MemoryContextSwitchTo(buildstate.tmpCtx);
+	while( (list=ginGetEntry(&buildstate.accum, &entry, &nlist)) != NULL )
+		ginEntryInsert(index, &buildstate.ginstate, entry, list, nlist, TRUE);
+	MemoryContextSwitchTo(oldCtx);
+
+	MemoryContextDelete(buildstate.tmpCtx);
+
+	/* since we just counted the # of tuples, may as well update stats */
+	IndexCloseAndUpdateStats(heap, reltuples, index, buildstate.indtuples);
+
+	PG_RETURN_VOID();
+}
+
+/*
+ * Inserts value during normal insertion
+ */
+static uint32
+ginHeapTupleInsert( Relation index, GinState *ginstate, Datum value, ItemPointer item) {
+	Datum	*entries;
+	uint32 i,nentries;
+
+	entries = extractEntriesSU( ginstate, value, &nentries);
+
+	if ( nentries==0 )
+		/* nothing to insert */
+		return 0;
+
+	for(i=0;i<nentries;i++) 
+		ginEntryInsert(index, ginstate, entries[i], item, 1, FALSE);
+
+	return nentries;
+}
+
+Datum
+gininsert(PG_FUNCTION_ARGS) {
+	Relation    index = (Relation) PG_GETARG_POINTER(0);
+	Datum      *values = (Datum *) PG_GETARG_POINTER(1);
+	bool       *isnull = (bool *) PG_GETARG_POINTER(2);
+	ItemPointer ht_ctid = (ItemPointer) PG_GETARG_POINTER(3);
+#ifdef NOT_USED
+	Relation    heapRel = (Relation) PG_GETARG_POINTER(4);
+	bool        checkUnique = PG_GETARG_BOOL(5);
+#endif
+	GinState 	ginstate;
+	MemoryContext oldCtx;
+	MemoryContext insertCtx;
+	uint32 res;
+
+	if ( *isnull )
+		PG_RETURN_BOOL(false);
+
+	insertCtx = AllocSetContextCreate(CurrentMemoryContext,
+												"Gin insert temporary context",
+												ALLOCSET_DEFAULT_MINSIZE,
+												ALLOCSET_DEFAULT_INITSIZE,
+												ALLOCSET_DEFAULT_MAXSIZE);
+
+	oldCtx = MemoryContextSwitchTo(insertCtx);
+
+	initGinState(&ginstate, index);
+
+	res = ginHeapTupleInsert(index, &ginstate, *values, ht_ctid); 
+
+	MemoryContextSwitchTo(oldCtx);
+	MemoryContextDelete(insertCtx);
+
+	PG_RETURN_BOOL(res>0);
+}
+
diff --git a/src/backend/access/gin/ginscan.c b/src/backend/access/gin/ginscan.c
new file mode 100644
index 0000000000000000000000000000000000000000..b641b82e6621568ffa08b0979a7ca7ae97908c80
--- /dev/null
+++ b/src/backend/access/gin/ginscan.c
@@ -0,0 +1,256 @@
+/*-------------------------------------------------------------------------
+ *
+ * ginscan.c
+ *    routines to manage scans inverted index relations
+ *
+ *
+ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *          $PostgreSQL: pgsql/src/backend/access/gin/ginscan.c,v 1.1 2006/05/02 11:28:54 teodor Exp $
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/gin.h"
+#include "access/heapam.h"
+#include "catalog/index.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "utils/memutils.h"
+
+
+Datum 
+ginbeginscan(PG_FUNCTION_ARGS) {
+	Relation    rel = (Relation) PG_GETARG_POINTER(0);
+	int         keysz = PG_GETARG_INT32(1);
+	ScanKey     scankey = (ScanKey) PG_GETARG_POINTER(2);
+	IndexScanDesc scan;
+
+	scan = RelationGetIndexScan(rel, keysz, scankey);
+
+	PG_RETURN_POINTER(scan);
+}
+
+static void
+fillScanKey( GinState *ginstate, GinScanKey key, Datum query, 
+				Datum *entryValues, uint32 nEntryValues, StrategyNumber strategy ) {
+	uint32 i,j;
+
+	key->nentries = nEntryValues;
+	key->entryRes = (bool*)palloc0( sizeof(bool) * nEntryValues ); 
+	key->scanEntry = (GinScanEntry) palloc( sizeof(GinScanEntryData) * nEntryValues );
+	key->strategy = strategy;
+	key->query = query;
+	key->firstCall= TRUE;
+	ItemPointerSet( &(key->curItem), InvalidBlockNumber, InvalidOffsetNumber );
+
+	for(i=0; i<nEntryValues; i++) {
+		key->scanEntry[i].pval = key->entryRes + i;
+		key->scanEntry[i].entry = entryValues[i];
+		ItemPointerSet( &(key->scanEntry[i].curItem), InvalidBlockNumber, InvalidOffsetNumber );
+		key->scanEntry[i].offset = InvalidOffsetNumber;
+		key->scanEntry[i].buffer = InvalidBuffer;
+		key->scanEntry[i].list = NULL;
+		key->scanEntry[i].nlist = 0;
+
+		/* 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 ) {
+				key->scanEntry[i].master = key->scanEntry + j;
+				break;
+			}
+	}
+}
+
+static void
+resetScanKeys(GinScanKey keys, uint32 nkeys) {
+	uint32	i, j;
+
+	if ( keys == NULL )
+		return;
+
+	for(i=0;i<nkeys;i++) {
+		GinScanKey key = keys + i;
+
+		key->firstCall = TRUE;
+		ItemPointerSet( &(key->curItem), InvalidBlockNumber, InvalidOffsetNumber );
+
+		for(j=0;j<key->nentries;j++) {
+			if ( key->scanEntry[j].buffer != InvalidBuffer )
+				ReleaseBuffer( key->scanEntry[i].buffer );
+
+			ItemPointerSet( &(key->scanEntry[j].curItem), InvalidBlockNumber, InvalidOffsetNumber );
+			key->scanEntry[j].offset = InvalidOffsetNumber;
+			key->scanEntry[j].buffer = InvalidBuffer;
+			key->scanEntry[j].list = NULL;
+			key->scanEntry[j].nlist = 0;
+		}
+	}
+}
+
+static void
+freeScanKeys(GinScanKey keys, uint32 nkeys, bool removeRes) {
+	uint32	i, j;
+
+	if ( keys == NULL )
+		return;
+
+	for(i=0;i<nkeys;i++) {
+		GinScanKey key = keys + i;
+
+		for(j=0;j<key->nentries;j++) {
+			if ( key->scanEntry[j].buffer != InvalidBuffer )
+				ReleaseBuffer( key->scanEntry[j].buffer );
+			if ( removeRes && key->scanEntry[j].list ) 
+				pfree(key->scanEntry[j].list);
+		}
+
+		if ( removeRes )
+			pfree(key->entryRes);
+		pfree(key->scanEntry);
+	}
+	
+	pfree(keys);
+}
+
+void
+newScanKey( IndexScanDesc scan ) {
+	ScanKey     scankey = scan->keyData;
+	GinScanOpaque so = (GinScanOpaque) scan->opaque;
+	int i;
+	uint32 nkeys = 0;
+
+	so->keys = (GinScanKey) palloc( scan->numberOfKeys * sizeof(GinScanKeyData) );
+
+	for(i=0; i<scan->numberOfKeys; i++) {
+		Datum*	entryValues;
+		uint32	nEntryValues;
+
+		if ( scankey[i].sk_flags & SK_ISNULL )
+			elog(ERROR, "Gin doesn't support NULL as scan key");
+		Assert( scankey[i].sk_attno == 1 );
+
+		entryValues = (Datum*)DatumGetPointer(
+							FunctionCall3(
+								&so->ginstate.extractQueryFn,
+								scankey[i].sk_argument,
+								PointerGetDatum( &nEntryValues ),
+								UInt16GetDatum(scankey[i].sk_strategy)
+							)
+						);
+		if ( entryValues==NULL || nEntryValues == 0 )
+			/* full scan... */
+			continue;
+
+		fillScanKey( &so->ginstate, &(so->keys[nkeys]), scankey[i].sk_argument,
+				entryValues, nEntryValues, scankey[i].sk_strategy );
+		nkeys++;
+	}
+
+	so->nkeys = nkeys;
+
+	if ( so->nkeys == 0 )
+		elog(ERROR, "Gin doesn't support full scan due to it's awful inefficiency");
+}
+
+Datum
+ginrescan(PG_FUNCTION_ARGS) {
+	IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
+	ScanKey     scankey = (ScanKey) PG_GETARG_POINTER(1);
+	GinScanOpaque	so;
+
+	so = (GinScanOpaque) scan->opaque;
+
+	if ( so == NULL ) {
+		/* if called from ginbeginscan */
+		so = (GinScanOpaque)palloc( sizeof(GinScanOpaqueData) );
+		so->tempCtx = AllocSetContextCreate(CurrentMemoryContext,
+								"Gin scan temporary context",
+								ALLOCSET_DEFAULT_MINSIZE,
+								ALLOCSET_DEFAULT_INITSIZE,
+								ALLOCSET_DEFAULT_MAXSIZE);
+		initGinState(&so->ginstate, scan->indexRelation);
+		scan->opaque = so;
+	} else {
+		freeScanKeys(so->keys, so->nkeys, TRUE);
+		freeScanKeys(so->markPos, so->nkeys, FALSE);
+	}
+
+	so->markPos=so->keys=NULL;
+
+	if ( scankey && scan->numberOfKeys > 0 ) {
+		memmove(scan->keyData, scankey,
+			scan->numberOfKeys * sizeof(ScanKeyData));
+	}
+
+	PG_RETURN_VOID();
+}
+
+
+Datum
+ginendscan(PG_FUNCTION_ARGS) {
+	IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
+    GinScanOpaque   so = (GinScanOpaque) scan->opaque;
+
+	if ( so != NULL ) {
+		freeScanKeys(so->keys, so->nkeys, TRUE); 
+		freeScanKeys(so->markPos, so->nkeys, FALSE); 
+
+		MemoryContextDelete(so->tempCtx);
+
+		pfree(so);
+	}
+
+	PG_RETURN_VOID();
+}
+
+static GinScanKey
+copyScanKeys( GinScanKey keys, uint32 nkeys ) {
+	GinScanKey	newkeys;
+	uint32 i, j;
+
+	newkeys = (GinScanKey)palloc( sizeof(GinScanKeyData) * nkeys );
+	memcpy( newkeys, keys, sizeof(GinScanKeyData) * nkeys );
+
+	for(i=0;i<nkeys;i++) {
+		newkeys[i].scanEntry = (GinScanEntry)palloc(sizeof(GinScanEntryData) * keys[i].nentries );
+		memcpy( newkeys[i].scanEntry, keys[i].scanEntry, sizeof(GinScanEntryData) * keys[i].nentries );
+
+		for(j=0;j<keys[i].nentries; j++) { 
+			if ( keys[i].scanEntry[j].buffer != InvalidBuffer )
+				IncrBufferRefCount( keys[i].scanEntry[j].buffer );
+			if ( keys[i].scanEntry[j].master ) {
+				int masterN = keys[i].scanEntry[j].master - keys[i].scanEntry;
+				newkeys[i].scanEntry[j].master = newkeys[i].scanEntry + masterN;
+			}
+		}
+	}
+
+	return newkeys;
+}
+
+Datum 
+ginmarkpos(PG_FUNCTION_ARGS) {
+	IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
+	GinScanOpaque   so = (GinScanOpaque) scan->opaque;
+
+	freeScanKeys(so->markPos, so->nkeys, FALSE);
+	so->markPos = copyScanKeys( so->keys, so->nkeys );
+
+	PG_RETURN_VOID();
+}
+
+Datum 
+ginrestrpos(PG_FUNCTION_ARGS) {
+	IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
+	GinScanOpaque   so = (GinScanOpaque) scan->opaque;
+
+	freeScanKeys(so->keys, so->nkeys, FALSE);
+	so->keys = copyScanKeys( so->markPos, so->nkeys );
+
+	PG_RETURN_VOID();
+}
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
new file mode 100644
index 0000000000000000000000000000000000000000..b9cb80a6cf156f2d8dc81c32ba2602f47b8e7962
--- /dev/null
+++ b/src/backend/access/gin/ginutil.c
@@ -0,0 +1,203 @@
+/*-------------------------------------------------------------------------
+ *
+ * ginutil.c
+ *    utilities routines for the postgres inverted index access method.
+ *
+ *
+ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *          $PostgreSQL: pgsql/src/backend/access/gin/ginutil.c,v 1.1 2006/05/02 11:28:54 teodor Exp $
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/gin.h"
+#include "access/heapam.h"
+#include "catalog/index.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+
+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);
+}
+
+/*
+ * Allocate a new page (either by recycling, or by extending the index file)
+ * The returned buffer is already pinned and exclusive-locked
+ * Caller is responsible for initializing the page by calling GinInitBuffer
+ */
+
+Buffer
+GinNewBuffer(Relation index) {
+	Buffer	buffer;
+	bool	needLock;
+
+	/* First, try to get a page from FSM */
+	for(;;) {
+		BlockNumber blkno = GetFreeIndexPage(&index->rd_node);
+		if (blkno == InvalidBlockNumber)
+			break;
+
+		buffer = ReadBuffer(index, blkno);
+
+		/*
+		 * We have to guard against the possibility that someone else already
+		 * recycled this page; the buffer may be locked if so.
+		 */
+		 if (ConditionalLockBuffer(buffer)) {
+			Page        page = BufferGetPage(buffer);
+
+			if (PageIsNew(page))
+				return buffer;  /* OK to use, if never initialized */
+
+			if (GinPageIsDeleted(page))
+				return buffer;  /* OK to use */
+
+			LockBuffer(buffer, GIN_UNLOCK);
+		}
+
+		/* Can't use it, so release buffer and try again */
+		ReleaseBuffer(buffer);
+	}
+
+	/* Must extend the file */
+	needLock = !RELATION_IS_LOCAL(index);
+	if (needLock)
+		LockRelationForExtension(index, ExclusiveLock);
+
+	buffer = ReadBuffer(index, P_NEW);
+	LockBuffer(buffer, GIN_EXCLUSIVE);
+
+	if (needLock)
+		UnlockRelationForExtension(index, ExclusiveLock);
+
+	return buffer;
+}
+
+void
+GinInitPage(Page page, uint32 f, Size pageSize) {
+	GinPageOpaque opaque;
+
+	PageInit(page, pageSize, sizeof(GinPageOpaqueData));
+
+	opaque = GinPageGetOpaque(page);
+	memset( opaque, 0, sizeof(GinPageOpaqueData) );
+	opaque->flags = f;   
+	opaque->rightlink = InvalidBlockNumber;
+}
+
+void
+GinInitBuffer(Buffer b, uint32 f) {
+	GinInitPage( BufferGetPage(b), f, BufferGetPageSize(b) );
+}
+
+int
+compareEntries(GinState *ginstate, Datum a, Datum b) {
+	return DatumGetInt32(
+		FunctionCall2(
+			&ginstate->compareFn,
+			a, b
+		)
+	);
+}
+
+static FmgrInfo* cmpDatumPtr=NULL;
+static bool needUnique = FALSE;
+
+static int
+cmpEntries(const void * a, const void * b) {
+	int res = DatumGetInt32(
+		FunctionCall2(
+			cmpDatumPtr,
+			*(Datum*)a,
+			*(Datum*)b
+		)
+	);
+
+	if ( res == 0 ) 
+		needUnique = TRUE;
+
+	return res;
+}
+
+Datum*
+extractEntriesS(GinState *ginstate, Datum value, uint32 *nentries) {
+	Datum *entries;
+
+   	entries = (Datum*)DatumGetPointer(
+							FunctionCall2(
+								&ginstate->extractValueFn,
+								value,
+								PointerGetDatum( nentries )
+							)
+			);
+
+	if ( entries == NULL )
+		*nentries = 0;
+
+	if ( *nentries > 1 ) {
+		cmpDatumPtr = &ginstate->compareFn;
+		needUnique = FALSE;
+		qsort(entries, *nentries, sizeof(Datum), cmpEntries); 
+	}
+
+	return entries;
+}
+
+
+Datum*
+extractEntriesSU(GinState *ginstate, Datum value, uint32 *nentries) {
+	Datum *entries = extractEntriesS(ginstate, value, nentries);
+
+	if ( *nentries>1 && needUnique ) {
+		Datum *ptr, *res;
+
+		ptr = res = entries;
+
+		while( ptr - entries < *nentries ) {
+			if ( compareEntries(ginstate, *ptr, *res ) != 0 ) 
+				*(++res) = *ptr++;
+			else
+				ptr++;
+		}
+
+		*nentries = res + 1 - entries;
+	}
+
+	return entries;
+}
+
+/*
+ * It's analog of PageGetTempPage(), but copies whole page
+ */
+Page
+GinPageGetCopyPage( Page page ) {
+	Size    pageSize = PageGetPageSize( page );
+	Page tmppage;
+
+	tmppage=(Page)palloc( pageSize );
+	memcpy( tmppage, page, pageSize );
+	
+	return tmppage;
+}
diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
new file mode 100644
index 0000000000000000000000000000000000000000..ab1861fdaa94b575b1ec2c9f969d78332a35643c
--- /dev/null
+++ b/src/backend/access/gin/ginvacuum.c
@@ -0,0 +1,647 @@
+/*-------------------------------------------------------------------------
+ *
+ * ginvacuum.c
+ *    delete & vacuum routines for the postgres GIN
+ *
+ *
+ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *          $PostgreSQL: pgsql/src/backend/access/gin/ginvacuum.c,v 1.1 2006/05/02 11:28:54 teodor Exp $
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/gin.h"
+#include "access/heapam.h"
+#include "catalog/index.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "utils/memutils.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "commands/vacuum.h"
+
+typedef struct {
+	Relation				index;
+	IndexBulkDeleteResult	*result;
+	IndexBulkDeleteCallback	callback;
+	void					*callback_state;
+	GinState				ginstate;
+} GinVacuumState;
+
+
+/*
+ * Cleans array of ItemPointer (removes dead pointers)
+ * Results are always stored in *cleaned, which will be allocated
+ * if its needed. In case of *cleaned!=NULL caller is resposible to 
+ * enough space. *cleaned and items may point to the same
+ * memory addres.
+ */
+
+static uint32
+ginVacuumPostingList( GinVacuumState *gvs, ItemPointerData *items, uint32 nitem, ItemPointerData **cleaned ) {
+	uint32 i,j=0;
+
+	/*
+	 * just scan over ItemPointer array
+	 */
+
+	for(i=0;i<nitem;i++) {
+		if ( gvs->callback(items+i, gvs->callback_state) ) { 
+			gvs->result->tuples_removed += 1;
+			if ( !*cleaned )  {
+				*cleaned = (ItemPointerData*)palloc(sizeof(ItemPointerData)*nitem);
+				if ( i!=0 ) 
+					memcpy( *cleaned, items, sizeof(ItemPointerData)*i);
+			}
+		} else {
+			gvs->result->num_index_tuples += 1;
+			if (i!=j)
+				(*cleaned)[j] = items[i];
+			j++;
+		}
+	}
+
+	return j;
+}
+
+/*
+ * fills WAL record for vacuum leaf page
+ */
+static void
+xlogVacuumPage(Relation index, Buffer buffer) {
+	Page	page = BufferGetPage( buffer );
+	XLogRecPtr  recptr;
+	XLogRecData rdata[3];
+	ginxlogVacuumPage	data;
+	char *backup;
+	char itups[BLCKSZ];
+	uint32 len=0;
+
+	Assert( GinPageIsLeaf( page ) );
+
+	if (index->rd_istemp)
+		return; 
+
+	data.node = index->rd_node;
+	data.blkno = BufferGetBlockNumber(buffer);
+
+	if ( GinPageIsData( page ) ) {
+		backup = GinDataPageGetData( page );
+		data.nitem = GinPageGetOpaque( page )->maxoff;
+		if ( data.nitem )
+			len = MAXALIGN( sizeof(ItemPointerData)*data.nitem );
+	} else {
+		char *ptr;
+		OffsetNumber i;
+
+		ptr = backup = itups;
+		for(i=FirstOffsetNumber;i<=PageGetMaxOffsetNumber(page);i++) {
+			IndexTuple itup =  (IndexTuple) PageGetItem(page, PageGetItemId(page, i));
+			memcpy( ptr, itup, IndexTupleSize( itup ) );
+			ptr += MAXALIGN( IndexTupleSize( itup ) );
+		}
+
+		data.nitem = PageGetMaxOffsetNumber(page);
+		len = ptr-backup;
+	}
+
+	rdata[0].buffer = buffer;
+	rdata[0].buffer_std = ( GinPageIsData( page ) ) ? FALSE : TRUE;
+	rdata[0].len = 0;
+	rdata[0].data = NULL;
+	rdata[0].next = rdata + 1;
+
+	rdata[1].buffer = InvalidBuffer;
+	rdata[1].len = sizeof(ginxlogVacuumPage);
+	rdata[1].data = (char*)&data;
+
+	if ( len == 0 ) {
+		rdata[1].next = NULL;
+	} else {
+		rdata[1].next = rdata + 2;
+
+		rdata[2].buffer = InvalidBuffer;
+		rdata[2].len = len;
+		rdata[2].data = backup;
+		rdata[2].next = NULL;
+	}
+
+	recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_VACUUM_PAGE, rdata);
+	PageSetLSN(page, recptr);
+	PageSetTLI(page, ThisTimeLineID);
+}
+
+static bool
+ginVacuumPostingTreeLeaves( GinVacuumState *gvs, BlockNumber blkno, bool isRoot, Buffer *rootBuffer ) { 
+	Buffer 	buffer = ReadBuffer( gvs->index, blkno );
+	Page	page   = BufferGetPage( buffer );
+	bool 	hasVoidPage = FALSE;	
+
+	/* 
+	 * We should be sure that we don't concurrent with inserts, insert process
+	 * never release root page until end (but it can unlock it and lock again).
+	 * If we lock root with with LockBufferForCleanup, new scan process can't begin,
+	 * but previous may run. 
+	 * ginmarkpos/start* keeps buffer pinned, so we will wait for it.
+	 * We lock only one posting tree in whole index, so, it's concurrent enough..
+	 * Side effect: after this is full complete, tree is unused by any other process 
+	 */
+
+	LockBufferForCleanup( buffer );
+
+	Assert( GinPageIsData(page) );
+
+	if ( GinPageIsLeaf(page) ) {
+		OffsetNumber newMaxOff, oldMaxOff = GinPageGetOpaque(page)->maxoff;
+		ItemPointerData *cleaned = NULL;
+
+		newMaxOff = ginVacuumPostingList( gvs, 
+				(ItemPointer)GinDataPageGetData(page), oldMaxOff, &cleaned );
+
+		/* saves changes about deleted tuple ... */
+		if ( oldMaxOff != newMaxOff ) {
+
+			START_CRIT_SECTION();
+
+			if ( newMaxOff > 0 ) 
+				memcpy( GinDataPageGetData(page), cleaned, sizeof(ItemPointerData) * newMaxOff );
+			pfree( cleaned );
+			GinPageGetOpaque(page)->maxoff = newMaxOff;
+
+			xlogVacuumPage(gvs->index,  buffer);
+
+			MarkBufferDirty( buffer );
+			END_CRIT_SECTION();
+			
+			/* if root is a leaf page, we don't desire futher processing */ 
+			if ( !isRoot && GinPageGetOpaque(page)->maxoff < FirstOffsetNumber )
+				hasVoidPage = TRUE;
+		}
+	} else {
+		OffsetNumber i;
+		bool isChildHasVoid = FALSE;
+
+		for( i=FirstOffsetNumber ; i <= GinPageGetOpaque(page)->maxoff ; i++ ) {
+			PostingItem *pitem = (PostingItem*)GinDataPageGetItem(page, i);
+			if ( ginVacuumPostingTreeLeaves( gvs, PostingItemGetBlockNumber(pitem), FALSE, NULL ) )
+				isChildHasVoid = TRUE;
+		}
+
+		if ( isChildHasVoid )
+			hasVoidPage = TRUE;
+	}
+
+	/* if we have root and theres void pages in tree, then we don't release lock
+	   to go further processing and guarantee that tree is unused */
+	if ( !(isRoot && hasVoidPage) ) {
+		UnlockReleaseBuffer( buffer );
+	} else {
+		Assert( rootBuffer );
+		*rootBuffer = buffer;
+	}
+
+	return hasVoidPage;
+}
+
+static void
+ginDeletePage(GinVacuumState *gvs, BlockNumber deleteBlkno, BlockNumber leftBlkno, 
+		BlockNumber parentBlkno, OffsetNumber myoff, bool isParentRoot ) {
+	Buffer	dBuffer = ReadBuffer( gvs->index, deleteBlkno );
+	Buffer	lBuffer	= (leftBlkno==InvalidBlockNumber) ? InvalidBuffer : ReadBuffer( gvs->index, leftBlkno );
+	Buffer	pBuffer	= ReadBuffer( gvs->index, parentBlkno );
+	Page page, parentPage;
+
+	LockBuffer( dBuffer, GIN_EXCLUSIVE );
+	if ( !isParentRoot ) /* parent is already locked by LockBufferForCleanup() */
+		LockBuffer( pBuffer, GIN_EXCLUSIVE );
+
+	START_CRIT_SECTION();
+
+	if ( leftBlkno!= InvalidBlockNumber ) {
+		BlockNumber rightlink;
+
+		LockBuffer( lBuffer, GIN_EXCLUSIVE );
+
+		page = BufferGetPage( dBuffer );
+		rightlink = GinPageGetOpaque(page)->rightlink;
+
+		page = BufferGetPage( lBuffer );
+		GinPageGetOpaque(page)->rightlink = rightlink;
+	}
+
+	parentPage = BufferGetPage( pBuffer );
+	PageDeletePostingItem(parentPage, myoff);
+
+	page = BufferGetPage( dBuffer );
+	GinPageGetOpaque(page)->flags = GIN_DELETED;
+
+	if (!gvs->index->rd_istemp) {
+		XLogRecPtr  recptr;
+		XLogRecData rdata[4];
+		ginxlogDeletePage	 data;
+		int n;
+
+		data.node = gvs->index->rd_node;
+		data.blkno = deleteBlkno;
+		data.parentBlkno = parentBlkno;
+		data.parentOffset = myoff;
+		data.leftBlkno = leftBlkno; 
+		data.rightLink = GinPageGetOpaque(page)->rightlink; 
+
+		rdata[0].buffer = dBuffer;
+		rdata[0].buffer_std = FALSE;
+		rdata[0].data = NULL;
+		rdata[0].len = 0;
+		rdata[0].next = rdata + 1;
+
+		rdata[1].buffer = pBuffer;
+		rdata[1].buffer_std = FALSE;
+		rdata[1].data = NULL;
+		rdata[1].len = 0;
+		rdata[1].next = rdata + 2;
+
+		if ( leftBlkno!= InvalidBlockNumber ) { 	
+			rdata[2].buffer = lBuffer;
+			rdata[2].buffer_std = FALSE;
+			rdata[2].data = NULL;
+			rdata[2].len = 0;
+			rdata[2].next = rdata + 3;
+			n = 3;
+		} else
+			n = 2;
+
+		rdata[n].buffer = InvalidBuffer;
+		rdata[n].buffer_std = FALSE;
+		rdata[n].len = sizeof(ginxlogDeletePage);
+		rdata[n].data = (char*)&data;
+		rdata[n].next = NULL;
+
+		recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_DELETE_PAGE, rdata);
+		PageSetLSN(page, recptr);
+		PageSetTLI(page, ThisTimeLineID);
+		PageSetLSN(parentPage, recptr);
+		PageSetTLI(parentPage, ThisTimeLineID);
+		if ( leftBlkno!= InvalidBlockNumber ) {
+			page = BufferGetPage( lBuffer );
+			PageSetLSN(page, recptr);
+			PageSetTLI(page, ThisTimeLineID);
+		}
+	}
+
+	MarkBufferDirty( pBuffer );
+	if ( !isParentRoot )
+		LockBuffer( pBuffer, GIN_UNLOCK );
+	ReleaseBuffer( pBuffer );
+
+	if ( leftBlkno!= InvalidBlockNumber ) {
+		MarkBufferDirty( lBuffer );
+		UnlockReleaseBuffer( lBuffer );
+	}
+
+	MarkBufferDirty( dBuffer );
+	UnlockReleaseBuffer( dBuffer );
+
+	END_CRIT_SECTION();
+
+	gvs->result->pages_deleted++;
+}
+
+typedef struct DataPageDeleteStack {
+	struct DataPageDeleteStack	*child;	
+	struct DataPageDeleteStack	*parent;
+
+	BlockNumber					blkno;
+	bool						isRoot;
+} DataPageDeleteStack;
+
+/*
+ * scans posting tree and deletes empty pages
+ */
+static bool
+ginScanToDelete( GinVacuumState *gvs, BlockNumber blkno, bool isRoot, DataPageDeleteStack *parent, OffsetNumber myoff ) {
+	DataPageDeleteStack	*me;
+	Buffer	buffer;
+	Page	page;
+	bool	meDelete = FALSE;
+
+	if ( isRoot ) {
+		me = parent;
+	} else {
+		if ( ! parent->child ) {
+			me  = (DataPageDeleteStack*)palloc0(sizeof(DataPageDeleteStack));
+			me->parent=parent;
+			parent->child = me;
+			me->blkno = InvalidBlockNumber;
+		} else
+			me = parent->child;
+	}
+
+	buffer = ReadBuffer( gvs->index, blkno ); 
+	page = BufferGetPage( buffer );
+
+	Assert( GinPageIsData(page) );
+
+	if ( !GinPageIsLeaf(page) ) {
+		OffsetNumber i;
+
+		for(i=FirstOffsetNumber;i<=GinPageGetOpaque(page)->maxoff;i++) {
+			PostingItem *pitem = (PostingItem*)GinDataPageGetItem(page, i);
+
+			if ( ginScanToDelete( gvs, PostingItemGetBlockNumber(pitem), FALSE, me, i ) )
+				i--;
+		}
+	}
+
+	if ( GinPageGetOpaque(page)->maxoff < FirstOffsetNumber ) {
+		if ( !( me->blkno == InvalidBlockNumber && GinPageRightMost(page) ) ) {
+			/* we never delete right most branch */
+			Assert( !isRoot );
+			if ( GinPageGetOpaque(page)->maxoff < FirstOffsetNumber )  {
+				ginDeletePage( gvs, blkno, me->blkno, me->parent->blkno, myoff, me->parent->isRoot );
+				meDelete = TRUE;
+			}
+		}
+	}
+
+	ReleaseBuffer( buffer );
+
+	if ( !meDelete )
+		me->blkno = blkno;
+
+	return meDelete;
+}
+
+static void
+ginVacuumPostingTree( GinVacuumState *gvs, BlockNumber rootBlkno ) {
+	Buffer	rootBuffer = InvalidBuffer;
+	DataPageDeleteStack	root, *ptr, *tmp;
+
+	if ( ginVacuumPostingTreeLeaves(gvs, rootBlkno, TRUE, &rootBuffer)==FALSE ) {
+		Assert( rootBuffer == InvalidBuffer );
+		return;
+	}
+
+	memset(&root,0,sizeof(DataPageDeleteStack));
+	root.blkno = rootBlkno;
+	root.isRoot = TRUE;
+
+	vacuum_delay_point();
+
+	ginScanToDelete( gvs, rootBlkno, TRUE, &root, InvalidOffsetNumber ); 
+
+	ptr = root.child;
+	while( ptr ) {
+		tmp = ptr->child;
+		pfree( ptr );
+		ptr = tmp;
+	}
+
+	UnlockReleaseBuffer( rootBuffer );
+}
+
+/*
+ * returns modified page or NULL if page isn't modified.
+ * Function works with original page until first change is occured,
+ * then page is copied into temprorary one.
+ */
+static Page
+ginVacuumEntryPage(GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint32 *nroot) {
+	Page	origpage = BufferGetPage( buffer ), tmppage;
+	OffsetNumber i, maxoff = PageGetMaxOffsetNumber( origpage );
+
+	tmppage = origpage;
+
+	*nroot=0;
+
+	for(i=FirstOffsetNumber; i<= maxoff; i++) {
+		IndexTuple itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
+
+		if ( GinIsPostingTree(itup) ) {
+			/* store posting tree's roots for further processing,
+			   we can't vacuum it just now due to risk of deadlocks with scans/inserts */
+			roots[ *nroot ] = GinItemPointerGetBlockNumber(&itup->t_tid);
+			(*nroot)++;
+		} else if ( GinGetNPosting(itup) > 0 ) {
+			/* if we already create temrorary page, we will make changes in place */
+			ItemPointerData	*cleaned = (tmppage==origpage) ? NULL : GinGetPosting(itup );
+			uint32  newN = ginVacuumPostingList( gvs, GinGetPosting(itup), GinGetNPosting(itup), &cleaned );
+			
+			if ( GinGetNPosting(itup) != newN ) {
+				bool	isnull;
+				Datum value;
+
+				/*
+				 * Some ItemPointers was deleted, so we should remake our tuple 
+				 */
+
+				if ( tmppage==origpage ) {
+					/*
+					 * On first difference we create temprorary page in memory
+					 * and copies content in to it.
+					 */
+					tmppage=GinPageGetCopyPage ( origpage );
+
+					if ( newN > 0 ) { 
+						Size	pos = ((char*)GinGetPosting(itup)) - ((char*)origpage);
+						memcpy( tmppage+pos, cleaned, sizeof(ItemPointerData)*newN );
+					}
+
+					pfree( cleaned );
+
+					/* set itup pointer to new page */
+					itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
+				}
+
+				value = index_getattr(itup, FirstOffsetNumber, gvs->ginstate.tupdesc, &isnull);
+				itup = GinFormTuple(&gvs->ginstate, value, GinGetPosting(itup), newN);
+				PageIndexTupleDelete(tmppage, i);
+
+				if ( PageAddItem( tmppage, (Item)itup, IndexTupleSize(itup), i, LP_USED ) != i )
+						elog(ERROR, "failed to add item to index page in \"%s\"",
+								RelationGetRelationName(gvs->index));
+
+				pfree( itup );
+			}
+		}
+	}
+
+	return 	( tmppage==origpage ) ? NULL : tmppage;
+}
+
+Datum
+ginbulkdelete(PG_FUNCTION_ARGS) {
+	Relation    index = (Relation) PG_GETARG_POINTER(0);
+	IndexBulkDeleteCallback callback = (IndexBulkDeleteCallback) PG_GETARG_POINTER(1);
+	void       *callback_state = (void *) PG_GETARG_POINTER(2);
+	BlockNumber	blkno = GIN_ROOT_BLKNO;
+	GinVacuumState	gvs;
+	Buffer 		buffer;
+	BlockNumber	rootOfPostingTree[ BLCKSZ/ (sizeof(IndexTupleData)+sizeof(ItemId)) ];
+	uint32 nRoot;
+
+	gvs.index = index;
+	gvs.result = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
+	gvs.callback = callback;
+	gvs.callback_state = callback_state;
+	initGinState(&gvs.ginstate, index);
+
+	buffer = ReadBuffer( index, blkno );
+
+	/* find leaf page */
+	for(;;) {
+		Page page = BufferGetPage( buffer );
+    	IndexTuple  itup;
+
+		LockBuffer(buffer,GIN_SHARE);
+
+		Assert( !GinPageIsData(page) );
+
+		if ( GinPageIsLeaf(page) ) {
+			LockBuffer(buffer,GIN_UNLOCK);
+			LockBuffer(buffer,GIN_EXCLUSIVE);
+
+			if ( blkno==GIN_ROOT_BLKNO && !GinPageIsLeaf(page) ) {
+				LockBuffer(buffer,GIN_UNLOCK);
+				continue; /* check it one more */
+			}
+			break;	
+		}
+
+		Assert( PageGetMaxOffsetNumber(page) >= FirstOffsetNumber );
+
+		itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber));
+		blkno = GinItemPointerGetBlockNumber(&(itup)->t_tid);
+		Assert( blkno!= InvalidBlockNumber );
+
+		LockBuffer(buffer,GIN_UNLOCK);
+		buffer = ReleaseAndReadBuffer( buffer, index, blkno );
+	}
+
+	/* right now we found leftmost page in entry's BTree */
+
+	for(;;) {
+		Page	page = BufferGetPage( buffer );
+		Page	resPage;
+		uint32 	i;
+
+		Assert( !GinPageIsData(page) );
+
+		resPage = ginVacuumEntryPage(&gvs, buffer, rootOfPostingTree, &nRoot);
+
+		blkno = GinPageGetOpaque( page )->rightlink;
+
+		if ( resPage ) {
+			START_CRIT_SECTION();
+			PageRestoreTempPage( resPage, page );
+			xlogVacuumPage(gvs.index,  buffer);
+			MarkBufferDirty( buffer );
+			UnlockReleaseBuffer(buffer);
+			END_CRIT_SECTION();
+		} else {
+			UnlockReleaseBuffer(buffer);
+		}
+
+		vacuum_delay_point();
+
+		for(i=0; i<nRoot; i++) { 
+			ginVacuumPostingTree( &gvs, rootOfPostingTree[i] );
+			vacuum_delay_point();
+		}
+
+		if ( blkno==InvalidBlockNumber ) /*rightmost page*/
+			break;
+
+		buffer = ReadBuffer( index, blkno );
+		LockBuffer(buffer,GIN_EXCLUSIVE);
+	}
+
+	PG_RETURN_POINTER(gvs.result);
+}
+
+Datum 
+ginvacuumcleanup(PG_FUNCTION_ARGS) {
+	Relation    index = (Relation) PG_GETARG_POINTER(0);
+	IndexVacuumCleanupInfo *info = (IndexVacuumCleanupInfo *) PG_GETARG_POINTER(1);
+	IndexBulkDeleteResult *stats = (IndexBulkDeleteResult *) PG_GETARG_POINTER(2);
+	bool	 needLock = !RELATION_IS_LOCAL(index);
+    BlockNumber npages,
+				blkno;
+	BlockNumber nFreePages,
+			   *freePages,
+			   maxFreePages;
+	BlockNumber lastBlock = GIN_ROOT_BLKNO,
+				   lastFilledBlock = GIN_ROOT_BLKNO;
+
+
+	if (info->vacuum_full) {
+		LockRelation(index, AccessExclusiveLock);
+		needLock = false;
+	}
+
+	if (needLock)
+		LockRelationForExtension(index, ExclusiveLock);
+	npages = RelationGetNumberOfBlocks(index);
+	if (needLock)
+		UnlockRelationForExtension(index, ExclusiveLock);
+
+	maxFreePages = npages;
+	if (maxFreePages > MaxFSMPages)
+		maxFreePages = MaxFSMPages;
+
+	nFreePages = 0;
+	freePages = (BlockNumber *) palloc(sizeof(BlockNumber) * maxFreePages);
+
+	for (blkno = GIN_ROOT_BLKNO + 1; blkno < npages; blkno++) {
+		Buffer buffer;
+		Page   page;
+
+		vacuum_delay_point();
+	
+		buffer = ReadBuffer(index, blkno);
+		LockBuffer(buffer, GIN_SHARE);
+		page = (Page) BufferGetPage(buffer);
+
+		if ( GinPageIsDeleted(page) ) {
+			if (nFreePages < maxFreePages)
+				freePages[nFreePages++] = blkno;
+		} else
+			lastFilledBlock = blkno;
+
+		UnlockReleaseBuffer(buffer);
+	}
+	lastBlock = npages - 1;
+
+	if (info->vacuum_full && nFreePages > 0) {
+		/* try to truncate index */
+		int         i;
+		for (i = 0; i < nFreePages; i++) 
+			if (freePages[i] >= lastFilledBlock) {
+				nFreePages = i;
+				break;
+			}
+
+		if (lastBlock > lastFilledBlock)
+			RelationTruncate(index, lastFilledBlock + 1);
+
+		stats->pages_removed = lastBlock - lastFilledBlock;
+	}
+
+	RecordIndexFreeSpace(&index->rd_node, nFreePages, freePages);
+	stats->pages_free = nFreePages;
+
+	if (needLock)
+		LockRelationForExtension(index, ExclusiveLock);
+	stats->num_pages = RelationGetNumberOfBlocks(index);
+	if (needLock)
+		UnlockRelationForExtension(index, ExclusiveLock);
+
+	if (info->vacuum_full)
+		UnlockRelation(index, AccessExclusiveLock);
+
+	PG_RETURN_POINTER(stats);
+}
+
diff --git a/src/backend/access/gin/ginxlog.c b/src/backend/access/gin/ginxlog.c
new file mode 100644
index 0000000000000000000000000000000000000000..bc6a458e5fc961b46c32e357144e7b0e6d4aba72
--- /dev/null
+++ b/src/backend/access/gin/ginxlog.c
@@ -0,0 +1,544 @@
+/*-------------------------------------------------------------------------
+ *
+ * ginxlog.c
+ *	  WAL replay logic for inverted index.
+ *
+ *
+ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *			 $PostgreSQL: pgsql/src/backend/access/gin/ginxlog.c,v 1.1 2006/05/02 11:28:54 teodor Exp $
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/gin.h"
+#include "access/heapam.h"
+#include "catalog/index.h"
+#include "commands/vacuum.h"
+#include "miscadmin.h"
+#include "utils/memutils.h"
+
+static MemoryContext opCtx;     /* working memory for operations */
+static MemoryContext topCtx;
+
+typedef struct ginIncompleteSplit {
+	RelFileNode	node;
+    BlockNumber leftBlkno;
+	BlockNumber rightBlkno;
+	BlockNumber rootBlkno;
+} ginIncompleteSplit;
+
+static List *incomplete_splits;
+
+static void
+pushIncompleteSplit(RelFileNode node, BlockNumber leftBlkno, BlockNumber rightBlkno, BlockNumber rootBlkno) {
+	ginIncompleteSplit *split; 
+
+	MemoryContextSwitchTo( topCtx );	
+
+	split = palloc(sizeof(ginIncompleteSplit));
+
+	split->node = node;
+	split->leftBlkno = leftBlkno;
+	split->rightBlkno = rightBlkno;
+	split->rootBlkno = rootBlkno;
+
+	incomplete_splits = lappend(incomplete_splits, split);
+
+	MemoryContextSwitchTo( opCtx );	
+}
+
+static void
+forgetIncompleteSplit(RelFileNode node, BlockNumber leftBlkno, BlockNumber updateBlkno) {
+	ListCell   *l;
+
+	foreach(l, incomplete_splits) {
+		ginIncompleteSplit	*split = (ginIncompleteSplit *) lfirst(l);
+
+		if ( RelFileNodeEquals(node, split->node) && leftBlkno == split->leftBlkno && updateBlkno == split->rightBlkno ) {
+			incomplete_splits = list_delete_ptr(incomplete_splits, split);
+			break;
+		}
+	}
+}
+
+static void
+ginRedoCreateIndex(XLogRecPtr lsn, XLogRecord *record) {
+	RelFileNode *node = (RelFileNode *) XLogRecGetData(record);
+	Relation	reln;
+	Buffer		buffer;
+	Page		page;
+
+	reln = XLogOpenRelation(*node);
+	buffer = XLogReadBuffer(reln, GIN_ROOT_BLKNO, true);
+	Assert(BufferIsValid(buffer));
+	page = (Page) BufferGetPage(buffer);
+
+	GinInitBuffer(buffer, GIN_LEAF);
+
+	PageSetLSN(page, lsn);
+	PageSetTLI(page, ThisTimeLineID);
+
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+}
+
+static void
+ginRedoCreatePTree(XLogRecPtr lsn, XLogRecord *record) {
+	ginxlogCreatePostingTree	*data = (ginxlogCreatePostingTree*)XLogRecGetData(record);
+	ItemPointerData				*items = (ItemPointerData*)(XLogRecGetData(record) + sizeof(ginxlogCreatePostingTree));
+	Relation	reln;
+	Buffer		buffer;
+	Page		page;
+
+	reln = XLogOpenRelation(data->node);
+	buffer = XLogReadBuffer(reln, data->blkno, true);
+	Assert(BufferIsValid(buffer));
+	page = (Page) BufferGetPage(buffer);
+
+	GinInitBuffer(buffer, GIN_DATA|GIN_LEAF);
+	memcpy( GinDataPageGetData(page), items, sizeof(ItemPointerData) * data->nitem );
+	GinPageGetOpaque(page)->maxoff = data->nitem;
+
+	PageSetLSN(page, lsn);
+	PageSetTLI(page, ThisTimeLineID);
+
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+}
+
+static void
+ginRedoInsert(XLogRecPtr lsn, XLogRecord *record) {
+	ginxlogInsert	*data = (ginxlogInsert*)XLogRecGetData(record);
+	Relation	reln;
+	Buffer		buffer;
+	Page		page;
+
+	/* nothing else to do if page was backed up (and no info to do it with) */
+	if (record->xl_info & XLR_BKP_BLOCK_1)
+		return;
+
+	reln = XLogOpenRelation(data->node);
+	buffer = XLogReadBuffer(reln, data->blkno, false);
+	Assert(BufferIsValid(buffer));
+	page = (Page) BufferGetPage(buffer);
+
+	if ( data->isData ) {
+		Assert( data->isDelete == FALSE );
+		Assert( GinPageIsData( page ) );
+
+		if ( data->isLeaf ) {
+			OffsetNumber i;
+			ItemPointerData	*items = (ItemPointerData*)( XLogRecGetData(record) + sizeof(ginxlogInsert) );
+
+			Assert( GinPageIsLeaf( page ) );
+			Assert( data->updateBlkno == InvalidBlockNumber );
+
+			for(i=0;i<data->nitem;i++)
+				GinDataPageAddItem( page, items+i, data->offset + i );	
+		} else {
+			PostingItem *pitem;
+
+			Assert( !GinPageIsLeaf( page ) );
+
+			if ( data->updateBlkno != InvalidBlockNumber ) {
+				/* update link to right page after split */ 
+				pitem = (PostingItem*)GinDataPageGetItem(page, data->offset);
+				PostingItemSetBlockNumber( pitem, data->updateBlkno );
+			}
+
+			pitem = (PostingItem*)( XLogRecGetData(record) + sizeof(ginxlogInsert) );
+
+			GinDataPageAddItem( page, pitem, data->offset );
+
+			if ( data->updateBlkno != InvalidBlockNumber ) 
+				forgetIncompleteSplit(data->node, PostingItemGetBlockNumber( pitem ), data->updateBlkno);
+		}
+	} else {
+		IndexTuple itup;
+
+		Assert( !GinPageIsData( page ) );
+
+		if ( data->updateBlkno != InvalidBlockNumber ) {
+			/* update link to right page after split */ 
+			Assert( !GinPageIsLeaf( page ) );
+			Assert( data->offset>=FirstOffsetNumber && data->offset<=PageGetMaxOffsetNumber(page) );
+			itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, data->offset));
+			ItemPointerSet(&itup->t_tid, data->updateBlkno, InvalidOffsetNumber);
+		}
+
+		if ( data->isDelete ) {
+			Assert( GinPageIsLeaf( page ) );
+			Assert( data->offset>=FirstOffsetNumber && data->offset<=PageGetMaxOffsetNumber(page) );
+			PageIndexTupleDelete(page, data->offset);
+		}
+
+		itup = (IndexTuple)( XLogRecGetData(record) + sizeof(ginxlogInsert) );
+
+		if ( PageAddItem( page, (Item)itup, IndexTupleSize(itup), data->offset, LP_USED) == InvalidOffsetNumber )
+			elog(ERROR, "failed to add item to index page in %u/%u/%u", 
+				data->node.spcNode, data->node.dbNode, data->node.relNode );
+
+		if ( !data->isLeaf && data->updateBlkno != InvalidBlockNumber )
+			forgetIncompleteSplit(data->node, GinItemPointerGetBlockNumber( &itup->t_tid ), data->updateBlkno);
+	}
+
+	PageSetLSN(page, lsn);
+	PageSetTLI(page, ThisTimeLineID);
+
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+}
+
+static void
+ginRedoSplit(XLogRecPtr lsn, XLogRecord *record) {
+	ginxlogSplit	*data = (ginxlogSplit*)XLogRecGetData(record);
+	Relation	reln;
+	Buffer		lbuffer, rbuffer;
+	Page		lpage, rpage;
+	uint32		flags = 0;
+
+	reln = XLogOpenRelation(data->node);
+
+	if ( data->isLeaf )
+		flags |= GIN_LEAF;
+	if ( data->isData )
+		flags |= GIN_DATA;
+
+	lbuffer = XLogReadBuffer(reln, data->lblkno, data->isRootSplit);
+	Assert(BufferIsValid(lbuffer));
+	lpage = (Page) BufferGetPage(lbuffer);
+	GinInitBuffer(lbuffer, flags);
+
+	rbuffer = XLogReadBuffer(reln, data->rblkno, true);
+	Assert(BufferIsValid(rbuffer));
+	rpage = (Page) BufferGetPage(rbuffer);
+	GinInitBuffer(rbuffer, flags);
+
+	GinPageGetOpaque(lpage)->rightlink = BufferGetBlockNumber( rbuffer );
+	GinPageGetOpaque(rpage)->rightlink = data->rrlink;
+
+	if ( data->isData ) {
+		char	*ptr = XLogRecGetData(record) + sizeof(ginxlogSplit);
+		Size	sizeofitem = GinSizeOfItem(lpage);
+		OffsetNumber i;
+		ItemPointer	bound;
+
+		for(i=0;i<data->separator;i++) {
+			GinDataPageAddItem( lpage, ptr, InvalidOffsetNumber );
+			ptr += sizeofitem;
+		}
+
+		for(i=data->separator;i<data->nitem;i++) {
+			GinDataPageAddItem( rpage, ptr, InvalidOffsetNumber );
+			ptr += sizeofitem;
+		}
+
+		/* set up right key */
+		bound = GinDataPageGetRightBound(lpage);
+		if ( data->isLeaf ) 
+			*bound = *(ItemPointerData*)GinDataPageGetItem(lpage, GinPageGetOpaque(lpage)->maxoff);
+		else
+			*bound = ((PostingItem*)GinDataPageGetItem(lpage, GinPageGetOpaque(lpage)->maxoff))->key;
+
+		bound = GinDataPageGetRightBound(rpage);
+		*bound = data->rightbound;
+	} else {
+		IndexTuple itup = (IndexTuple)( XLogRecGetData(record) + sizeof(ginxlogSplit) );
+		OffsetNumber i;
+
+		for(i=0;i<data->separator;i++) {
+			if ( PageAddItem( lpage, (Item)itup, IndexTupleSize(itup), InvalidOffsetNumber, LP_USED) == InvalidOffsetNumber )
+				elog(ERROR, "failed to add item to index page in %u/%u/%u", 
+					data->node.spcNode, data->node.dbNode, data->node.relNode );
+			itup = (IndexTuple)(  ((char*)itup) + MAXALIGN( IndexTupleSize(itup) ) );
+		}
+
+		for(i=data->separator;i<data->nitem;i++) {
+			if ( PageAddItem( rpage, (Item)itup, IndexTupleSize(itup), InvalidOffsetNumber, LP_USED) == InvalidOffsetNumber )
+				elog(ERROR, "failed to add item to index page in %u/%u/%u", 
+					data->node.spcNode, data->node.dbNode, data->node.relNode );
+			itup = (IndexTuple)(  ((char*)itup) + MAXALIGN( IndexTupleSize(itup) ) );
+		}
+	}
+
+	PageSetLSN(rpage, lsn);
+	PageSetTLI(lpage, ThisTimeLineID);
+	MarkBufferDirty(rbuffer);
+
+	PageSetLSN(lpage, lsn);
+	PageSetTLI(lpage, ThisTimeLineID);
+	MarkBufferDirty(lbuffer);
+
+	if ( !data->isLeaf && data->updateBlkno != InvalidBlockNumber ) 
+		forgetIncompleteSplit(data->node, data->leftChildBlkno, data->updateBlkno);
+
+	if ( data->isRootSplit ) {
+		Buffer	rootBuf = XLogReadBuffer(reln, data->rootBlkno, false);
+		Page	rootPage = BufferGetPage( rootBuf );
+
+		GinInitBuffer( rootBuf, flags & ~GIN_LEAF );
+
+		if ( data->isData ) {
+			Assert( data->rootBlkno != GIN_ROOT_BLKNO );
+			dataFillRoot(NULL, rootBuf, lbuffer, rbuffer);
+		} else {
+			Assert( data->rootBlkno == GIN_ROOT_BLKNO );
+			entryFillRoot(NULL, rootBuf, lbuffer, rbuffer);
+		}
+
+		PageSetLSN(rootPage, lsn);
+		PageSetTLI(rootPage, ThisTimeLineID);
+
+		MarkBufferDirty(rootBuf);
+		UnlockReleaseBuffer(rootBuf);
+	} else 
+		pushIncompleteSplit(data->node, data->lblkno, data->rblkno, data->rootBlkno);
+
+	UnlockReleaseBuffer(rbuffer);
+	UnlockReleaseBuffer(lbuffer);
+}
+
+static void
+ginRedoVacuumPage(XLogRecPtr lsn, XLogRecord *record) {
+	ginxlogVacuumPage	*data = (ginxlogVacuumPage*)XLogRecGetData(record);
+	Relation	reln;
+	Buffer		buffer;
+	Page		page;
+
+	/* nothing else to do if page was backed up (and no info to do it with) */
+	if (record->xl_info & XLR_BKP_BLOCK_1)
+		return;
+
+	reln = XLogOpenRelation(data->node);
+	buffer = XLogReadBuffer(reln, data->blkno, false);
+	Assert(BufferIsValid(buffer));
+	page = (Page) BufferGetPage(buffer);
+
+	if ( GinPageIsData( page ) ) {
+		memcpy( GinDataPageGetData(page), XLogRecGetData(record) + sizeof(ginxlogVacuumPage),
+					GinSizeOfItem(page) * data->nitem );
+		GinPageGetOpaque(page)->maxoff = data->nitem;
+	} else {
+		OffsetNumber i, *tod;
+		IndexTuple itup = (IndexTuple)( XLogRecGetData(record) + sizeof(ginxlogVacuumPage) );
+
+		tod = (OffsetNumber*)palloc( sizeof(OffsetNumber) * PageGetMaxOffsetNumber(page) );
+		for(i=FirstOffsetNumber;i<=PageGetMaxOffsetNumber(page);i++)
+			tod[i-1] = i;
+
+		PageIndexMultiDelete(page, tod, PageGetMaxOffsetNumber(page));	
+			
+		for(i=0;i<data->nitem;i++) {
+			if ( PageAddItem( page, (Item)itup, IndexTupleSize(itup), InvalidOffsetNumber, LP_USED) == InvalidOffsetNumber )
+				elog(ERROR, "failed to add item to index page in %u/%u/%u", 
+					data->node.spcNode, data->node.dbNode, data->node.relNode );
+			itup = (IndexTuple)(  ((char*)itup) + MAXALIGN( IndexTupleSize(itup) ) );
+		}
+	}
+
+	PageSetLSN(page, lsn);
+	PageSetTLI(page, ThisTimeLineID);
+
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+}
+
+static void
+ginRedoDeletePage(XLogRecPtr lsn, XLogRecord *record) {
+	ginxlogDeletePage	*data = (ginxlogDeletePage*)XLogRecGetData(record);
+	Relation	reln;
+	Buffer		buffer;
+	Page		page;
+
+	reln = XLogOpenRelation(data->node);
+
+	if ( !( record->xl_info & XLR_BKP_BLOCK_1) ) {
+		buffer = XLogReadBuffer(reln, data->blkno, false);
+		page = BufferGetPage( buffer );
+		Assert(GinPageIsData(page));
+		GinPageGetOpaque(page)->flags = GIN_DELETED;
+		PageSetLSN(page, lsn);
+		PageSetTLI(page, ThisTimeLineID);
+		MarkBufferDirty(buffer);
+		UnlockReleaseBuffer(buffer);
+	}
+
+	if ( !( record->xl_info & XLR_BKP_BLOCK_2) ) { 
+		buffer = XLogReadBuffer(reln, data->parentBlkno, false);
+		page = BufferGetPage( buffer );
+		Assert(GinPageIsData(page));
+		Assert(!GinPageIsLeaf(page));
+		PageDeletePostingItem(page, data->parentOffset);
+		PageSetLSN(page, lsn);
+		PageSetTLI(page, ThisTimeLineID);
+		MarkBufferDirty(buffer);
+		UnlockReleaseBuffer(buffer);
+	}
+
+	if ( !( record->xl_info & XLR_BKP_BLOCK_2) && data->leftBlkno != InvalidBlockNumber ) { 
+		buffer = XLogReadBuffer(reln, data->leftBlkno, false);
+		page = BufferGetPage( buffer );
+		Assert(GinPageIsData(page));
+		GinPageGetOpaque(page)->rightlink = data->rightLink;
+		PageSetLSN(page, lsn);
+		PageSetTLI(page, ThisTimeLineID);
+		MarkBufferDirty(buffer);
+		UnlockReleaseBuffer(buffer);
+	}
+}
+
+void 
+gin_redo(XLogRecPtr lsn, XLogRecord *record) {
+	uint8       info = record->xl_info & ~XLR_INFO_MASK;
+
+	topCtx = MemoryContextSwitchTo(opCtx);
+	switch (info) {
+		case	XLOG_GIN_CREATE_INDEX:
+			ginRedoCreateIndex(lsn, record);
+			break;
+		case	XLOG_GIN_CREATE_PTREE:
+			ginRedoCreatePTree(lsn, record);
+			break;
+		case	XLOG_GIN_INSERT:
+			ginRedoInsert(lsn, record);
+			break;
+		case	XLOG_GIN_SPLIT:
+			ginRedoSplit(lsn, record);
+			break;
+		case	XLOG_GIN_VACUUM_PAGE:
+			ginRedoVacuumPage(lsn, record);
+			break;
+		case	XLOG_GIN_DELETE_PAGE:
+			ginRedoDeletePage(lsn, record);
+			break;
+		default:
+			elog(PANIC, "gin_redo: unknown op code %u", info);
+	}
+	MemoryContextSwitchTo(topCtx);
+	MemoryContextReset(opCtx);
+}
+
+static void
+desc_node( StringInfo buf, RelFileNode node, BlockNumber blkno ) {
+	appendStringInfo(buf,"node: %u/%u/%u blkno: %u",
+		node.spcNode, node.dbNode, node.relNode, blkno);
+}
+
+void 
+gin_desc(StringInfo buf, uint8 xl_info, char *rec) {
+	uint8       info = xl_info & ~XLR_INFO_MASK;
+
+	switch (info) {
+		case	XLOG_GIN_CREATE_INDEX:
+			appendStringInfo(buf,"Create index, ");
+			desc_node(buf, *(RelFileNode*)rec, GIN_ROOT_BLKNO );
+			break;
+		case	XLOG_GIN_CREATE_PTREE:
+			appendStringInfo(buf,"Create posting tree, ");
+			desc_node(buf, ((ginxlogCreatePostingTree*)rec)->node, ((ginxlogCreatePostingTree*)rec)->blkno );
+			break;
+		case	XLOG_GIN_INSERT:
+			appendStringInfo(buf,"Insert item, ");
+			desc_node(buf, ((ginxlogInsert*)rec)->node, ((ginxlogInsert*)rec)->blkno );
+			appendStringInfo(buf," offset: %u nitem: %u isdata: %c isleaf %c isdelete %c updateBlkno:%u",
+				((ginxlogInsert*)rec)->offset,
+				((ginxlogInsert*)rec)->nitem,
+				( ((ginxlogInsert*)rec)->isData ) ? 'T' : 'F',
+				( ((ginxlogInsert*)rec)->isLeaf ) ? 'T' : 'F',
+				( ((ginxlogInsert*)rec)->isDelete ) ? 'T' : 'F',
+				((ginxlogInsert*)rec)->updateBlkno
+			);
+
+			break;
+		case	XLOG_GIN_SPLIT:
+			appendStringInfo(buf,"Page split, ");
+			desc_node(buf, ((ginxlogSplit*)rec)->node, ((ginxlogSplit*)rec)->lblkno );
+			appendStringInfo(buf," isrootsplit: %c", ( ((ginxlogSplit*)rec)->isRootSplit ) ? 'T' : 'F');
+			break;
+		case	XLOG_GIN_VACUUM_PAGE:
+			appendStringInfo(buf,"Vacuum page, ");
+			desc_node(buf, ((ginxlogVacuumPage*)rec)->node, ((ginxlogVacuumPage*)rec)->blkno );
+			break;
+		case	XLOG_GIN_DELETE_PAGE:
+			appendStringInfo(buf,"Delete page, ");
+			desc_node(buf, ((ginxlogDeletePage*)rec)->node, ((ginxlogDeletePage*)rec)->blkno );
+			break;
+		default:
+			elog(PANIC, "gin_desc: unknown op code %u", info);
+	}
+}
+
+void 
+gin_xlog_startup(void) {
+	incomplete_splits = NIL;
+
+	opCtx = AllocSetContextCreate(CurrentMemoryContext,
+		"GIN recovery temporary context",
+		ALLOCSET_DEFAULT_MINSIZE,
+		ALLOCSET_DEFAULT_INITSIZE,
+		ALLOCSET_DEFAULT_MAXSIZE);
+}
+
+static void
+ginContinueSplit( ginIncompleteSplit *split ) {
+	GinBtreeData btree;
+	Relation	reln;
+	Buffer		buffer;
+	GinBtreeStack	stack;
+
+	/* elog(NOTICE,"ginContinueSplit root:%u l:%u r:%u",  split->rootBlkno, split->leftBlkno, split->rightBlkno); */
+	reln = XLogOpenRelation(split->node);
+
+	buffer =  XLogReadBuffer(reln, split->leftBlkno, false);
+
+	if ( split->rootBlkno == GIN_ROOT_BLKNO ) {
+		prepareEntryScan( &btree, reln, (Datum)0, NULL );
+		btree.entry = ginPageGetLinkItup( buffer );
+	} else {
+		Page		page = BufferGetPage( buffer );
+
+		prepareDataScan( &btree, reln );
+
+    	PostingItemSetBlockNumber( &(btree.pitem), split->leftBlkno );
+		if ( GinPageIsLeaf(page) )
+			btree.pitem.key = *(ItemPointerData*)GinDataPageGetItem(page,
+				GinPageGetOpaque(page)->maxoff);
+		else
+			btree.pitem.key = ((PostingItem*)GinDataPageGetItem(page,
+				GinPageGetOpaque(page)->maxoff))->key;
+	}
+
+	btree.rightblkno = split->rightBlkno; 
+
+	stack.blkno = split->leftBlkno;
+	stack.buffer = buffer;
+	stack.off = InvalidOffsetNumber;
+	stack.parent = NULL;
+
+	findParents( &btree, &stack, split->rootBlkno);
+	ginInsertValue( &btree, stack.parent );
+
+	UnlockReleaseBuffer( buffer );
+}
+
+void 
+gin_xlog_cleanup(void) {
+	ListCell   *l;
+	MemoryContext topCtx;
+
+	topCtx = MemoryContextSwitchTo(opCtx);
+
+	foreach(l, incomplete_splits) {
+		ginIncompleteSplit *split = (ginIncompleteSplit *) lfirst(l);
+		ginContinueSplit( split );
+		MemoryContextReset( opCtx );
+	}
+
+	MemoryContextSwitchTo(topCtx);
+	MemoryContextDelete(opCtx);
+}
+
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index f7b48df1263025fa5a167b7e223cf2e0eec02591..f65e9374c5d292663cd1d40e8c326a6e5813d15f 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -3,7 +3,7 @@
  *
  * Resource managers definition
  *
- * $PostgreSQL: pgsql/src/backend/access/transam/rmgr.c,v 1.21 2005/11/07 17:36:45 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/access/transam/rmgr.c,v 1.22 2006/05/02 11:28:54 teodor Exp $
  */
 #include "postgres.h"
 
@@ -13,6 +13,7 @@
 #include "access/heapam.h"
 #include "access/multixact.h"
 #include "access/nbtree.h"
+#include "access/gin.h"
 #include "access/xact.h"
 #include "access/xlog_internal.h"
 #include "commands/dbcommands.h"
@@ -35,7 +36,7 @@ const RmgrData RmgrTable[RM_MAX_ID + 1] = {
 	{"Heap", heap_redo, heap_desc, NULL, NULL},
 	{"Btree", btree_redo, btree_desc, btree_xlog_startup, btree_xlog_cleanup},
 	{"Hash", hash_redo, hash_desc, NULL, NULL},
-	{"Reserved 13", NULL, NULL, NULL, NULL},
+	{"Gin", gin_redo, gin_desc, gin_xlog_startup, gin_xlog_cleanup},
 	{"Gist", gist_redo, gist_desc, gist_xlog_startup, gist_xlog_cleanup},
 	{"Sequence", seq_redo, seq_desc, NULL, NULL}
 };
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index ce85d077a34fdfa124dfd00e2dda82924495f6f0..18845eecea2adabcd840363058719b826c0bc3dc 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.144 2006/03/05 15:58:23 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.145 2006/05/02 11:28:54 teodor Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -376,6 +376,13 @@ check_index_is_clusterable(Relation OldHeap, Oid indexOid, bool recheck)
 							RelationGetRelationName(OldIndex))));
 	}
 
+	if (!OldIndex->rd_am->amclusterable) 
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			   errmsg("cannot cluster on index \"%s\" because access method does not clusterable",
+			   	RelationGetRelationName(OldIndex))));
+
+
 	/*
 	 * Disallow clustering system relations.  This will definitely NOT work
 	 * for shared relations (we have no way to update pg_class rows in other
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index d66c403e515f4db35e3783745bf0da94588a72f9..c493cc8e0beed6d7f049fd280010fca0af067be1 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/opclasscmds.c,v 1.43 2006/03/14 22:48:18 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/opclasscmds.c,v 1.44 2006/05/02 11:28:54 teodor Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -273,11 +273,11 @@ DefineOpClass(CreateOpClassStmt *stmt)
 		else
 		{
 			/*
-			 * Currently, only GiST allows storagetype different from
+			 * Currently, only GiST and GIN allows storagetype different from
 			 * datatype.  This hardcoded test should be eliminated in favor of
 			 * adding another boolean column to pg_am ...
 			 */
-			if (amoid != GIST_AM_OID)
+			if (!(amoid == GIST_AM_OID || amoid == GIN_AM_OID))
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 						 errmsg("storage type may not be different from data type for access method \"%s\"",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index c6ebdd5770b8a2d320b2d8b3ae2fb31d7d8c82ff..ececa0c07329fe1954aec4e64098cfd3ffcac017 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -13,7 +13,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.326 2006/03/31 23:32:06 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.327 2006/05/02 11:28:54 teodor Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2982,7 +2982,16 @@ scan_index(Relation indrel, double num_tuples)
 	/*
 	 * Check for tuple count mismatch.	If the index is partial, then it's OK
 	 * for it to have fewer tuples than the heap; else we got trouble.
+	 *
+	 * XXX Hack. Since GIN stores every pointer to heap several times and
+	 * counting num_index_tuples during vacuum is very comlpex and slow
+	 * we just copy num_tuples to num_index_tuples as upper limit to avoid
+	 * WARNING and optimizer mistakes.
 	 */
+	if ( indrel->rd_rel->relam == GIN_AM_OID ) 
+	{
+		stats->num_index_tuples = num_tuples; 
+	} else
 	if (stats->num_index_tuples != num_tuples)
 	{
 		if (stats->num_index_tuples > num_tuples ||
@@ -3052,7 +3061,16 @@ vacuum_index(VacPageList vacpagelist, Relation indrel,
 	/*
 	 * Check for tuple count mismatch.	If the index is partial, then it's OK
 	 * for it to have fewer tuples than the heap; else we got trouble.
+	 *
+	 * XXX Hack. Since GIN stores every pointer to heap several times and
+	 * counting num_index_tuples during vacuum is very comlpex and slow
+	 * we just copy num_tuples to num_index_tuples as upper limit to avoid
+	 * WARNING and optimizer mistakes.
 	 */
+	if ( indrel->rd_rel->relam == GIN_AM_OID ) 
+	{
+		stats->num_index_tuples = num_tuples; 
+	} else
 	if (stats->num_index_tuples != num_tuples + keep_tuples)
 	{
 		if (stats->num_index_tuples > num_tuples + keep_tuples ||
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 5f830ef0cd3a8422ac0b5acb77c1a28b22ee75a7..5aeadce0917809914475e22b4caa120b6f41bfb9 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -15,7 +15,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/adt/selfuncs.c,v 1.204 2006/05/02 04:34:18 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/selfuncs.c,v 1.205 2006/05/02 11:28:55 teodor Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -4829,3 +4829,21 @@ gistcostestimate(PG_FUNCTION_ARGS)
 
 	PG_RETURN_VOID();
 }
+
+Datum
+gincostestimate(PG_FUNCTION_ARGS)
+{
+	PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0);
+	IndexOptInfo *index = (IndexOptInfo *) PG_GETARG_POINTER(1);
+	List	   *indexQuals = (List *) PG_GETARG_POINTER(2);
+	Cost	   *indexStartupCost = (Cost *) PG_GETARG_POINTER(3);
+	Cost	   *indexTotalCost = (Cost *) PG_GETARG_POINTER(4);
+	Selectivity *indexSelectivity = (Selectivity *) PG_GETARG_POINTER(5);
+	double	   *indexCorrelation = (double *) PG_GETARG_POINTER(6);
+
+	genericcostestimate(root, index, indexQuals, 0.0,
+						indexStartupCost, indexTotalCost,
+						indexSelectivity, indexCorrelation);
+
+	PG_RETURN_VOID();
+}
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 27f31c40fb1ab5e16cbda2f9a3e04e5d02657c62..1295288b89cd07a4e8d18d054afb6694f84bc776 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/init/globals.c,v 1.97 2006/03/05 15:58:46 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/init/globals.c,v 1.98 2006/05/02 11:28:55 teodor Exp $
  *
  * NOTES
  *	  Globals used all over the place should be declared here and not
@@ -107,3 +107,5 @@ int			VacuumCostDelay = 0;
 
 int			VacuumCostBalance = 0;		/* working state for vacuum */
 bool		VacuumCostActive = false;
+
+int 		GinFuzzySearchLimit = 0;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 465e77c48856c42d2c3e1ab3c42ff7a83af76415..c2c1318b4cb4361576355d0aefe1b9be8263e231 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -10,7 +10,7 @@
  * Written by Peter Eisentraut <peter_e@gmx.net>.
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.317 2006/04/25 14:11:56 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.318 2006/05/02 11:28:55 teodor Exp $
  *
  *--------------------------------------------------------------------
  */
@@ -64,7 +64,7 @@
 #include "utils/memutils.h"
 #include "utils/pg_locale.h"
 #include "pgstat.h"
-
+#include "access/gin.h"
 
 #ifndef PG_KRB_SRVTAB
 #define PG_KRB_SRVTAB ""
@@ -1572,6 +1572,16 @@ static struct config_int ConfigureNamesInt[] =
 		0, 0, INT_MAX, assign_tcp_keepalives_count, show_tcp_keepalives_count
 	},
 
+	{
+		{"gin_fuzzy_search_limit", PGC_USERSET, UNGROUPED,
+			gettext_noop("Sets the maximum allowed result for exact search by gin."),
+			NULL,
+			0
+		},
+		&GinFuzzySearchLimit,
+		0, 0, INT_MAX, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL
diff --git a/src/include/access/gin.h b/src/include/access/gin.h
new file mode 100644
index 0000000000000000000000000000000000000000..8fff7f42326c3ab0468a95d6e43b177e844d7741
--- /dev/null
+++ b/src/include/access/gin.h
@@ -0,0 +1,436 @@
+/*--------------------------------------------------------------------------
+ * gin.h
+ *    header file for postgres inverted index access method implementation.
+ *
+ *  Copyright (c) 2006, PostgreSQL Global Development Group
+ *  $PostgreSQL: pgsql/src/include/access/gin.h,v 1.1 2006/05/02 11:28:55 teodor Exp $
+ *--------------------------------------------------------------------------
+ */
+
+
+#ifndef GIN_H
+#define GIN_H
+
+#include "access/xlog.h"
+#include "access/xlogdefs.h"
+#include "storage/bufpage.h"
+#include "storage/off.h"
+#include "utils/rel.h"
+#include "access/itup.h"
+#include "fmgr.h"
+
+
+/*
+ * amproc indexes for inverted indexes.
+ */
+#define GIN_COMPARE_PROC               1
+#define GIN_EXTRACTVALUE_PROC          2
+#define GIN_EXTRACTQUERY_PROC          3
+#define GIN_CONSISTENT_PROC            4
+#define GINNProcs                      4
+
+typedef XLogRecPtr GinNSN;
+
+/*
+ * Page opaque data in a inverted index page.
+ */
+typedef struct GinPageOpaqueData {
+	uint16 			flags;
+ 	OffsetNumber	maxoff; 	/* number entries on GIN_DATA page:
+							   number of heap ItemPointer on GIN_DATA|GIN_LEAF page
+							   and number of records on GIN_DATA & ~GIN_LEAF page
+							 */
+	BlockNumber rightlink;  
+} GinPageOpaqueData;
+
+typedef GinPageOpaqueData *GinPageOpaque;
+
+#define GIN_ROOT_BLKNO	(0)
+
+typedef struct {
+	BlockIdData	child_blkno; /* use it instead of BlockNumber to
+								save space on page */
+	ItemPointerData	key;
+} PostingItem;
+
+#define	PostingItemGetBlockNumber(pointer) \
+	BlockIdGetBlockNumber(&(pointer)->child_blkno)
+
+#define	PostingItemSetBlockNumber(pointer, blockNumber) \
+	BlockIdSet(&((pointer)->child_blkno), (blockNumber))
+
+/*
+ * Page opaque data in a inverted index page.
+ */
+#define GIN_DATA          (1 << 0)
+#define GIN_LEAF          (1 << 1)
+#define GIN_DELETED       (1 << 2)
+
+/*
+ * Works on page 
+ */
+#define GinPageGetOpaque(page) ( (GinPageOpaque) PageGetSpecialPointer(page) )
+
+#define GinPageIsLeaf(page)    ( GinPageGetOpaque(page)->flags & GIN_LEAF )
+#define GinPageSetLeaf(page)   ( GinPageGetOpaque(page)->flags |= GIN_LEAF )
+#define GinPageSetNonLeaf(page)    ( GinPageGetOpaque(page)->flags &= ~GIN_LEAF )
+#define GinPageIsData(page)    ( GinPageGetOpaque(page)->flags & GIN_DATA )
+#define GinPageSetData(page)   ( GinPageGetOpaque(page)->flags |= GIN_DATA )
+
+#define GinPageIsDeleted(page) ( GinPageGetOpaque(page)->flags & GIN_DELETED)
+#define GinPageSetDeleted(page)    ( GinPageGetOpaque(page)->flags |= GIN_DELETED)
+#define GinPageSetNonDeleted(page) ( GinPageGetOpaque(page)->flags &= ~GIN_DELETED)
+
+#define GinPageRightMost(page) ( GinPageGetOpaque(page)->rightlink == InvalidBlockNumber)
+
+/*
+ * Define our ItemPointerGet(BlockNumber|GetOffsetNumber)
+ * to prevent asserts
+ */
+
+#define GinItemPointerGetBlockNumber(pointer) \
+	BlockIdGetBlockNumber(&(pointer)->ip_blkid)
+
+#define GinItemPointerGetOffsetNumber(pointer) \
+	((pointer)->ip_posid)
+
+/*
+ * Support work on IndexTuuple on leaf pages
+ */
+#define GinGetNPosting(itup)	GinItemPointerGetOffsetNumber(&(itup)->t_tid)
+#define GinSetNPosting(itup,n)	ItemPointerSetOffsetNumber(&(itup)->t_tid,(n))
+#define GIN_TREE_POSTING		((OffsetNumber)0xffff)
+#define GinIsPostingTree(itup)	( GinGetNPosting(itup)==GIN_TREE_POSTING )
+#define GinSetPostingTree(itup, blkno)	( GinSetNPosting((itup),GIN_TREE_POSTING ), ItemPointerSetBlockNumber(&(itup)->t_tid, blkno) ) 
+#define GinGetPostingTree(itup)	GinItemPointerGetBlockNumber(&(itup)->t_tid) 
+
+#define GinGetOrigSizePosting(itup)	GinItemPointerGetBlockNumber(&(itup)->t_tid)
+#define GinSetOrigSizePosting(itup,n)	ItemPointerSetBlockNumber(&(itup)->t_tid,(n))
+#define GinGetPosting(itup)			( (ItemPointer)(( ((char*)(itup)) + SHORTALIGN(GinGetOrigSizePosting(itup)) )) )
+
+#define GinMaxItemSize \
+    ((BLCKSZ - SizeOfPageHeaderData - \
+		MAXALIGN(sizeof(GinPageOpaqueData))) / 3 - sizeof(ItemIdData))
+
+
+/*
+ * Data (posting tree) pages
+ */
+#define GinDataPageGetData(page)	\
+	(PageGetContents(page)+MAXALIGN(sizeof(ItemPointerData)))
+#define GinDataPageGetRightBound(page)	((ItemPointer)PageGetContents(page))
+#define GinSizeOfItem(page)	( (GinPageIsLeaf(page)) ? sizeof(ItemPointerData) : sizeof(PostingItem) )
+#define GinDataPageGetItem(page,i) ( GinDataPageGetData(page) + ((i)-1) * GinSizeOfItem(page) ) 
+
+#define GinDataPageGetFreeSpace(page)	\
+	( BLCKSZ - SizeOfPageHeaderData - MAXALIGN(sizeof(GinPageOpaqueData)) - \
+		GinPageGetOpaque(page)->maxoff * GinSizeOfItem(page) - \
+		MAXALIGN(sizeof(ItemPointerData)))
+
+
+
+#define GIN_UNLOCK 	BUFFER_LOCK_UNLOCK
+#define GIN_SHARE  	BUFFER_LOCK_SHARE
+#define GIN_EXCLUSIVE  BUFFER_LOCK_EXCLUSIVE
+
+typedef struct GinState {
+	FmgrInfo	compareFn;
+	FmgrInfo	extractValueFn;
+	FmgrInfo	extractQueryFn;
+	FmgrInfo	consistentFn;
+
+	TupleDesc	tupdesc;
+} GinState;
+
+/* XLog stuff */
+
+#define XLOG_GIN_CREATE_INDEX  0x00
+
+#define XLOG_GIN_CREATE_PTREE  0x10
+
+typedef struct ginxlogCreatePostingTree {
+    RelFileNode 	node;
+	BlockNumber 	blkno;
+	uint32			nitem;
+	/* follows list of heap's ItemPointer */
+} ginxlogCreatePostingTree;
+
+#define XLOG_GIN_INSERT  0x20
+
+typedef struct ginxlogInsert {
+	RelFileNode		node;
+	BlockNumber		blkno;
+	BlockNumber		updateBlkno;
+	OffsetNumber	offset;
+	bool 			isDelete;
+	bool			isData;
+	bool			isLeaf;
+	OffsetNumber	nitem;	
+
+	/* follows: tuples or ItemPointerData or PostingItem or list of ItemPointerData*/
+} ginxlogInsert;
+
+#define XLOG_GIN_SPLIT  0x30
+
+typedef struct ginxlogSplit {
+	RelFileNode		node;
+	BlockNumber		lblkno;
+	BlockNumber		rootBlkno;
+	BlockNumber		rblkno;
+	BlockNumber		rrlink;
+	OffsetNumber	separator;
+	OffsetNumber	nitem;
+
+	bool			isData;
+	bool			isLeaf;
+	bool			isRootSplit;
+
+	BlockNumber		leftChildBlkno;
+	BlockNumber		updateBlkno;
+
+	ItemPointerData	rightbound; /* used only in posting tree */
+	/* follows: list of tuple or ItemPointerData or PostingItem */
+} ginxlogSplit;
+
+#define	XLOG_GIN_VACUUM_PAGE	0x40
+
+typedef struct ginxlogVacuumPage {
+	RelFileNode     node;
+	BlockNumber		blkno;
+	OffsetNumber	nitem;
+	/* follows content of page */
+} ginxlogVacuumPage;
+
+#define XLOG_GIN_DELETE_PAGE	0x50 
+
+typedef struct ginxlogDeletePage {
+	RelFileNode		node;
+	BlockNumber		blkno;
+	BlockNumber		parentBlkno;
+	OffsetNumber	parentOffset;
+	BlockNumber		leftBlkno;
+	BlockNumber		rightLink;
+} ginxlogDeletePage;
+
+/* ginutil.c */
+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, uint32 *nentries);
+extern Datum* extractEntriesSU(GinState *ginstate, Datum value, uint32 *nentries);
+extern Page GinPageGetCopyPage( Page page );
+
+/* gininsert.c */
+extern Datum ginbuild(PG_FUNCTION_ARGS);
+extern Datum gininsert(PG_FUNCTION_ARGS);
+
+/* ginxlog.c */
+extern void gin_redo(XLogRecPtr lsn, XLogRecord *record);
+extern void gin_desc(StringInfo buf, uint8 xl_info, char *rec);
+extern void gin_xlog_startup(void);
+extern void gin_xlog_cleanup(void);
+
+/* ginbtree.c */
+
+typedef struct GinBtreeStack {
+	BlockNumber			blkno;
+	Buffer				buffer;
+	OffsetNumber 		off;
+	/* predictNumber contains prediction number of pages on current level */ 
+	uint32				predictNumber;
+	struct GinBtreeStack *parent;
+} GinBtreeStack;
+
+typedef struct GinBtreeData *GinBtree;
+
+typedef struct GinBtreeData {
+	/* search methods */
+	BlockNumber		(*findChildPage)(GinBtree, GinBtreeStack *);
+	bool			(*isMoveRight)(GinBtree, Page);
+	bool			(*findItem)(GinBtree, GinBtreeStack *);
+
+	/* insert methods */
+	OffsetNumber	(*findChildPtr)(GinBtree, Page, BlockNumber, OffsetNumber);
+	BlockNumber		(*getLeftMostPage)(GinBtree, Page);
+	bool			(*isEnoughSpace)(GinBtree, Buffer, OffsetNumber);
+	void			(*placeToPage)(GinBtree, Buffer, OffsetNumber, XLogRecData**);
+	Page			(*splitPage)(GinBtree, Buffer, Buffer, OffsetNumber, XLogRecData**);
+	void			(*fillRoot)(GinBtree, Buffer, Buffer, Buffer);
+
+	bool			searchMode;
+
+	Relation		index;
+	GinState		*ginstate;
+	bool			fullScan;
+	bool			isBuild;
+
+	BlockNumber		rightblkno;
+
+	/* Entry options */
+	Datum			entryValue;
+	IndexTuple		entry;
+	bool			isDelete;
+
+	/* Data (posting tree) option */
+	ItemPointerData	*items;
+	uint32			nitem;
+	uint32			curitem;
+
+	PostingItem		pitem;
+} GinBtreeData;
+
+extern GinBtreeStack* ginPrepareFindLeafPage(GinBtree btree, BlockNumber blkno);
+extern GinBtreeStack* ginFindLeafPage(GinBtree btree, GinBtreeStack *stack );
+extern void freeGinBtreeStack( GinBtreeStack *stack );
+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 void entryFillRoot(GinBtree btree, Buffer root, Buffer lbuf, Buffer rbuf);
+extern IndexTuple ginPageGetLinkItup(Buffer buf);
+
+/* gindatapage.c */
+extern int compareItemPointers( ItemPointer a, ItemPointer b );
+extern void  MergeItemPointers(
+				ItemPointerData *dst, 
+				ItemPointerData *a, uint32 na, 
+				ItemPointerData *b, uint32 nb
+			);
+
+extern void GinDataPageAddItem( Page page, void *data, OffsetNumber offset );
+extern void PageDeletePostingItem(Page page, OffsetNumber offset);
+
+typedef struct {
+	GinBtreeData	btree;
+	GinBtreeStack 	*stack;
+} GinPostingTreeScan;
+
+extern GinPostingTreeScan* prepareScanPostingTree( Relation index, 
+			BlockNumber rootBlkno, bool searchMode);
+extern void insertItemPointer(GinPostingTreeScan *gdi, 
+			ItemPointerData *items, uint32 nitem);
+extern Buffer scanBeginPostingTree( GinPostingTreeScan *gdi );
+extern void dataFillRoot(GinBtree btree, Buffer root, Buffer lbuf, Buffer rbuf);
+extern void prepareDataScan( GinBtree btree, Relation index);
+/* ginscan.c */
+
+typedef struct GinScanEntryData *GinScanEntry;
+
+typedef struct GinScanEntryData {
+	/* link to the equals entry in current scan key */
+	GinScanEntry	master; 
+
+	/* link to values reported to consistentFn,
+	   points to GinScanKey->entryRes[i]*/ 
+	bool			*pval; 
+
+	/* entry, got from extractQueryFn */ 
+	Datum			entry;
+
+	/* current ItemPointer to heap, its offset in buffer and buffer */
+	ItemPointerData		curItem;
+	OffsetNumber		offset;
+	Buffer				buffer;
+
+	/* in case of Posing list */
+	ItemPointerData		*list;
+	uint32				nlist;
+
+	bool				isFinished;
+	bool				reduceResult;
+	uint32				predictNumberResult;
+} GinScanEntryData;
+
+typedef struct GinScanKeyData {
+	/* Number of entries in query (got by extractQueryFn) */ 
+	uint32			nentries;
+
+	/* array of ItemPointer result, reported to consistentFn */
+	bool			*entryRes;
+
+	/* array of scans per entry */ 
+	GinScanEntry	scanEntry;
+
+	/* for calling consistentFn(GinScanKey->entryRes, strategy, query) */
+	StrategyNumber	strategy;
+	Datum			query;
+
+	ItemPointerData	curItem;
+	bool			firstCall;
+	bool			isFinished;
+} GinScanKeyData;
+
+typedef GinScanKeyData *GinScanKey; 
+
+typedef struct GinScanOpaqueData {
+	MemoryContext	tempCtx;
+	GinState 		ginstate;
+
+	GinScanKey		keys;
+	uint32			nkeys;
+
+	GinScanKey		markPos;
+} GinScanOpaqueData;
+
+typedef GinScanOpaqueData *GinScanOpaque;
+
+extern Datum ginbeginscan(PG_FUNCTION_ARGS);
+extern Datum ginendscan(PG_FUNCTION_ARGS);
+extern Datum ginrescan(PG_FUNCTION_ARGS);
+extern Datum ginmarkpos(PG_FUNCTION_ARGS);
+extern Datum ginrestrpos(PG_FUNCTION_ARGS);
+extern void newScanKey( IndexScanDesc scan );
+
+/* ginget.c */
+extern DLLIMPORT int	GinFuzzySearchLimit;
+
+#define ItemPointerSetMax(p)	ItemPointerSet( (p), (BlockNumber)0xffffffff, (OffsetNumber)0xffff ) 
+#define ItemPointerIsMax(p)	( ItemPointerGetBlockNumber(p) == (BlockNumber)0xffffffff && ItemPointerGetOffsetNumber(p) == (OffsetNumber)0xffff ) 
+#define ItemPointerSetMin(p)	ItemPointerSet( (p), (BlockNumber)0, (OffsetNumber)0) 
+#define ItemPointerIsMin(p)	( ItemPointerGetBlockNumber(p) == (BlockNumber)0 && ItemPointerGetOffsetNumber(p) == (OffsetNumber)0 ) 
+
+extern Datum gingetmulti(PG_FUNCTION_ARGS);
+extern Datum gingettuple(PG_FUNCTION_ARGS);
+
+/* ginvacuum.c */
+extern Datum ginbulkdelete(PG_FUNCTION_ARGS);
+extern Datum ginvacuumcleanup(PG_FUNCTION_ARGS);
+
+/* ginarrayproc.c */
+extern Datum ginarrayextract(PG_FUNCTION_ARGS);
+extern Datum ginarrayconsistent(PG_FUNCTION_ARGS);
+
+/* I'm not sure that is the best place */
+extern Datum arrayoverlap(PG_FUNCTION_ARGS);
+extern Datum arraycontains(PG_FUNCTION_ARGS);
+extern Datum arraycontained(PG_FUNCTION_ARGS);
+
+/* ginbulk.c */
+typedef struct {
+    Datum       value;
+	uint32      length;
+	uint32      number;
+	ItemPointerData *list;
+	bool		shouldSort;
+} EntryAccumulator;
+
+typedef struct {
+	GinState	*ginstate;
+    EntryAccumulator    *entries;
+	uint32      length;
+	uint32      number;
+	uint32      curget;
+	uint32      allocatedMemory;
+} BuildAccumulator;
+
+extern void ginInitBA(BuildAccumulator *accum);
+extern void ginInsertRecordBA( BuildAccumulator *accum, 
+				ItemPointer heapptr, Datum *entries, uint32 nentry );
+extern ItemPointerData* ginGetEntry(BuildAccumulator *accum, Datum *entry, uint32 *n);
+
+#endif
diff --git a/src/include/access/rmgr.h b/src/include/access/rmgr.h
index da6bc6968359419d8ef8852cdbea9114d46fb380..471b0cfb76475a6e9ffdba9e4de988bd88de55dc 100644
--- a/src/include/access/rmgr.h
+++ b/src/include/access/rmgr.h
@@ -3,7 +3,7 @@
  *
  * Resource managers definition
  *
- * $PostgreSQL: pgsql/src/include/access/rmgr.h,v 1.15 2005/11/07 17:36:46 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/access/rmgr.h,v 1.16 2006/05/02 11:28:55 teodor Exp $
  */
 #ifndef RMGR_H
 #define RMGR_H
@@ -23,6 +23,7 @@ typedef uint8 RmgrId;
 #define RM_HEAP_ID				10
 #define RM_BTREE_ID				11
 #define RM_HASH_ID				12
+#define RM_GIN_ID				13
 #define RM_GIST_ID				14
 #define RM_SEQ_ID				15
 #define RM_MAX_ID				RM_SEQ_ID
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 4c76aa70fa82ab23ec9467e52308a9bd0f359fc9..97cf51ef5452c97a10796519ba8849a16c51a445 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.328 2006/04/30 18:30:40 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.329 2006/05/02 11:28:55 teodor Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200604301
+#define CATALOG_VERSION_NO	200605021
 
 #endif
diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h
index c81409bbbfdac8e87eaa42e57b1221e3008f42f1..29d19147d2d4df669e46a667acfc914764d28e75 100644
--- a/src/include/catalog/pg_am.h
+++ b/src/include/catalog/pg_am.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_am.h,v 1.40 2006/03/05 15:58:54 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_am.h,v 1.41 2006/05/02 11:28:55 teodor Exp $
  *
  * NOTES
  *		the genbki.sh script reads this file and generates .bki
@@ -51,6 +51,7 @@ CATALOG(pg_am,2601)
 	bool		amoptionalkey;	/* can query omit key for the first column? */
 	bool		amindexnulls;	/* does AM support NULL index entries? */
 	bool		amconcurrent;	/* does AM support concurrent updates? */
+	bool		amclusterable;	/* does AM support cluster command? */
 	regproc		aminsert;		/* "insert this tuple" function */
 	regproc		ambeginscan;	/* "start new scan" function */
 	regproc		amgettuple;		/* "next valid tuple" function */
@@ -76,7 +77,7 @@ typedef FormData_pg_am *Form_pg_am;
  *		compiler constants for pg_am
  * ----------------
  */
-#define Natts_pg_am						21
+#define Natts_pg_am						22
 #define Anum_pg_am_amname				1
 #define Anum_pg_am_amstrategies			2
 #define Anum_pg_am_amsupport			3
@@ -86,32 +87,36 @@ typedef FormData_pg_am *Form_pg_am;
 #define Anum_pg_am_amoptionalkey		7
 #define Anum_pg_am_amindexnulls			8
 #define Anum_pg_am_amconcurrent			9
-#define Anum_pg_am_aminsert				10
-#define Anum_pg_am_ambeginscan			11
-#define Anum_pg_am_amgettuple			12
-#define Anum_pg_am_amgetmulti			13
-#define Anum_pg_am_amrescan				14
-#define Anum_pg_am_amendscan			15
-#define Anum_pg_am_ammarkpos			16
-#define Anum_pg_am_amrestrpos			17
-#define Anum_pg_am_ambuild				18
-#define Anum_pg_am_ambulkdelete			19
-#define Anum_pg_am_amvacuumcleanup		20
-#define Anum_pg_am_amcostestimate		21
+#define Anum_pg_am_amclusterable		10
+#define Anum_pg_am_aminsert				11
+#define Anum_pg_am_ambeginscan			12
+#define Anum_pg_am_amgettuple			13
+#define Anum_pg_am_amgetmulti			14
+#define Anum_pg_am_amrescan				15
+#define Anum_pg_am_amendscan			16
+#define Anum_pg_am_ammarkpos			17
+#define Anum_pg_am_amrestrpos			18
+#define Anum_pg_am_ambuild				19
+#define Anum_pg_am_ambulkdelete			20
+#define Anum_pg_am_amvacuumcleanup		21
+#define Anum_pg_am_amcostestimate		22
 
 /* ----------------
  *		initial contents of pg_am
  * ----------------
  */
 
-DATA(insert OID = 403 (  btree	5 1 1 t t t t t btinsert btbeginscan btgettuple btgetmulti btrescan btendscan btmarkpos btrestrpos btbuild btbulkdelete btvacuumcleanup btcostestimate ));
+DATA(insert OID = 403 (  btree	5 1 1 t t t t t t btinsert btbeginscan btgettuple btgetmulti btrescan btendscan btmarkpos btrestrpos btbuild btbulkdelete btvacuumcleanup btcostestimate ));
 DESCR("b-tree index access method");
 #define BTREE_AM_OID 403
-DATA(insert OID = 405 (  hash	1 1 0 f f f f t hashinsert hashbeginscan hashgettuple hashgetmulti hashrescan hashendscan hashmarkpos hashrestrpos hashbuild hashbulkdelete - hashcostestimate ));
+DATA(insert OID = 405 (  hash	1 1 0 f f f f t f hashinsert hashbeginscan hashgettuple hashgetmulti hashrescan hashendscan hashmarkpos hashrestrpos hashbuild hashbulkdelete - hashcostestimate ));
 DESCR("hash index access method");
 #define HASH_AM_OID 405
-DATA(insert OID = 783 (  gist	100 7 0 f t f f t gistinsert gistbeginscan gistgettuple gistgetmulti gistrescan gistendscan gistmarkpos gistrestrpos gistbuild gistbulkdelete gistvacuumcleanup gistcostestimate ));
+DATA(insert OID = 783 (  gist	100 7 0 f t f f t t gistinsert gistbeginscan gistgettuple gistgetmulti gistrescan gistendscan gistmarkpos gistrestrpos gistbuild gistbulkdelete gistvacuumcleanup gistcostestimate ));
 DESCR("GiST index access method");
 #define GIST_AM_OID 783
+DATA(insert OID = 2742 (  gin	100 4 0 f f f f t f gininsert ginbeginscan gingettuple gingetmulti ginrescan ginendscan ginmarkpos ginrestrpos ginbuild ginbulkdelete ginvacuumcleanup gincostestimate ));
+DESCR("GIN index access method");
+#define GIN_AM_OID 2742
 
 #endif   /* PG_AM_H */
diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h
index 26f409c0c48a1124c33a825f3a82e288d4cc9d36..bb72deef235ab029f39289e2288f15badce1016a 100644
--- a/src/include/catalog/pg_amop.h
+++ b/src/include/catalog/pg_amop.h
@@ -23,7 +23,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_amop.h,v 1.69 2006/03/05 15:58:54 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_amop.h,v 1.70 2006/05/02 11:28:55 teodor Exp $
  *
  * NOTES
  *	 the genbki.sh script reads this file and generates .bki
@@ -636,4 +636,18 @@ DATA(insert (	2595	0 10 t	1515 ));
 DATA(insert (	2595	0 11 t	1514 ));
 DATA(insert (	2595	0 12 t	2590 ));
 
+/*
+ * gin _int4_ops
+ */
+DATA(insert (	2745	0 1  f	2750 ));
+DATA(insert (	2745	0 2  f	2751 ));
+DATA(insert (	2745	0 3  t	2752 ));
+
+/*
+ * gin _text_ops
+ */
+DATA(insert (	2746	0 1  f	2750 ));
+DATA(insert (	2746	0 2  f	2751 ));
+DATA(insert (	2746	0 3  t	2752 ));
+
 #endif   /* PG_AMOP_H */
diff --git a/src/include/catalog/pg_amproc.h b/src/include/catalog/pg_amproc.h
index 2efb359edcb9c921d7be44d9075f26103ec3a19d..85467edd0cd2f823e23b6da829a8e25c683100a2 100644
--- a/src/include/catalog/pg_amproc.h
+++ b/src/include/catalog/pg_amproc.h
@@ -19,7 +19,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_amproc.h,v 1.56 2006/03/05 15:58:54 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_amproc.h,v 1.57 2006/05/02 11:28:55 teodor Exp $
  *
  * NOTES
  *	  the genbki.sh script reads this file and generates .bki
@@ -185,4 +185,14 @@ DATA(insert (	2595	0 5 2581 ));
 DATA(insert (	2595	0 6 2582 ));
 DATA(insert (	2595	0 7 2584 ));
 
+/* gin */
+DATA(insert (	2745	0 1  351 ));
+DATA(insert (	2745	0 2 2743 ));
+DATA(insert (	2745	0 3 2743 ));
+DATA(insert (	2745	0 4 2744 ));
+DATA(insert (	2746	0 1  360 ));
+DATA(insert (	2746	0 2 2743 ));
+DATA(insert (	2746	0 3 2743 ));
+DATA(insert (	2746	0 4 2744 ));
+
 #endif   /* PG_AMPROC_H */
diff --git a/src/include/catalog/pg_opclass.h b/src/include/catalog/pg_opclass.h
index e70633f84b819d56767674edc5ab28e78187b28f..4a5c5351ca34c5153bf3a3d941942eba60c2b3a5 100644
--- a/src/include/catalog/pg_opclass.h
+++ b/src/include/catalog/pg_opclass.h
@@ -27,7 +27,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_opclass.h,v 1.68 2006/03/05 15:58:54 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_opclass.h,v 1.69 2006/05/02 11:28:55 teodor Exp $
  *
  * NOTES
  *	  the genbki.sh script reads this file and generates .bki
@@ -176,5 +176,7 @@ DATA(insert OID = 2235 (	405		aclitem_ops		PGNSP PGUID 1033 t 0 ));
 DATA(insert OID = 2593 (	783		box_ops			PGNSP PGUID  603 t 0 ));
 DATA(insert OID = 2594 (	783		poly_ops		PGNSP PGUID  604 t 603 ));
 DATA(insert OID = 2595 (	783		circle_ops		PGNSP PGUID  718 t 603 ));
+DATA(insert OID = 2745 (	2742	_int4_ops		PGNSP PGUID  1007 t 23 ));
+DATA(insert OID = 2746 (	2742	_text_ops		PGNSP PGUID  1009 t 25 ));
 
 #endif   /* PG_OPCLASS_H */
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index af70c92a88ecf80f6c7de3966a51022be40ba8b5..a296bf3e41c321edafbcdebd28e4d2031a8b5972 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_operator.h,v 1.142 2006/03/05 15:58:54 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_operator.h,v 1.143 2006/05/02 11:28:55 teodor Exp $
  *
  * NOTES
  *	  the genbki.sh script reads this file and generates .bki
@@ -871,6 +871,11 @@ DATA(insert OID = 2577 (  "|>>"    PGNSP PGUID b f 604 604	16	 0	 0	 0	 0	 0	 0
 DATA(insert OID = 2589 (  "&<|"    PGNSP PGUID b f 718 718	16	 0	 0	 0	 0	 0	 0 circle_overbelow positionsel positionjoinsel ));
 DATA(insert OID = 2590 (  "|&>"    PGNSP PGUID b f 718 718	16	 0	 0	 0	 0	 0	 0 circle_overabove positionsel positionjoinsel ));
 
+/* overlap/contains/contained from arrays */
+DATA(insert OID = 2750 (  "&&"	   PGNSP PGUID b f 2277 2277	16 2750	 0	 0	 0	 0	 0 arrayoverlap areasel areajoinsel ));
+DATA(insert OID = 2751 (  "@"	   PGNSP PGUID b f 2277 2277	16 2752	 0	 0	 0	 0	 0 arraycontains contsel contjoinsel ));
+DATA(insert OID = 2752 (  "~"	   PGNSP PGUID b f 2277 2277	16 2751	 0	 0	 0	 0	 0 arraycontained contsel contjoinsel ));
+
 
 /*
  * function prototypes
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 43c87f36477ae81145ddf9e411e16480710e263b..c4e41ba9c6d31fea973825a3a9dc03bd4ff79afb 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.408 2006/04/26 22:33:17 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.409 2006/05/02 11:28:55 teodor Exp $
  *
  * NOTES
  *	  The script catalog/genbki.sh reads this file and generates .bki
@@ -3813,6 +3813,45 @@ DESCR("GiST support");
 DATA(insert OID = 2592 (  gist_circle_compress	PGNSP PGUID 12 f f t f i 1 2281 "2281" _null_ _null_ _null_ gist_circle_compress - _null_ ));
 DESCR("GiST support");
 
+/* GIN */
+DATA(insert OID = 2730 (  gingettuple	   PGNSP PGUID 12 f f t f v 2 16 "2281 2281" _null_ _null_ _null_  gingettuple - _null_ ));
+DESCR("gin(internal)");
+DATA(insert OID = 2731 (  gingetmulti	   PGNSP PGUID 12 f f t f v 4 16 "2281 2281 2281 2281" _null_ _null_ _null_  gingetmulti - _null_ ));
+DESCR("gin(internal)");
+DATA(insert OID = 2732 (  gininsert		   PGNSP PGUID 12 f f t f v 6 16 "2281 2281 2281 2281 2281 2281" _null_ _null_ _null_	gininsert - _null_ ));
+DESCR("gin(internal)");
+DATA(insert OID = 2733 (  ginbeginscan	   PGNSP PGUID 12 f f t f v 3 2281 "2281 2281 2281" _null_ _null_ _null_	ginbeginscan - _null_ ));
+DESCR("gin(internal)");
+DATA(insert OID = 2734 (  ginrescan		   PGNSP PGUID 12 f f t f v 2 2278 "2281 2281" _null_ _null_ _null_ ginrescan - _null_ ));
+DESCR("gin(internal)");
+DATA(insert OID = 2735 (  ginendscan	   PGNSP PGUID 12 f f t f v 1 2278 "2281" _null_ _null_ _null_	ginendscan - _null_ ));
+DESCR("gin(internal)");
+DATA(insert OID = 2736 (  ginmarkpos	   PGNSP PGUID 12 f f t f v 1 2278 "2281" _null_ _null_ _null_	ginmarkpos - _null_ ));
+DESCR("gin(internal)");
+DATA(insert OID = 2737 (  ginrestrpos	   PGNSP PGUID 12 f f t f v 1 2278 "2281" _null_ _null_ _null_	ginrestrpos - _null_ ));
+DESCR("gin(internal)");
+DATA(insert OID = 2738 (  ginbuild		   PGNSP PGUID 12 f f t f v 3 2278 "2281 2281 2281" _null_ _null_ _null_ ginbuild - _null_ ));
+DESCR("gin(internal)");
+DATA(insert OID = 2739 (  ginbulkdelete    PGNSP PGUID 12 f f t f v 3 2281 "2281 2281 2281" _null_ _null_ _null_ ginbulkdelete - _null_ ));
+DESCR("gin(internal)");
+DATA(insert OID = 2740 (  ginvacuumcleanup PGNSP PGUID 12 f f t f v 3 2281 "2281 2281 2281" _null_ _null_ _null_ ginvacuumcleanup - _null_ ));
+DESCR("gin(internal)");
+DATA(insert OID = 2741 (  gincostestimate  PGNSP PGUID 12 f f t f v 7 2278 "2281 2281 2281 2281 2281 2281 2281" _null_ _null_ _null_  gincostestimate - _null_ ));
+DESCR("gin(internal)");
+
+/* GIN array support */
+DATA(insert OID = 2743 (  ginarrayextract    PGNSP PGUID 12 f f t f i 2 2281 "2277 2281" _null_ _null_ _null_	ginarrayextract - _null_ ));
+DESCR("GIN array support");
+DATA(insert OID = 2744 (  ginarrayconsistent PGNSP PGUID 12 f f t f i 3 16 "2281 21 2281" _null_ _null_ _null_	ginarrayconsistent - _null_ ));
+DESCR("GIN array support");
+
+/* overlap/contains/contained */
+DATA(insert OID = 2747 (  arrayoverlap		   PGNSP PGUID 12 f f t f i 2 16 "2277 2277" _null_ _null_ _null_ arrayoverlap - _null_ ));
+DESCR("anyarray overlap");
+DATA(insert OID = 2748 (  arraycontains		   PGNSP PGUID 12 f f t f i 2 16 "2277 2277" _null_ _null_ _null_ arraycontains - _null_ ));
+DESCR("anyarray contains");
+DATA(insert OID = 2749 (  arraycontained	   PGNSP PGUID 12 f f t f i 2 16 "2277 2277" _null_ _null_ _null_ arraycontained - _null_ ));
+DESCR("anyarray contained");
 
 /*
  * Symbolic values for provolatile column: these indicate whether the result
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
index 723ae07092de66d87344313bc23fc1b82da83661..35fda0d9221700c40cd80a4f433bf19fab9e45d3 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/selfuncs.h,v 1.32 2006/04/27 17:52:40 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/selfuncs.h,v 1.33 2006/05/02 11:28:55 teodor Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -168,5 +168,6 @@ extern Selectivity estimate_hash_bucketsize(PlannerInfo *root, Node *hashkey,
 extern Datum btcostestimate(PG_FUNCTION_ARGS);
 extern Datum hashcostestimate(PG_FUNCTION_ARGS);
 extern Datum gistcostestimate(PG_FUNCTION_ARGS);
+extern Datum gincostestimate(PG_FUNCTION_ARGS);
 
 #endif   /* SELFUNCS_H */
diff --git a/src/test/regress/data/array.data b/src/test/regress/data/array.data
new file mode 100644
index 0000000000000000000000000000000000000000..12a420fe2227380682885aa9d48259df611aac13
--- /dev/null
+++ b/src/test/regress/data/array.data
@@ -0,0 +1,100 @@
+1	{92,75,71,52,64,83}	{AAAAAAAA44066,AAAAAA1059,AAAAAAAAAAA176,AAAAAAA48038}
+2	{3,6}	{AAAAAA98232,AAAAAAAA79710,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAAAAAAA55798,AAAAAAAAA12793}
+3	{37,64,95,43,3,41,13,30,11,43}	{AAAAAAAAAA48845,AAAAA75968,AAAAA95309,AAA54451,AAAAAAAAAA22292,AAAAAAA99836,A96617,AA17009,AAAAAAAAAAAAAA95246}
+4	{71,39,99,55,33,75,45}	{AAAAAAAAA53663,AAAAAAAAAAAAAAA67062,AAAAAAAAAA64777,AAA99043,AAAAAAAAAAAAAAAAAAA91804,39557}
+5	{50,42,77,50,4}	{AAAAAAAAAAAAAAAAA26540,AAAAAAA79710,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA176,AAAAA95309,AAAAAAAAAAA46154,AAAAAA66777,AAAAAAAAA27249,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA70104}
+6	{39,35,5,94,17,92,60,32}	{AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+7	{12,51,88,64,8}	{AAAAAAAAAAAAAAAAAA12591,AAAAAAAAAAAAAAAAA50407,AAAAAAAAAAAA67946}
+8	{60,84}	{AAAAAAA81898,AAAAAA1059,AAAAAAAAAAAA81511,AAAAA961,AAAAAAAAAAAAAAAA31334,AAAAA64741,AA6416,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAA50407}
+9	{56,52,35,27,80,44,81,22}	{AAAAAAAAAAAAAAA73034,AAAAAAAAAAAAA7929,AAAAAAA66161,AA88409,39557,A27153,AAAAAAAA9523,AAAAAAAAAAA99000}
+10	{71,5,45}	{AAAAAAAAAAA21658,AAAAAAAAAAAA21089,AAA54451,AAAAAAAAAAAAAAAAAA54141,AAAAAAAAAAAAAA28620,AAAAAAAAAAA21658,AAAAAAAAAAA74076,AAAAAAAAA27249}
+11	{41,86,74,48,22,74,47,50}	{AAAAAAAA9523,AAAAAAAAAAAA37562,AAAAAAAAAAAAAAAA14047,AAAAAAAAAAA46154,AAAA41702,AAAAAAAAAAAAAAAAA764,AAAAA62737,39557}
+12	{17,99,18,52,91,72,0,43,96,23}	{AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+13	{3,52,34,23}	{AAAAAA98232,AAAA49534,AAAAAAAAAAA21658}
+14	{78,57,19}	{AAAA8857,AAAAAAAAAAAAAAA73034,AAAAAAAA81587,AAAAAAAAAAAAAAA68526,AAAAA75968,AAAAAAAAAAAAAA65909,AAAAAAAAA10012,AAAAAAAAAAAAAA65909}
+15	{17,14,16,63,67}	{AA6416,AAAAAAAAAA646,AAAAA95309}
+16	{14,63,85,11}	{AAAAAA66777}
+17	{7,10,81,85}	{AAAAAA43678,AAAAAAA12144,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAAAAA15356}
+18	{1}	{AAAAAAAAAAA33576,AAAAA95309,64261,AAA59323,AAAAAAAAAAAAAA95246,55847,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAAAA64374}
+19	{52,82,17,74,23,46,69,51,75}	{AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+20	{72,89,70,51,54,37,8,49,79}	{AAAAAA58494}
+21	{2,8,65,10,5,79,43}	{AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAAAAA91804,AAAAA64669,AAAAAAAAAAAAAAAA1443,AAAAAAAAAAAAAAAA23657,AAAAA12179,AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAA31334,AAAAAAAAAAAAAAAA41303,AAAAAAAAAAAAAAAAAAA85420}
+22	{11,6,56,62,53,30}	{AAAAAAAA72908}
+23	{40,90,5,38,72,40,30,10,43,55}	{A6053,AAAAAAAAAAA6119,AA44673,AAAAAAAAAAAAAAAAA764,AA17009,AAAAA17383,AAAAA70514,AAAAA33250,AAAAA95309,AAAAAAAAAAAA37562}
+24	{94,61,99,35,48}	{AAAAAAAAAAA50956,AAAAAAAAAAA15165,AAAA85070,AAAAAAAAAAAAAAA36627,AAAAA961,AAAAAAAAAA55219}
+25	{31,1,10,11,27,79,38}	{AAAAAAAAAAAAAAAAAA59334,45449}
+26	{71,10,9,69,75}	{47735,AAAAAAA21462,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA91804,AAAAAAAAA72121,AAAAAAAAAAAAAAAAAAA1205,AAAAA41597,AAAA8857,AAAAAAAAAAAAAAAAAAA15356,AA17009}
+27	{94}	{AA6416,A6053,AAAAAAA21462,AAAAAAA57334,AAAAAAAAAAAAAAAAAA12591,AA88409,AAAAAAAAAAAAA70254}
+28	{14,33,6,34,14}	{AAAAAAAAAAAAAAA13198,AAAAAAAA69452,AAAAAAAAAAA82945,AAAAAAA12144,AAAAAAAAA72121,AAAAAAAAAA18601}
+29	{39,21}	{AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA38885,AAAA85070,AAAAAAAAAAAAAAAAAAA70104,AAAAA66674,AAAAAAAAAAAAA62007,AAAAAAAA69452,AAAAAAA1242,AAAAAAAAAAAAAAAA1729,AAAA35194}
+30	{26,81,47,91,34}	{AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240}
+31	{80,24,18,21,54}	{AAAAAAAAAAAAAAA13198,AAAAAAAAAAAAAAAAAAA70415,A27153,AAAAAAAAA53663,AAAAAAAAAAAAAAAAA50407,A68938}
+32	{58,79,82,80,67,75,98,10,41}	{AAAAAAAAAAAAAAAAAA61286,AAA54451,AAAAAAAAAAAAAAAAAAA87527,A96617,51533}
+33	{74,73}	{A85417,AAAAAAA56483,AAAAA17383,AAAAAAAAAAAAA62159,AAAAAAAAAAAA52814,AAAAAAAAAAAAA85723,AAAAAAAAAAAAAAAAAA55796}
+34	{70,45}	{AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAA28620,AAAAAAAAAA55219,AAAAAAAA23648,AAAAAAAAAA22292,AAAAAAA1242}
+35	{23,40}	{AAAAAAAAAAAA52814,AAAA48949,AAAAAAAAA34727,AAAA8857,AAAAAAAAAAAAAAAAAAA62179,AAAAAAAAAAAAAAA68526,AAAAAAA99836,AAAAAAAA50094,AAAA91194,AAAAAAAAAAAAA73084}
+36	{79,82,14,52,30,5,79}	{AAAAAAAAA53663,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA89194,AA88409,AAAAAAAAAAAAAAA81326,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAA33598}
+37	{53,11,81,39,3,78,58,64,74}	{AAAAAAAAAAAAAAAAAAA17075,AAAAAAA66161,AAAAAAAA23648,AAAAAAAAAAAAAA10611}
+38	{59,5,4,95,28}	{AAAAAAAAAAA82945,A96617,47735,AAAAA12179,AAAAA64669,AAAAAA99807,AA74433,AAAAAAAAAAAAAAAAA59387}
+39	{82,43,99,16,74}	{AAAAAAAAAAAAAAA67062,AAAAAAA57334,AAAAAAAAAAAAAA65909,A27153,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAA64777,AAAAAAAAAAAA81511,AAAAAAAAAAAAAA65909,AAAAAAAAAAAAAA28620}
+40	{34}	{AAAAAAAAAAAAAA10611,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAA31334,AAAAA70466,AAAAAAAA81587,AAAAAAA74623}
+41	{19,26,63,12,93,73,27,94}	{AAAAAAA79710,AAAAAAAAAA55219,AAAA41702,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAAAAA63050,AAAAAAA99836,AAAAAAAAAAAAAA8666}
+42	{15,76,82,75,8,91}	{AAAAAAAAAAA176,AAAAAA38063,45449,AAAAAA54032,AAAAAAA81898,AA6416,AAAAAAAAAAAAAAAAAAA62179,45449,AAAAA60038,AAAAAAAA81587}
+43	{39,87,91,97,79,28}	{AAAAAAAAAAA74076,A96617,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAAAAA55796,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAA67946}
+44	{40,58,68,29,54}	{AAAAAAA81898,AAAAAA66777,AAAAAA98232}
+45	{99,45}	{AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+46	{53,24}	{AAAAAAAAAAA53908,AAAAAA54032,AAAAA17383,AAAA48949,AAAAAAAAAA18601,AAAAA64669,45449,AAAAAAAAAAA98051,AAAAAAAAAAAAAAAAAA71621}
+47	{98,23,64,12,75,61}	{AAA59323,AAAAA95309,AAAAAAAAAAAAAAAA31334,AAAAAAAAA27249,AAAAA17383,AAAAAAAAAAAA37562,AAAAAA1059,A84822,55847,AAAAA70466}
+48	{76,14}	{AAAAAAAAAAAAA59671,AAAAAAAAAAAAAAAAAAA91804,AAAAAA66777,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAA73084,AAAAAAA79710,AAAAAAAAAAAAAAA40402,AAAAAAAAAAAAAAAAAAA65037}
+49	{56,5,54,37,49}	{AA21643,AAAAAAAAAAA92631,AAAAAAAA81587}
+50	{20,12,37,64,93}	{AAAAAAAAAA5483,AAAAAAAAAAAAAAAAAAA1205,AA6416,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAAAA47955}
+51	{47}	{AAAAAAAAAAAAAA96505,AAAAAAAAAAAAAAAAAA36842,AAAAA95309,AAAAAAAA81587,AA6416,AAAA91194,AAAAAA58494,AAAAAA1059,AAAAAAAA69452}
+52	{89,0}	{AAAAAAAAAAAAAAAAAA47955,AAAAAAA48038,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAA73084,AAAAA70466,AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA46154,AA66862}
+53	{38,17}	{AAAAAAAAAAA21658}
+54	{70,47}	{AAAAAAAAAAAAAAAAAA54141,AAAAA40681,AAAAAAA48038,AAAAAAAAAAAAAAAA29150,AAAAA41597,AAAAAAAAAAAAAAAAAA59334,AA15322}
+55	{47,79,47,64,72,25,71,24,93}	{AAAAAAAAAAAAAAAAAA55796,AAAAA62737}
+56	{33,7,60,54,93,90,77,85,39}	{AAAAAAAAAAAAAAAAAA32918,AA42406}
+57	{23,45,10,42,36,21,9,96}	{AAAAAAAAAAAAAAAAAAA70415}
+58	{92}	{AAAAAAAAAAAAAAAA98414,AAAAAAAA23648,AAAAAAAAAAAAAAAAAA55796,AA25381,AAAAAAAAAAA6119}
+59	{9,69,46,77}	{39557,AAAAAAA89932,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAAAAAA26540,AAA20874,AA6416,AAAAAAAAAAAAAAAAAA47955}
+60	{62,2,59,38,89}	{AAAAAAA89932,AAAAAAAAAAAAAAAAAAA15356,AA99927,AA17009,AAAAAAAAAAAAAAA35875}
+61	{72,2,44,95,54,54,13}	{AAAAAAAAAAAAAAAAAAA91804}
+62	{83,72,29,73}	{AAAAAAAAAAAAA15097,AAAA8857,AAAAAAAAAAAA35809,AAAAAAAAAAAA52814,AAAAAAAAAAAAAAAAAAA38885,AAAAAAAAAAAAAAAAAA24183,AAAAAA43678,A96617}
+63	{11,4,61,87}	{AAAAAAAAA27249,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAA13198,AAA20874,39557,51533,AAAAAAAAAAA53908,AAAAAAAAAAAAAA96505,AAAAAAAA78938}
+64	{26,19,34,24,81,78}	{A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240}
+65	{61,5,76,59,17}	{AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+66	{31,23,70,52,4,33,48,25}	{AAAAAAAAAAAAAAAAA69675,AAAAAAAA50094,AAAAAAAAAAA92631,AAAA35194,39557,AAAAAAA99836}
+67	{31,94,7,10}	{AAAAAA38063,A96617,AAAA35194,AAAAAAAAAAAA67946}
+68	{90,43,38}	{AA75092,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAA92631,AAAAAAAAA10012,AAAAAAAAAAAAA7929,AA21643}
+69	{67,35,99,85,72,86,44}	{AAAAAAAAAAAAAAAAAAA1205,AAAAAAAA50094,AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAAAAAAA47955}
+70	{56,70,83}	{AAAA41702,AAAAAAAAAAA82945,AA21643,AAAAAAAAAAA99000,A27153,AA25381,AAAAAAAAAAAAAA96505,AAAAAAA1242}
+71	{74,26}	{AAAAAAAAAAA50956,AA74433,AAAAAAA21462,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAA70254,AAAAAAAAAA43419,39557}
+72	{22,1,16,78,20,91,83}	{47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+73	{88,25,96,78,65,15,29,19}	{AAA54451,AAAAAAAAA27249,AAAAAAA9228,AAAAAAAAAAAAAAA67062,AAAAAAAAAAAAAAAAAAA70415,AAAAA17383,AAAAAAAAAAAAAAAA33598}
+74	{32}	{AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+75	{12,96,83,24,71,89,55}	{AAAA48949,AAAAAAAA29716,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAA29150,AAA28075,AAAAAAAAAAAAAAAAA43052}
+76	{92,55,10,7}	{AAAAAAAAAAAAAAA67062}
+77	{97,15,32,17,55,59,18,37,50,39}	{AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+78	{55,89,44,84,34}	{AAAAAAAAAAA6119,AAAAAAAAAAAAAA8666,AA99927,AA42406,AAAAAAA81898,AAAAAAA9228,AAAAAAAAAAA92631,AA21643,AAAAAAAAAAAAAA28620}
+79	{45}	{AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+80	{74,89,44,80,0}	{AAAA35194,AAAAAAAA79710,AAA20874,AAAAAAAAAAAAAAAAAAA70104,AAAAAAAAAAAAA73084,AAAAAAA57334,AAAAAAA9228,AAAAAAAAAAAAA62007}
+81	{63,77,54,48,61,53,97}	{AAAAAAAAAAAAAAA81326,AAAAAAAAAA22292,AA25381,AAAAAAAAAAA74076,AAAAAAA81898,AAAAAAAAA72121}
+82	{34,60,4,79,78,16,86,89,42,50}	{AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104}
+83	{14,10}	{AAAAAAAAAA22292,AAAAAAAAAAAAA70254,AAAAAAAAAAA6119}
+84	{11,83,35,13,96,94}	{AAAAA95309,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAAA24183}
+85	{39,60}	{AAAAAAAAAAAAAAAA55798,AAAAAAAAAA22292,AAAAAAA66161,AAAAAAA21462,AAAAAAAAAAAAAAAAAA12591,55847,AAAAAA98232,AAAAAAAAAAA46154}
+86	{33,81,72,74,45,36,82}	{AAAAAAAA81587,AAAAAAAAAAAAAA96505,45449,AAAA80176}
+87	{57,27,50,12,97,68}	{AAAAAAAAAAAAAAAAA26540,AAAAAAAAA10012,AAAAAAAAAAAA35809,AAAAAAAAAAAAAAAA29150,AAAAAAAAAAA82945,AAAAAA66777,31228,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAA96505}
+88	{41,90,77,24,6,24}	{AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433}
+89	{40,32,17,6,30,88}	{AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+90	{88,75}	{AAAAA60038,AAAAAAAA23648,AAAAAAAAAAA99000,AAAA41702,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAA68526}
+91	{78}	{AAAAAAAAAAAAA62007,AAA99043}
+92	{85,63,49,45}	{AAAAAAA89932,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA21089}
+93	{11}	{AAAAAAAAAAA176,AAAAAAAAAAAAAA8666,AAAAAAAAAAAAAAA453,AAAAAAAAAAAAA85723,A68938,AAAAAAAAAAAAA9821,AAAAAAA48038,AAAAAAAAAAAAAAAAA59387,AA99927,AAAAA17383}
+94	{98,9,85,62,88,91,60,61,38,86}	{AAAAAAAA81587,AAAAA17383,AAAAAAAA81587}
+95	{47,77}	{AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA74076,AAAAAAAAAA18107,AAAAA40681,AAAAAAAAAAAAAAA35875,AAAAA60038,AAAAAAA56483}
+96	{23,97,43}	{AAAAAAAAAA646,A87088}
+97	{54,2,86,65}	{47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643}
+98	{38,34,32,89}	{AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+99	{37,86}	{AAAAAAAAAAAAAAAAAA32918,AAAAA70514,AAAAAAAAA10012,AAAAAAAAAAAAAAAAA59387,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA15356}
+100	{85,32,57,39,49,84,32,3,30}	{AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 17d073d37c7356f40942b28b65c6536fe7a24f89..561e4b034083c172773057ff4679ac4666aae300 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -302,6 +302,144 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
  {0,1,2,3}
 (1 row)
 
+SELECT * FROM array_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_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_op_test WHERE i @ '{17}' ORDER BY seqno;
+ seqno |                i                |                                                                 t                                                                  
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+     6 | {39,35,5,94,17,92,60,32}        | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+    12 | {17,99,18,52,91,72,0,43,96,23}  | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+    15 | {17,14,16,63,67}                | {AA6416,AAAAAAAAAA646,AAAAA95309}
+    19 | {52,82,17,74,23,46,69,51,75}    | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+    53 | {38,17}                         | {AAAAAAAAAAA21658}
+    65 | {61,5,76,59,17}                 | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+    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}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
+ seqno |                i                |                                                                 t                                                                  
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+     6 | {39,35,5,94,17,92,60,32}        | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+    12 | {17,99,18,52,91,72,0,43,96,23}  | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+    15 | {17,14,16,63,67}                | {AA6416,AAAAAAAAAA646,AAAAA95309}
+    19 | {52,82,17,74,23,46,69,51,75}    | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+    53 | {38,17}                         | {AAAAAAAAAAA21658}
+    65 | {61,5,76,59,17}                 | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+    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}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @ '{32,17}' ORDER BY seqno;
+ seqno |                i                |                                                                 t                                                                  
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+     6 | {39,35,5,94,17,92,60,32}        | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+    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}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
+ seqno |                i                |                                                                 t                                                                  
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+     6 | {39,35,5,94,17,92,60,32}        | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+    12 | {17,99,18,52,91,72,0,43,96,23}  | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+    15 | {17,14,16,63,67}                | {AA6416,AAAAAAAAAA646,AAAAA95309}
+    19 | {52,82,17,74,23,46,69,51,75}    | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+    53 | {38,17}                         | {AAAAAAAAAAA21658}
+    65 | {61,5,76,59,17}                 | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+    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}
+(11 rows)
+
+SELECT * FROM array_op_test WHERE i ~ '{38,34,32,89}' ORDER BY seqno;
+ seqno |       i       |                                                             t                                                              
+-------+---------------+----------------------------------------------------------------------------------------------------------------------------
+    40 | {34}          | {AAAAAAAAAAAAAA10611,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAA31334,AAAAA70466,AAAAAAAA81587,AAAAAAA74623}
+    74 | {32}          | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+    98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE t @ '{AAAAAAAA72908}' ORDER BY seqno;
+ seqno |           i           |                                                                     t                                                                      
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+    22 | {11,6,56,62,53,30}    | {AAAAAAAA72908}
+    45 | {99,45}               | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+    72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+    79 | {45}                  | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
+ seqno |           i           |                                                                     t                                                                      
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+    22 | {11,6,56,62,53,30}    | {AAAAAAAA72908}
+    45 | {99,45}               | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+    72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+    79 | {45}                  | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE t @ '{AAAAAAAAAA646}' ORDER BY seqno;
+ seqno |        i         |                                 t                                  
+-------+------------------+--------------------------------------------------------------------
+    15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+    79 | {45}             | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+    96 | {23,97,43}       | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
+ seqno |        i         |                                 t                                  
+-------+------------------+--------------------------------------------------------------------
+    15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+    79 | {45}             | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+    96 | {23,97,43}       | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE t @ '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
+ seqno |  i   |                                 t                                  
+-------+------+--------------------------------------------------------------------
+    79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(1 row)
+
+SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
+ seqno |           i           |                                                                     t                                                                      
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+    15 | {17,14,16,63,67}      | {AA6416,AAAAAAAAAA646,AAAAA95309}
+    22 | {11,6,56,62,53,30}    | {AAAAAAAA72908}
+    45 | {99,45}               | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+    72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+    79 | {45}                  | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+    96 | {23,97,43}            | {AAAAAAAAAA646,A87088}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE t ~ '{AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}' ORDER BY seqno;
+ seqno |         i          |                                                     t                                                     
+-------+--------------------+-----------------------------------------------------------------------------------------------------------
+    22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+    45 | {99,45}            | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+(2 rows)
+
 -- array casts
 SELECT ARRAY[1,2,3]::text[]::int[]::float8[] AS "{1,2,3}";
  {1,2,3} 
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index 13aca04270bf5888067b33d410f139e1a1da9680..a07dc64a65e627228bf23c376780b75d449c85af 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -154,6 +154,155 @@ SELECT count(*) FROM gcircle_tbl WHERE f1 && '<(500,500),500>'::circle;
      2
 (1 row)
 
+RESET enable_seqscan;
+RESET enable_indexscan;
+RESET enable_bitmapscan;
+--
+-- GIN over int[]
+--
+SET enable_seqscan = OFF;
+SET enable_indexscan = ON;
+SET enable_bitmapscan = ON;
+CREATE INDEX intarrayidx ON array_index_op_test USING gin (i);
+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 i @ '{17}' ORDER BY seqno;
+ seqno |                i                |                                                                 t                                                                  
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+     6 | {39,35,5,94,17,92,60,32}        | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+    12 | {17,99,18,52,91,72,0,43,96,23}  | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+    15 | {17,14,16,63,67}                | {AA6416,AAAAAAAAAA646,AAAAA95309}
+    19 | {52,82,17,74,23,46,69,51,75}    | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+    53 | {38,17}                         | {AAAAAAAAAAA21658}
+    65 | {61,5,76,59,17}                 | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+    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}
+(8 rows)
+
+SELECT * FROM array_index_op_test WHERE i && '{17}' ORDER BY seqno;
+ seqno |                i                |                                                                 t                                                                  
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+     6 | {39,35,5,94,17,92,60,32}        | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+    12 | {17,99,18,52,91,72,0,43,96,23}  | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+    15 | {17,14,16,63,67}                | {AA6416,AAAAAAAAAA646,AAAAA95309}
+    19 | {52,82,17,74,23,46,69,51,75}    | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+    53 | {38,17}                         | {AAAAAAAAAAA21658}
+    65 | {61,5,76,59,17}                 | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+    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}
+(8 rows)
+
+SELECT * FROM array_index_op_test WHERE i @ '{32,17}' ORDER BY seqno;
+ seqno |                i                |                                                                 t                                                                  
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+     6 | {39,35,5,94,17,92,60,32}        | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+    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}
+(3 rows)
+
+SELECT * FROM array_index_op_test WHERE i && '{32,17}' ORDER BY seqno;
+ seqno |                i                |                                                                 t                                                                  
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+     6 | {39,35,5,94,17,92,60,32}        | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+    12 | {17,99,18,52,91,72,0,43,96,23}  | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+    15 | {17,14,16,63,67}                | {AA6416,AAAAAAAAAA646,AAAAA95309}
+    19 | {52,82,17,74,23,46,69,51,75}    | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+    53 | {38,17}                         | {AAAAAAAAAAA21658}
+    65 | {61,5,76,59,17}                 | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+    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}
+(11 rows)
+
+SELECT * FROM array_index_op_test WHERE i ~ '{38,34,32,89}' ORDER BY seqno;
+ seqno |       i       |                                                             t                                                              
+-------+---------------+----------------------------------------------------------------------------------------------------------------------------
+    40 | {34}          | {AAAAAAAAAAAAAA10611,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAA31334,AAAAA70466,AAAAAAAA81587,AAAAAAA74623}
+    74 | {32}          | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+    98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+(3 rows)
+
+CREATE INDEX textarrayidx ON array_index_op_test USING gin (t);
+SELECT * FROM array_index_op_test WHERE t @ '{AAAAAAAA72908}' ORDER BY seqno;
+ seqno |           i           |                                                                     t                                                                      
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+    22 | {11,6,56,62,53,30}    | {AAAAAAAA72908}
+    45 | {99,45}               | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+    72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+    79 | {45}                  | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_index_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
+ seqno |           i           |                                                                     t                                                                      
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+    22 | {11,6,56,62,53,30}    | {AAAAAAAA72908}
+    45 | {99,45}               | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+    72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+    79 | {45}                  | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_index_op_test WHERE t @ '{AAAAAAAAAA646}' ORDER BY seqno;
+ seqno |        i         |                                 t                                  
+-------+------------------+--------------------------------------------------------------------
+    15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+    79 | {45}             | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+    96 | {23,97,43}       | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_index_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
+ seqno |        i         |                                 t                                  
+-------+------------------+--------------------------------------------------------------------
+    15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+    79 | {45}             | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+    96 | {23,97,43}       | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_index_op_test WHERE t @ '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
+ seqno |  i   |                                 t                                  
+-------+------+--------------------------------------------------------------------
+    79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(1 row)
+
+SELECT * FROM array_index_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
+ seqno |           i           |                                                                     t                                                                      
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+    15 | {17,14,16,63,67}      | {AA6416,AAAAAAAAAA646,AAAAA95309}
+    22 | {11,6,56,62,53,30}    | {AAAAAAAA72908}
+    45 | {99,45}               | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+    72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+    79 | {45}                  | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+    96 | {23,97,43}            | {AAAAAAAAAA646,A87088}
+(6 rows)
+
+SELECT * FROM array_index_op_test WHERE t ~ '{AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}' ORDER BY seqno;
+ seqno |         i          |                                                     t                                                     
+-------+--------------------+-----------------------------------------------------------------------------------------------------------
+    22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+    45 | {99,45}            | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+(2 rows)
+
 RESET enable_seqscan;
 RESET enable_indexscan;
 RESET enable_bitmapscan;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 448be93519b776a2fa7e31dcb12a5fe6d9986152..7a610e40ce7c7b24b9cfcc39c722d7f51695d1a1 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -186,3 +186,13 @@ CREATE TABLE bt_f8_heap (
 	seqno 		float8, 
 	random 		int4
 );
+CREATE TABLE array_op_test (
+	seqno		int4,
+	i			int4[],
+	t			text[]
+);
+CREATE TABLE array_index_op_test (
+	seqno		int4,
+	i			int4[],
+	t			text[]
+);
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index f367d0a5526ac775906f21054249b1f8aa5feae7..329905fb4d751c8455c8043f537401660292de4b 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -21,7 +21,12 @@ create function binary_coercible(oid, oid) returns bool as
 'SELECT ($1 = $2) OR
  EXISTS(select 1 from pg_cast where
         castsource = $1 and casttarget = $2 and
-        castfunc = 0 and castcontext = ''i'')'
+        castfunc = 0 and castcontext = ''i'') OR
+ ( EXISTS(select 1 from pg_type source where
+		source.oid = $1 and source.typelem != 0 )
+	AND
+   EXISTS(select 1 from pg_type target where
+		target.oid = $2 and target.typname = ''anyarray'' ) )'
 language sql;
 -- This one ignores castcontext, so it considers only physical equivalence
 -- and not whether the coercion can be invoked implicitly.
@@ -768,12 +773,12 @@ WHERE p1.amopclaid = p3.oid AND p3.opcamid = p2.oid AND
 -- Detect missing pg_amop entries: should have as many strategy operators
 -- as AM expects for each opclass for the AM.  When nondefault subtypes are
 -- present, enforce condition separately for each subtype.
--- We have to exclude GiST, unfortunately, since it hasn't got any fixed
+-- We have to exclude GiST and GIN, unfortunately, since its havn't got any fixed
 -- requirements about strategy operators.
 SELECT p1.oid, p1.amname, p2.oid, p2.opcname, p3.amopsubtype
 FROM pg_am AS p1, pg_opclass AS p2, pg_amop AS p3
 WHERE p2.opcamid = p1.oid AND p3.amopclaid = p2.oid AND
-    p1.amname != 'gist' AND
+    p1.amname != 'gist' AND p1.amname != 'gin' AND
     p1.amstrategies != (SELECT count(*) FROM pg_amop AS p4
                         WHERE p4.amopclaid = p2.oid AND
                               p4.amopsubtype = p3.amopsubtype);
@@ -825,7 +830,10 @@ ORDER BY 1, 2, 3;
      783 |           10 | <<|
      783 |           11 | |>>
      783 |           12 | |&>
-(24 rows)
+    2742 |            1 | &&
+    2742 |            2 | @
+    2742 |            3 | ~
+(27 rows)
 
 -- Check that all operators linked to by opclass entries have selectivity
 -- estimators.  This is not absolutely required, but it seems a reasonable
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 10edcb3e8c4561197ef7b9579348f1e40c356f6a..d1b9e21530ed8ddb5061384ece32441b027b8ab1 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -10,6 +10,7 @@ SELECT relname, relhasindex
    ORDER BY relname;
        relname       | relhasindex 
 ---------------------+-------------
+ array_index_op_test | t
  bt_f8_heap          | t
  bt_i4_heap          | t
  bt_name_heap        | t
@@ -69,7 +70,7 @@ SELECT relname, relhasindex
  shighway            | t
  tenk1               | t
  tenk2               | t
-(59 rows)
+(60 rows)
 
 --
 -- another sanity check: every system catalog that has OIDs should have
diff --git a/src/test/regress/input/copy.source b/src/test/regress/input/copy.source
index 58fe2a0b45d9d39dde0d2d0f05852bac5d76f33e..326f1b84b622e68f4f6aad9d89a1307fb97b3c04 100644
--- a/src/test/regress/input/copy.source
+++ b/src/test/regress/input/copy.source
@@ -54,6 +54,10 @@ COPY bt_txt_heap FROM '@abs_srcdir@/data/desc.data';
 
 COPY bt_f8_heap FROM '@abs_srcdir@/data/hash.data';
 
+COPY array_op_test FROM '@abs_srcdir@/data/array.data';
+
+COPY array_index_op_test FROM '@abs_srcdir@/data/array.data';
+
 --- test copying in CSV mode with various styles
 --- of embedded line ending characters
 
diff --git a/src/test/regress/output/copy.source b/src/test/regress/output/copy.source
index afdaa509d2f3f7d38b6293a569a700d67d7d0bc9..527c2a80285012714404d8ea5db51bad09788921 100644
--- a/src/test/regress/output/copy.source
+++ b/src/test/regress/output/copy.source
@@ -31,6 +31,8 @@ COPY bt_i4_heap FROM '@abs_srcdir@/data/desc.data';
 COPY bt_name_heap FROM '@abs_srcdir@/data/hash.data';
 COPY bt_txt_heap FROM '@abs_srcdir@/data/desc.data';
 COPY bt_f8_heap FROM '@abs_srcdir@/data/hash.data';
+COPY array_op_test FROM '@abs_srcdir@/data/array.data';
+COPY array_index_op_test FROM '@abs_srcdir@/data/array.data';
 --- test copying in CSV mode with various styles
 --- of embedded line ending characters
 create temp table copytest (
diff --git a/src/test/regress/output/misc.source b/src/test/regress/output/misc.source
index 6bab1673d0340d1f77ed9538a4ab942242ced5e8..d4201a1fcac32ab60cc38b04c39e6b360990c779 100644
--- a/src/test/regress/output/misc.source
+++ b/src/test/regress/output/misc.source
@@ -570,6 +570,8 @@ SELECT user_relns() AS user_relns
  a_star
  abstime_tbl
  aggtest
+ array_index_op_test
+ array_op_test
  arrtest
  b
  b_star
@@ -663,7 +665,7 @@ SELECT user_relns() AS user_relns
  toyemp
  varchar_tbl
  xacttest
-(97 rows)
+(99 rows)
 
 SELECT name(equipment(hobby_construct(text 'skywalking', text 'mer')));
  name 
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index d0574beda01893c0d31c2b31380cf3fb7765bb92..9c1d65970173f8780192410334b26e6966b8d484 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -157,6 +157,22 @@ SELECT ARRAY[[1,2],[3,4]] || ARRAY[5,6] AS "{{1,2},{3,4},{5,6}}";
 SELECT ARRAY[0,0] || ARRAY[1,1] || ARRAY[2,2] AS "{0,0,1,1,2,2}";
 SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
 
+SELECT * FROM array_op_test WHERE i @ '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @ '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @ '{32,17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i ~ '{38,34,32,89}' ORDER BY seqno;
+
+SELECT * FROM array_op_test WHERE t @ '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @ '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @ '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t ~ '{AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}' ORDER BY seqno;
+
 -- array casts
 SELECT ARRAY[1,2,3]::text[]::int[]::float8[] AS "{1,2,3}";
 SELECT ARRAY[1,2,3]::text[]::int[]::float8[] is of (float8[]) as "TRUE";
diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql
index aa70bdd46f5fa88100882cbea7d935e7209e8417..ef7f1cca27e2195c8212e3448f22c7ccf3e064f4 100644
--- a/src/test/regress/sql/create_index.sql
+++ b/src/test/regress/sql/create_index.sql
@@ -129,6 +129,39 @@ SELECT count(*) FROM gpolygon_tbl WHERE f1 && '(1000,1000,0,0)'::polygon;
 
 SELECT count(*) FROM gcircle_tbl WHERE f1 && '<(500,500),500>'::circle;
 
+RESET enable_seqscan;
+RESET enable_indexscan;
+RESET enable_bitmapscan;
+
+--
+-- GIN over int[]
+--
+
+SET enable_seqscan = OFF;
+SET enable_indexscan = ON;
+SET enable_bitmapscan = ON;
+
+CREATE INDEX intarrayidx ON array_index_op_test USING gin (i);
+
+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 i @ '{17}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i && '{17}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i @ '{32,17}' ORDER BY seqno;
+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;
+
+CREATE INDEX textarrayidx ON array_index_op_test USING gin (t);
+
+SELECT * FROM array_index_op_test WHERE t @ '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t @ '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t @ '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t ~ '{AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}' ORDER BY seqno;
+
+
 RESET enable_seqscan;
 RESET enable_indexscan;
 RESET enable_bitmapscan;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index b5a2889f374a4b3c3b725ff0c5a13230330c3960..21efae2d55b7e36f43887490955bd08d2f35f77f 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -220,3 +220,14 @@ CREATE TABLE bt_f8_heap (
 	random 		int4
 );
 
+CREATE TABLE array_op_test (
+	seqno		int4,
+	i			int4[],
+	t			text[]
+);
+
+CREATE TABLE array_index_op_test (
+	seqno		int4,
+	i			int4[],
+	t			text[]
+);
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 0528e3657c3cbe839463a0bd10351f1eb6bc54be..7b1d2b54cba630935e835f1feee4773d071f03c9 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -24,7 +24,12 @@ create function binary_coercible(oid, oid) returns bool as
 'SELECT ($1 = $2) OR
  EXISTS(select 1 from pg_cast where
         castsource = $1 and casttarget = $2 and
-        castfunc = 0 and castcontext = ''i'')'
+        castfunc = 0 and castcontext = ''i'') OR
+ ( EXISTS(select 1 from pg_type source where
+		source.oid = $1 and source.typelem != 0 )
+	AND
+   EXISTS(select 1 from pg_type target where
+		target.oid = $2 and target.typname = ''anyarray'' ) )'
 language sql;
 
 -- This one ignores castcontext, so it considers only physical equivalence
@@ -634,13 +639,13 @@ WHERE p1.amopclaid = p3.oid AND p3.opcamid = p2.oid AND
 -- Detect missing pg_amop entries: should have as many strategy operators
 -- as AM expects for each opclass for the AM.  When nondefault subtypes are
 -- present, enforce condition separately for each subtype.
--- We have to exclude GiST, unfortunately, since it hasn't got any fixed
+-- We have to exclude GiST and GIN, unfortunately, since its havn't got any fixed
 -- requirements about strategy operators.
 
 SELECT p1.oid, p1.amname, p2.oid, p2.opcname, p3.amopsubtype
 FROM pg_am AS p1, pg_opclass AS p2, pg_amop AS p3
 WHERE p2.opcamid = p1.oid AND p3.amopclaid = p2.oid AND
-    p1.amname != 'gist' AND
+    p1.amname != 'gist' AND p1.amname != 'gin' AND
     p1.amstrategies != (SELECT count(*) FROM pg_amop AS p4
                         WHERE p4.amopclaid = p2.oid AND
                               p4.amopsubtype = p3.amopsubtype);