diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 8f6752f..38f111e 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -570,6 +570,15 @@
    </varlistentry>
 
    <varlistentry>
+    <term><acronym>SP-GiST</acronym></term>
+    <listitem>
+     <para>
+      <link linkend="SPGiST">Space-Partitioned Generalized Search Tree</link>
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><acronym>SQL</acronym></term>
     <listitem>
      <para>
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 7a698e5..b96dd65 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -82,6 +82,7 @@
 <!ENTITY catalogs   SYSTEM "catalogs.sgml">
 <!ENTITY geqo       SYSTEM "geqo.sgml">
 <!ENTITY gist       SYSTEM "gist.sgml">
+<!ENTITY spgist     SYSTEM "spgist.sgml">
 <!ENTITY gin        SYSTEM "gin.sgml">
 <!ENTITY planstats    SYSTEM "planstats.sgml">
 <!ENTITY indexam    SYSTEM "indexam.sgml">
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 2dedb15..37324f2 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -116,7 +116,7 @@ CREATE INDEX test1_id_index ON test1 (id);
 
   <para>
    <productname>PostgreSQL</productname> provides several index types:
-   B-tree, Hash, GiST and GIN.  Each index type uses a different
+   B-tree, Hash, GiST, SP-GiST and GIN.  Each index type uses a different
    algorithm that is best suited to different types of queries.
    By default, the <command>CREATE INDEX</command> command creates
    B-tree indexes, which fit the most common situations.
@@ -256,6 +256,23 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
   <para>
    <indexterm>
     <primary>index</primary>
+    <secondary>SP-GiST</secondary>
+   </indexterm>
+   <indexterm>
+    <primary>SP-GiST</primary>
+    <see>index</see>
+   </indexterm>
+   SP-GiST is a search tree, which allows to implement a wide range of 
+   different non-balanced disk-based data structures, such as quadtree, 
+   kd-tree, tries - popular data structures, originally developed for memory 
+   storage.
+
+   For more information see <xref linkend="SPGiST">.
+  </para>
+
+  <para>
+   <indexterm>
+    <primary>index</primary>
     <secondary>GIN</secondary>
    </indexterm>
    <indexterm>
diff --git a/doc/src/sgml/mvcc.sgml b/doc/src/sgml/mvcc.sgml
index b311d24..21ad766 100644
--- a/doc/src/sgml/mvcc.sgml
+++ b/doc/src/sgml/mvcc.sgml
@@ -1460,7 +1460,7 @@ SELECT pg_advisory_lock(q.id) FROM
     <variablelist>
      <varlistentry>
       <term>
-       B-tree and <acronym>GiST</acronym> indexes
+       B-tree, <acronym>GiST</acronym> and <acronym>SP-GiST</acronym> indexes
       </term>
       <listitem>
        <para>
@@ -1510,8 +1510,8 @@ SELECT pg_advisory_lock(q.id) FROM
     applications; since they also have more features than hash
     indexes, they are the recommended index type for concurrent
     applications that need to index scalar data. When dealing with
-    non-scalar data, B-trees are not useful, and GiST or GIN indexes should
-    be used instead.
+    non-scalar data, B-trees are not useful, and GiST, SP-GiST or GIN indexes 
+    should be used instead.
    </para>
   </sect1>
  </chapter>
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index ac1da22..7e80265 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -242,6 +242,7 @@
   &geqo;
   &indexam;
   &gist;
+  &spgist;
   &gin;
   &storage;
   &bki;
diff --git a/doc/src/sgml/spgist.sgml b/doc/src/sgml/spgist.sgml
new file mode 100644
index 0000000..0343866
--- /dev/null
+++ b/doc/src/sgml/spgist.sgml
@@ -0,0 +1,349 @@
+<!-- doc/src/sgml/spgist.sgml -->
+
+<chapter id="SPGiST">
+<title>SP-GiST Indexes</title>
+
+   <indexterm>
+    <primary>index</primary>
+    <secondary>SP-GiST</secondary>
+   </indexterm>
+
+<sect1 id="spgist-intro">
+ <title>Introduction</title>
+
+ <para>
+   <acronym>SP-GiST</acronym> is an abbreviation of space-partitioned 
+   <acronym>GiST</acronym> - the search tree, which aimed at facilitate 
+   development a wide range of different non-balanced disk-based data
+   structures, such as quadtree, kd-tree, tries - the popular data structures,
+   originally developed for memory storage. Main memory access structures 
+   usually designed as a set of dynamically allocated nodes linked by pointers,
+   which is not suitable for direct storing on disk, since these chains of 
+   pointers can be rather long and require too many disk accesses. In opposite,
+   disk based data structures have a high fanout to minimize I/O. The challenge
+   is to map nodes of tree to disk pages in such a way, so search 
+   algorithm accesses only a few disk pages, even if it traverse many nodes.
+ </para>
+
+ <para>
+  One advantage of <acronym>SP-GiST</acronym> is that it allows the development
+  of custom data types with the appropriate access methods, by
+  expert in the domain of the data type, rather than a database expert.
+ </para>
+
+  <para>
+    Some of the information here is derived from the Purdue University's 
+    SP-GiST Indexing Project
+    <ulink url="http://www.cs.purdue.edu/spgist/">web site</ulink> and.
+    The <acronym>SP-GiST</acronym>
+    implementation in <productname>PostgreSQL</productname> is primarily
+    maintained by Teodor Sigaev and Oleg Bartunov, and there is more
+    information on their
+    <!-- URL will be changed -->
+    <ulink url="http://www.sai.msu.su/~megera/wiki/spgist_dev">web site</ulink>.
+  </para>
+
+</sect1>
+
+<sect1 id="spgist-extensibility">
+ <title>Extensibility</title>
+
+ <para>
+    The <acronym>SP-GiST</acronym> interface has a high level abstraction and
+    requires the access method developer to implement only methods specific 
+    for a given data type. The <acronym>SP-GiST</acronym> core is responsible
+    for the efficient disk mapping, concurrency, logging and searching the tree 
+    structure.
+ </para>
+
+ <para>
+    Leaf tuples of a tree contain indexed values (k-d-tree or quadtree) or their 
+    tail (suffix tree), which are of the same type as an indexed 
+    column. Inner tuples are  more complex, since they are branching points
+    in tree and contain a set of nodes. Each node has a node prefix (can be 
+    common for all nodes in a tuple), which can be of different type,
+    for example, a character for suffix tree.
+ </para>
+
+ <para>
+    Some trees require knowledge of  level (or depth) of current tuple, so 
+    core  of <acronym>SP-GiST</acronym> provides the possibility 
+    for operator class to manage level counting.
+ </para>
+
+ <para>
+   There are five mandatory methods, that an index operator class for
+   <acronym>SP-GiST</acronym> should provide.
+ </para>
+
+ <variablelist>
+    <varlistentry>
+     <term><function>config</></term>
+     <listitem>
+      <para>
+        Returns oids of prefix and node types;
+      </para>
+     <para>
+     The <acronym>SQL</> declaration of the function must looks like this:
+<programlisting>
+CREATE OR REPLACE FUNCTION my_config()
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+</programlisting>
+
+        Config function should return pointer to C-struct:
+
+<programlisting>
+        typedef struct SpGistOpClassProp
+        {
+            Oid             prefixType;
+            Oid             nodeType;
+        } SpGistOpClassProp;
+</programlisting>
+     </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><function>choose</></term>
+     <listitem>
+      <para>
+        Choose a node (branch) to follow during insertion. If inner tuple 
+        doesn't contains the required node, then <function>choose</function> 
+        method should return a data for new node in tuple. 
+        If tuple prefix is not a prefix for new value, then 
+        <function>choose</function>method should provide
+        an information how to split prefix to the two ones. In this case, actually,
+        current inner tuple will be splitted to a prefix and postfix tuples.
+      </para>
+
+      <para>
+        The <acronym>SQL</> declaration of the function must looks like this:
+
+<programlisting>
+CREATE OR REPLACE FUNCTION my_choose(internal, internal)
+RETURNS void
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+</programlisting>
+
+        Function accepts spgChooseIn C-struct as input and spgChooseOut as
+        output:
+
+<programlisting>
+typedef struct spgChooseIn
+{
+    Datum   datum; /* full value to index */
+    int     level;
+
+    /*
+     * Inner tuple data
+     */
+    bool    allTheSame;
+    bool    hasPrefix;
+    Datum   prefixDatum;
+
+    int     nNodes;
+    Datum  *nodeDatums;
+} spgChooseIn;
+
+typedef enum spgChooseResultType
+{
+    spgMatchNode = 0,
+    spgAddNode,
+    spgSplitTuple
+} spgChooseResultType;
+
+typedef struct spgChooseOut
+{
+    spgChooseResultType resultType;
+    union {
+        struct {
+            int     nodeN;
+            int     levelAdd;
+            Datum   restDatum;
+        } matchNode;
+        struct {
+            Datum   nodeDatum;
+            int     nodeN;
+        } addNode;
+        struct {
+            /* new inner tuple with one node */
+            bool    prefixHasPrefix;
+            Datum   prefixPrefixDatum;
+            Datum   nodeDatum; /* this node points to postfix */
+
+            /* new next inner tuple with all old nodes */
+            bool    postfixHasPrefix;
+            Datum   postfixPrefixDatum;
+        } splitTuple;
+    } result;
+} spgChooseOut;
+
+</programlisting>
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><function>picksplit</></term>
+     <listitem>
+      <para>
+       Method provides information for creation of new inner tuple over set
+       of leaf tuples.
+      </para>
+
+      <para>
+        The <acronym>SQL</> declaration of the function must looks like this:
+
+<programlisting>
+CREATE OR REPLACE FUNCTION my_picksplit(internal, internal)
+RETURNS void
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+</programlisting>
+
+        Function accepts spgPickSplitIn C-struct as input and spgPickSplitOut as
+        output:
+
+<programlisting>
+typedef struct spgPickSplitIn
+{
+    int     nTuples;
+    Datum   *datums;
+    int     level;
+} spgPickSplitIn;
+
+typedef struct spgPickSplitOut
+{
+    bool    hasPrefix;
+    Datum   prefixDatum;
+    int     addLevel;
+                         
+    bool    allTheSame;
+    int     nNodes;
+    Datum  *nodeDatums;
+
+    int    *mapTuplesToNodes;
+    Datum  *leafTupleDatums;
+} spgPickSplitOut;
+
+</programlisting>
+
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><function>my_inner_consistent</></term>
+     <listitem>
+      <para>
+        Returns set of nodes (branches) to follow for search scan.
+      </para>
+
+      <para>
+        The <acronym>SQL</> declaration of the function must looks like this:
+
+<programlisting>
+CREATE OR REPLACE FUNCTION my_inner_consistent(internal, internal)
+RETURNS ivoid
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+</programlisting>
+
+        Function accepts spgInnerConsistentIn C-struct as input and 
+        spgInnerConsistentOut as output:
+
+<programlisting>
+typedef struct spgInnerConsistentIn
+{
+    StrategyNumber  strategy;
+    Datum           query;
+
+    int             level;
+    Datum           reconstructedValue;
+
+    bool            hasPrefix;
+    Datum           prefixDatum;
+
+    int             nNodes;
+    Datum          *nodeDatums;
+} spgInnerConsistentIn;
+
+typedef struct spgInnerConsistentOut
+{
+    int     nNodes;
+    int     levelAdd; /* including node's levels */
+    int     *nodeNumbers;
+    Datum   *reconstructedValues;
+} spgInnerConsistentOut;
+
+</programlisting>
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><function>my_leaf_consistent</></term>
+     <listitem>
+      <para>
+        Returns true if a leaf tuple satisfies a query.
+      </para>
+
+      <para>
+        The <acronym>SQL</> declaration of the function must looks like this:
+
+<programlisting>
+CREATE OR REPLACE FUNCTION my_leaf_consistent(int4, internal, int2, internal, internal, internal )
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+</programlisting>
+
+        An example of C-code:
+
+<programlisting>
+Datum       my_leaf_consistent(PG_FUNCTION_ARGS);
+PG_FUNCTION_INFO_V1(my_leaf_consistent);
+
+Datum
+my_leaf_consistent(PG_FUNCTION_ARGS)
+{
+    int             level = PG_GETARG_INT32(0);
+    QueryType      *query = PG_GETARG_QUERY_TYPE(1);
+    StrategyNumber  strategy = PG_GETARG_INT32(2);
+    LeafType       *leafDatum = PG_GETARG_LEAF_TYPE(3);
+    LeafType       *reconstructedValue = PG_GETARG_LEAF_TYPE(4);
+    bool           *recheck = (bool*)PG_GETARG_POINTER(5);
+
+    PG_RETURN_POINTER(_my_leaf_consistent(...));
+}
+</programlisting>
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+
+  <para>
+   All the SP-GiST support methods normally called in a short-lived memory
+   contexts; that is, <varname>CurrentMemoryContext</> will be reseted after
+   processing of each tuple.  It is therefore not very important to worry about
+   pfree'ing everything you palloc.
+  </para>
+
+</sect1>
+
+<sect1 id="spgist-examples">
+ <title>Examples</title>
+
+ <para>
+  The <productname>PostgreSQL</productname> source distribution includes
+  several examples of index methods implemented using
+  <acronym>SP-GiST</acronym>.  The core system currently provides suffix tree
+  over text columns and two trees over points: quadtree and k-d-tree.
+ </para>
+
+</sect1>
+
+</chapter>
diff --git a/doc/src/sgml/xindex.sgml b/doc/src/sgml/xindex.sgml
index 2832436..65bbea9 100644
--- a/doc/src/sgml/xindex.sgml
+++ b/doc/src/sgml/xindex.sgml
@@ -237,6 +237,13 @@
    </table>
 
   <para>
+   SP-GiST index is very close to GiST in basic idea except it's suitable
+   to implement various non-balanced trees originally developed for memory
+   storage. As an example, PostgreSQL includes implementation of quadtree and
+   kd-tree over points.
+  </para>
+
+  <para>
    GIN indexes are similar to GiST indexes in flexibility: they don't have a
    fixed set of strategies. Instead the support routines of each operator
    class interpret the strategy numbers according to the operator class's
diff --git a/src/backend/access/Makefile b/src/backend/access/Makefile
index a4c4ca7..0366d59 100644
--- a/src/backend/access/Makefile
+++ b/src/backend/access/Makefile
@@ -8,6 +8,6 @@ subdir = src/backend/access
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
-SUBDIRS	    = common gist hash heap index nbtree transam gin
+SUBDIRS	    = common gist hash heap index nbtree transam gin spgist
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/spgist/Makefile b/src/backend/access/spgist/Makefile
new file mode 100644
index 0000000..918da1f
--- /dev/null
+++ b/src/backend/access/spgist/Makefile
@@ -0,0 +1,19 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for access/spgist
+#
+# IDENTIFICATION
+#    src/backend/access/spgist/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/access/spgist
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = spgutils.o spginsert.o spgscan.o spgvacuum.o \
+	spgdoinsert.o spgxlog.o \
+	spgtextproc.o spgquadtreeproc.o spgkdtreeproc.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/spgist/README b/src/backend/access/spgist/README
new file mode 100644
index 0000000..888eaea
--- /dev/null
+++ b/src/backend/access/spgist/README
@@ -0,0 +1,210 @@
+SP-GiST for hackers
+
+SP-GiST is an abbreviation of space-partitioned GiST - the search tree, which 
+allows to implement a wide range of different non-balanced disk-based data 
+structures, such as quadtree, kd-tree, tries - popular data structures, 
+originally developed for memory storage. Main memory access structures usually 
+designed as a set of dynamically allocated nodes linked by pointers, which is 
+not suitable for direct storing on disk, since these chains of pointers can be 
+rather long and require too many disk accesses. In opposite, disk based data 
+structures have a high fanout to minimize I/O. The challenge is to map nodes of 
+tree to disk pages in such a way, so search algorithm accesses only a few disk 
+pages, even if it traverse many nodes.
+
+COMMON STRUCTURE DESCRIPTION
+
+Logically, tree is a set of tuples, each of different number of children, each 
+of which can also be an inner or leaf tuple. It's important to note, that 
+on-disk storage is not effective to store a lot of inner tuples and so leaf 
+tuples gathered into linked list, consequently, all leaves in such a list have 
+the same parent. Branches can be of different depth (actually, there is no 
+control or code to support balancing), which means that the tree is 
+non-balanced.
+
+SP-GiST core is responsible to map inner/leaf tuple to disk pages and imposes 
+the following restriction - the size of inner or leaf tuples should match a 
+page size, as well as each list of leaf tuples should be placed on the same 
+page. SP-GiST differs pages for inner and leaf tuples, they are called inner 
+and leaf pages respectively. Root page and root inner tuple are special - root 
+page contains only one inner tuple. If total number of leaf tuples is small 
+enough to fit all tuples to single page, then root page contains only array of 
+leaf tuples.
+
+Each inner tuples contains a 'nodes', which are (key,pointer) pairs, where 
+pointer (ItemPointerData) is a pointer to the child. Search traversal algorithm 
+on each inner tuple chooses a set of nodes to continue tree traverse in depth. 
+If it reaches a leaf page it scans a list of leaf tuples (they are on the same 
+page) to find matched ones. Insertion algorithm does the same, except, it 
+should choose only one node instead of a set and it could modify inner tuple to 
+satisfy a new value to be inserted. If it's needed to append new leaf tuple to 
+a list and there is no free space on page, then SP-GiST creates a new inner 
+tuple and distribute leaf tuples into a set of lists on, perhaps, several pages.
+
+Inner tuple consists of:
+
+  optional prefix value - all successors should be consistent with it.
+	Example: 
+		suffix tree  - prefix value is a common prefix
+		quad tree    - centroid
+		k-d-tree     - one coordinate
+
+  list of nodes, where node is a (key, pointer) pair.
+  	Example of a key: a single character for suffix tree
+
+Leaf tuple consists of:
+
+  a value
+	Example:
+		suffix tree - the rest of string (postfix)
+		quad and k-d tree - the point itself
+
+  ItemPointer to the heap
+
+INSERTION ALGORITHM
+
+Insertion algorithm is designed to keep a tree in consistent state at any 
+moment. Below is a simplified insertion algorithm specification (numbers refers 
+to a notes below):
+
+  Start with the first tuple on the root page (1)
+  
+  loop:
+	if (page is leaf) then
+		if (enough space)
+			insert on page and exit (5)
+		else (7)
+			call PickSplitFn() (2)
+		end if
+	else
+		switch(chooseFn)
+			case MatchNode  - continue with pointer of node 
+			case AddNode    - add node and then continue with node's 
+							  pointer  (3, 6)
+			case SplitTuple - split inner tuple to prefix and postfix 
+                              and continue with the postfix tuple (4, 6)
+	end if
+
+Notes:
+
+(1) root inner tuple is always a single tuple on page, since after addition 
+node to the root, inner tuple could be too large to be stored on the root page. 
+Current implementation allows moving non-root inner tuple to another page, 
+which isn't possible for root, else we have to separate code path to support 
+root migration. On other side, we could implement moving non-root inner tuple 
+from root page to another page, but since tuple has no link to parent tuple, it 
+can be difficult to support consistent state of tree.
+
+(2) Current implementation allows to do picksplit and insert a new leaf tuple 
+at once, if the new list of leaf tuples fits one page. It's always true for 
+trees like quad tree of k-d-tree, but suffix tree may require subsequent 
+picksplit.
+
+(3) Addition of node should keep size of inner tuple to small to fit on page. 
+After addition inner tuple could become too large to be stored on current page
+because of other tuples on page. In this case it will be moved to another (may 
+be new) inner page (see notes about page management). Next, moving tuple to 
+another page could change enumeration of tuples on page and pointers to tuples 
+could be invalid. To prevent that, core leaves a placeholder, named dead tuple 
+which can be reused later. See also Concurrency and Vacuum chapter. Right now 
+only suffix tree could add a node to the tuple, quad tree and k-d-tree make all
+possible nodes at once in PickSplitFn() call. Current implementation supports 
+ordered array of nodes.
+
+(4) Prefix value could only partially satisfy a new value, in this case it 
+needs to produce new subbranch of tree contains two new inner tuples: prefix 
+and postfix. Consider example of insertion into a suffix tree. We use the 
+following notation, where tuple's id used only for just for illustration (there
+is no id).
+
+ inner tuple: {tuple id}(prefix string)[ comma separated list of node's keys ]
+ leaf tuple: {tuple id}<value>
+
+Insert string 'www.gogo.com' to inner tuple {1}(www.google.com/)[a, i] . We 
+need to split it to two ones:
+
+	{2}(www.go)[o]  - prefix tuple
+	            |
+                {3}(gle.com/)[a,i] - postfix tuple
+
+On the next iteration of loop we add node [g] to tuple {2}
+
+				   NIL (no value inserted yet)
+				   |
+	{2}(www.go)[o, g]
+			    |
+			    {3}(gle.com/)[a,i]
+
+And finally, insert a leaf tuple
+
+				  {4}<o.com> 
+				   |
+	{2}(www.go)[o, g]
+			    |
+			    {3}(gle.com/)[a,i]
+
+As we can see, node's array moves to postfix tuple without any changes and 
+SP-GiST core believes, that prefix tuple is not larger than old inner tuple. 
+That allows to store prefix tuple instead of old inner tuple. SP-GiST core will 
+try to store postfix tuple on the same page if possible and will use another 
+page if there is not enough free space (see notes 5 and 6). Currently, quad and 
+k-d tree doesn't use this feature, because they grow their depth only by 
+PickSplitFn() call.
+
+(5) If pointer from node of parent is a NIL pointer (it can be a result of 
+chooseFn with SplitTuple output or vacuum), algorithm chooses a leaf page to 
+store. At first, it try to use the last used leaf page with the largest free 
+space (we track it) to better utilize a disk space. If it's not large enough, 
+then the algorithm allocates a new page.
+
+(6) Management of inner pages is very similar to management of leaf pages, 
+described in (5)
+
+(7) Actually, current implementation can move the whole list of leaf tuples and 
+a new tuple to another page, if the list is a short enough. This increases 
+space utilization, but doesn't changes the basis of the algorithm.
+
+CONCURRENCY
+
+Insertion algorithm always requires an exclusive lock on parent and child pages 
+(parent and child pages can be the same, see notes above). There is a 
+possibility of dead lock in case of cross referenced pages in different 
+branches, for example, inner tuple with id 1 on page N has a child on page M 
+and inner tuple with id 2 from another branch has a child on the same page N. 
+It can happens, due to remembering of the last used page. To prevent dead lock 
+we introduced a "triple parity" of page - if inner tuple is placed on page with 
+BlockNumber N, then it's child tuple should be placed on the same page or on 
+the page with the BlockNumber M and (N+1) mod 3 == M mod 3 to guarantee that 
+tuples on page M have no children on page N ( (M+1) mod 3 != N mod 3 ).
+
+Search traversal algorithm is rather traditional - it share locks pointed page 
+and works with pointed tuple and puts pointers from nodes to the stack, then 
+unlock the page and pop stack to get next pointer. But here there is a race 
+condition - while we playing around our stack, insertion process could move our 
+target inner (or leaf tuple after PickSplitFn() is replaced by inner tuple 
+stored on another page) tuple to another page. To prevent that, insertion 
+algorithm puts a redirection tuple, which contains pointer to the actual place 
+of child.
+
+VACUUM
+
+Vacuum traversal algorithm is similar to search one, except it always keeps 
+exclusive lock on parent page if child is a leaf page, since after vacuum list 
+of leaf tuples can be changed or becomes empty and pointer in node in inner 
+parent tuple should be updated.
+
+Also, vacuum converts redirection tuples to dead tuples, but only such tuples, 
+which have id of parent transaction is less than GetOldestXmin(). Dead tuples 
+can be reused, because they are just a placeholders to keep tuple's enumeration 
+constant.
+
+LAST USED PAGE MANAGEMENT
+
+List of last used page contains four pages - leaf page and three pages for each 
+"triple parity". This list is stored between calls on meta page, which is never 
+WAL-logged to decrease WAL traffic. Incorrected data on meta page isn't 
+critical, because we could allocate a new page at any moment.
+
+Authors:
+
+    Teodor Sigaev <teodor@sigaev.ru>
+    Oleg Bartunov <oleg@sai.msu.su>
diff --git a/src/backend/access/spgist/spgdoinsert.c b/src/backend/access/spgist/spgdoinsert.c
new file mode 100644
index 0000000..12c9ab1
--- /dev/null
+++ b/src/backend/access/spgist/spgdoinsert.c
@@ -0,0 +1,1517 @@
+/*-------------------------------------------------------------------------
+ *
+ * spgdoinsert.c
+ *    implementation of insert algorithm
+ *
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *          src/backend/access/spgist/spgdoinsert.c
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/spgist_private.h"
+#include "catalog/index.h"
+#include "miscadmin.h"
+#include "storage/bufmgr.h"
+#include "storage/itemptr.h"
+#include "storage/indexfsm.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+
+void
+updateNodeLink(SpGistInnerTuple tup, int nodeN, BlockNumber blkno, OffsetNumber offset)
+{
+	int 		i;
+	IndexTuple	node;
+
+	SGITITERATE(tup, i, node)
+	{
+		if (i == nodeN)
+		{
+			ItemPointerSet(&node->t_tid, blkno, offset); 
+			return;
+		}
+	}
+
+	abort();
+}
+
+static SpGistInnerTuple 
+addNode(SpGistState *state, SpGistInnerTuple tuple, Datum datum, int offset)
+{
+	IndexTuple			node, *nodes;
+	bool				isnull = false;
+	int					i;
+
+
+	if (offset < 0)
+		offset = tuple->nNodes;
+
+	nodes = palloc(sizeof(IndexTuple) * (tuple->nNodes + 1));
+	SGITITERATE(tuple, i, node)
+	{
+		if (i < offset)
+			nodes[i] = node;
+		else
+			nodes[i + 1] = node;
+	}
+
+	nodes[offset] = index_form_tuple(state->nodeTupDesc, &datum, &isnull);
+
+	return spgFormInnerTuple(state,
+								!!tuple->prefixSize, SGITDATUM(tuple, state),
+								tuple->nNodes + 1, nodes);
+}
+
+static int
+cmpOffsetNumbers(const void *a, const void *b, void *arg)
+{       
+    if ( *(OffsetNumber*)a == *(OffsetNumber*)b )
+		return 0;
+	return ( *(OffsetNumber*)a > *(OffsetNumber*)b ) ? 1 : -1;
+}
+
+void
+spgPageIndexMultiDead(SpGistState *state, Page page, OffsetNumber *itemnos, int nitems,
+					/* redirect chain */
+					BlockNumber blkno, OffsetNumber offnum)
+{
+	SpGistFakeTuple	tuple;
+	int 			i;
+
+	if (nitems == 0)
+		return;
+
+	if (state->isBuild == false && blkno != InvalidBlockNumber)
+	{
+		/*
+		 * set first tuple as redirection. While build we
+		 * don't need set redirection
+		 */
+		tuple = spgFormFakeTuple(state, blkno, offnum);
+
+		PageIndexTupleDelete(page, itemnos[0]);
+		itemnos[0] = PageAddItem(page, (Item)tuple, tuple->size,
+										itemnos[0], false, false);
+		Assert(itemnos[0] != InvalidOffsetNumber);
+		SpGistPageGetOpaque(page)->nRedirection++;
+
+		itemnos++;
+		nitems --;
+
+		if (nitems == 0)
+			return;
+	}
+
+	if (state->attType.attlen > 0)
+	{
+		/*
+		 * For fixed size type just mark tuples as dead
+		 */
+		for(i=0; i<nitems; i++)
+		{
+			tuple = (SpGistFakeTuple) PageGetItem(page, PageGetItemId(page, itemnos[i]));
+			Assert(tuple->isDead == 0 && tuple->isRedirect == 0 && ItemPointerIsValid(&tuple->pointer));
+			tuple->isDead = 1;
+			ItemPointerSetInvalid(&tuple->pointer);
+		}
+	}
+	else
+	{
+		/*
+		 * For varlena types replace tuples with fake ones
+		 */
+		tuple = spgFormFakeTuple(state, InvalidBlockNumber, InvalidOffsetNumber);
+
+		if (nitems > 1)
+			qsort_arg(itemnos, nitems, sizeof(*itemnos), cmpOffsetNumbers, NULL);
+		PageIndexMultiDelete(page, itemnos, nitems);
+
+		for(i=0; i<nitems; i++)
+		{
+			OffsetNumber r = PageAddItem(page, (Item)tuple, state->fakeTupleSize, itemnos[i], false, false);
+			Assert(r == itemnos[i]);
+		}
+	}
+
+	SpGistPageGetOpaque(page)->nDead += nitems;
+}
+
+typedef struct PageDesc 
+{
+	BlockNumber		blkno;
+	Buffer			buffer;
+	Page			page;
+	OffsetNumber	offnum;
+	int				node;
+} PageDesc;
+
+static void
+saveNodeLink(Relation index, PageDesc *parent, BlockNumber blkno, OffsetNumber offnum)
+{
+	SpGistInnerTuple innerTuple;
+
+	parent->page = BufferGetPage(parent->buffer);
+	innerTuple = (SpGistInnerTuple) PageGetItem(parent->page,
+												PageGetItemId(parent->page, parent->offnum));
+
+	updateNodeLink(innerTuple, parent->node, blkno, offnum);
+
+	SpGistSetLastUsedPage(index, parent->buffer);
+	MarkBufferDirty(parent->buffer);
+
+	parent->page = NULL;
+}
+
+static void
+addLeafTuple(Relation index, SpGistState *state, SpGistLeafTuple leafTuple,
+             PageDesc *current, PageDesc *parent, bool isNew)
+{
+	XLogRecData		rdata[4];
+	spgxlogAddLeaf	xlrec;
+
+	ACCEPT_RDATA_DATA(&xlrec, sizeof(xlrec), 0);
+	ACCEPT_RDATA_DATA(leafTuple, leafTuple->size, 1);
+	ACCEPT_RDATA_BUFFER(current->buffer, 2);
+
+	xlrec.node = index->rd_node;
+	xlrec.blknoLeaf = current->blkno;
+	xlrec.newPage = isNew;
+
+	xlrec.offnumLeaf = InvalidOffsetNumber;
+	xlrec.offnumHeadLeaf = InvalidOffsetNumber;
+	xlrec.blknoParent = InvalidBlockNumber;
+	xlrec.offnumParent = InvalidOffsetNumber;
+
+	START_CRIT_SECTION();
+
+	if (current->offnum == InvalidOffsetNumber || current->blkno == SPGIST_HEAD_BLKNO)
+	{
+		leafTuple->nextOffset = InvalidOffsetNumber;
+		current->offnum = SpGistPageAddNewItem(state, current->page, (Item)leafTuple, leafTuple->size, NULL);
+		Assert(current->offnum != InvalidOffsetNumber);
+
+		xlrec.offnumLeaf = current->offnum;
+
+		if (parent->buffer != InvalidBuffer)
+		{
+			ACCEPT_RDATA_BUFFER(current->buffer, 3);
+
+			xlrec.blknoParent = parent->blkno;
+			xlrec.offnumParent = parent->offnum;
+			xlrec.nodeI = parent->node;
+
+			saveNodeLink(index, parent, current->blkno, current->offnum);
+		}
+	}
+	else
+	{
+		SpGistLeafTuple		head;
+		OffsetNumber		offnum;
+
+		head = (SpGistLeafTuple) PageGetItem(current->page, PageGetItemId(current->page, current->offnum));
+		leafTuple->nextOffset = head->nextOffset;
+		offnum = SpGistPageAddNewItem(state, current->page, (Item)leafTuple, leafTuple->size, NULL);
+		Assert(offnum != InvalidOffsetNumber);
+
+		xlrec.offnumLeaf = offnum;
+		xlrec.offnumHeadLeaf = current->offnum;
+
+		/*
+		 * re-get head of list because it could be moved on page and
+		 * set new second element
+		 */
+		head = (SpGistLeafTuple) PageGetItem(current->page, PageGetItemId(current->page, current->offnum));
+		head->nextOffset = offnum;
+	}
+	MarkBufferDirty(current->buffer);
+
+	if (RelationNeedsWAL(index))
+	{
+		XLogRecPtr		recptr;
+
+		recptr = XLogInsert(RM_SPGIST_ID, XLOG_SPGIST_ADD_LEAF, rdata);
+
+		PageSetLSN(current->page, recptr);
+		PageSetTLI(current->page, ThisTimeLineID);
+
+		if (xlrec.blknoParent != InvalidBlockNumber)
+		{
+			parent->page = BufferGetPage(parent->buffer);
+			PageSetLSN(parent->page, recptr);
+			PageSetTLI(parent->page, ThisTimeLineID);
+		}
+	}
+
+	END_CRIT_SECTION();
+
+	SpGistSetLastUsedPage(index, current->buffer);
+	UnlockReleaseBuffer(current->buffer);
+
+	if (parent->buffer != InvalidBuffer)
+		UnlockReleaseBuffer(parent->buffer);
+}
+
+static int
+checkSplitConditions(Relation index, SpGistState *state, PageDesc *current, int *nToSplit)
+{
+	int					i = current->offnum,
+						n = 0,
+						totalSize = 0;
+
+	if (current->blkno == SPGIST_HEAD_BLKNO)
+	{
+
+		/*
+		 * we are split root, but on root page tuples are not linked,
+		 * so, link (as other metheds here suppose to) them and
+		 * force split (not move!)
+		 */
+		OffsetNumber	j, max = SpGistPageGetMaxOffset(current->page);
+
+		for(j=FirstOffsetNumber; j<=max; j++) 
+		{
+			SpGistLeafTuple	lt;
+
+			lt = (SpGistLeafTuple) PageGetItem(current->page, PageGetItemId(current->page, j));
+			Assert(lt->isDead == 0 && lt->isRedirect == 0);
+			Assert(ItemPointerIsValid(&lt->heapPtr));
+
+			lt->nextOffset = (j==max) ? InvalidOffsetNumber : j+1;
+		}
+
+		/* return impossible value to force split */
+		*nToSplit = BLCKSZ;
+		return BLCKSZ;
+	}
+				
+	while(i != InvalidOffsetNumber)
+	{
+		SpGistLeafTuple	it;
+
+		Assert(i>=FirstOffsetNumber && i<=SpGistPageGetMaxOffset(current->page));
+		it = (SpGistLeafTuple) PageGetItem(current->page, PageGetItemId(current->page, i));
+		Assert(it->isDead == 0);
+		Assert(ItemPointerIsValid(&it->heapPtr));
+
+		n++;
+		totalSize += it->size + sizeof(ItemIdData);
+		i = it->nextOffset;
+	}
+
+	*nToSplit = n;
+
+	return totalSize;
+}
+
+/*
+ * move too small amount of tuples to another page, because
+ * split will not be effective.
+  */
+static void
+moveLeafs(Relation index, SpGistState *state, PageDesc *current, PageDesc *parent,
+			 SpGistLeafTuple leafTuple)
+{
+	int					i = current->offnum,
+						n,
+						size = 0;
+	Buffer  			nbuf;
+	Page				npage;
+	SpGistLeafTuple 	it;
+	OffsetNumber		r = InvalidOffsetNumber,
+						startOffset = InvalidOffsetNumber,
+						noffnum = InvalidOffsetNumber;
+	OffsetNumber		*toDelete;
+	BlockNumber			nblkno;
+	XLogRecData			rdata[6];
+	spgxlogMoveLeafs	xlrec;
+	char				*tuples = NULL;
+
+	Assert(parent->buffer != InvalidOffsetNumber);
+	Assert(parent->buffer != current->buffer);
+
+	n = SpGistPageGetMaxOffset(current->page);
+	toDelete 	= palloc(sizeof(OffsetNumber) * n);
+
+	n = 0;
+	while(i != InvalidOffsetNumber)
+	{
+		SpGistLeafTuple	it;
+
+		Assert(i>=FirstOffsetNumber && i<=SpGistPageGetMaxOffset(current->page));
+		it = (SpGistLeafTuple) PageGetItem(current->page, PageGetItemId(current->page, i));
+		Assert(it->isDead == 0);
+		Assert(ItemPointerIsValid(&it->heapPtr));
+
+		toDelete[n] = i;
+		size += it->size + sizeof(ItemIdData);
+		n++;
+		i = it->nextOffset;
+	}
+
+	size += leafTuple->size + sizeof(ItemIdData);
+
+	nbuf = SpGistGetBuffer(index, GBUF_LEAF, size, &xlrec.newPage);
+	npage = BufferGetPage(nbuf);
+	nblkno = BufferGetBlockNumber(nbuf);
+
+	if (RelationNeedsWAL(index))
+	{
+		tuples = palloc(size);
+
+		ACCEPT_RDATA_BUFFER(current->buffer, 0);
+		ACCEPT_RDATA_BUFFER(nbuf, 1);
+		ACCEPT_RDATA_BUFFER(parent->buffer, 2);
+		ACCEPT_RDATA_DATA(&xlrec, sizeof(xlrec), 3);
+		ACCEPT_RDATA_DATA(toDelete, sizeof(OffsetNumber) * n, 4);
+		ACCEPT_RDATA_DATA(tuples, size - (n+1) * sizeof(ItemIdData), 5);
+
+		xlrec.node = index->rd_node;
+		STORE_STATE(state, xlrec.stateSrc);
+
+		xlrec.blknoSrc = BufferGetBlockNumber(current->buffer);
+		xlrec.blknoDst = BufferGetBlockNumber(nbuf);
+		xlrec.blknoParent = BufferGetBlockNumber(parent->buffer);
+
+		xlrec.nMoves = n;
+		xlrec.offnumParent = parent->offnum;
+		xlrec.nodeI = parent->node;
+
+	}
+
+	START_CRIT_SECTION();
+
+	for(i=0; i<n; i++)
+	{
+		it = (SpGistLeafTuple) PageGetItem(current->page, PageGetItemId(current->page, toDelete[i]));
+		it->nextOffset = r;
+		if (tuples)
+		{
+			memcpy(tuples, it, it->size);
+			tuples += it->size;
+		}
+		r = SpGistPageAddNewItem(state, npage, (Item)it, it->size, &startOffset);
+		Assert(r != InvalidOffsetNumber);
+	}
+
+	leafTuple->nextOffset = r;
+	if (tuples)
+		memcpy(tuples, leafTuple, leafTuple->size);
+	r = SpGistPageAddNewItem(state, npage, (Item)leafTuple, leafTuple->size, &startOffset);
+	Assert(r != InvalidOffsetNumber);
+
+	xlrec.offnumDst = noffnum = r;
+	spgPageIndexMultiDead(state, current->page, toDelete, n, nblkno, noffnum);
+
+	saveNodeLink(index, parent, nblkno, r);
+
+	MarkBufferDirty(current->buffer);
+	MarkBufferDirty(nbuf);
+	MarkBufferDirty(parent->buffer);
+
+	if (RelationNeedsWAL(index))
+	{
+		XLogRecPtr	recptr;
+
+		recptr = XLogInsert(RM_SPGIST_ID, XLOG_SPGIST_MOVE_LEAFS, rdata);
+
+		PageSetLSN(current->page, recptr);
+		PageSetTLI(current->page, ThisTimeLineID);
+		PageSetLSN(npage, recptr);
+		PageSetTLI(npage, ThisTimeLineID);
+		parent->page = BufferGetPage(parent->buffer);
+		PageSetLSN(parent->page, recptr);
+		PageSetTLI(parent->page, ThisTimeLineID);
+	}
+
+	END_CRIT_SECTION();
+
+	SpGistSetLastUsedPage(index, current->buffer);
+	SpGistSetLastUsedPage(index, nbuf);
+	SpGistSetLastUsedPage(index, parent->buffer);
+	UnlockReleaseBuffer(current->buffer);
+	UnlockReleaseBuffer(nbuf);
+	UnlockReleaseBuffer(parent->buffer);
+
+	return;
+}
+
+static void
+setRedirectionTuple(PageDesc *current, OffsetNumber position, 
+					BlockNumber	blkno, OffsetNumber offnum)
+{
+	SpGistFakeTuple	ft;
+
+	ft = (SpGistFakeTuple) PageGetItem(current->page, PageGetItemId(current->page, position));
+	Assert(ft->isRedirect == 1);
+	Assert(ItemPointerGetBlockNumber(&ft->pointer) == SPGIST_METAPAGE_BLKNO);
+	ItemPointerSet(&ft->pointer, blkno, offnum);
+}
+
+static void
+randomSplit(spgPickSplitIn *in, spgPickSplitOut *out)
+{
+	int i;
+
+	/*
+	 * if out.allTheSame is true then out variable contains useful
+	 * infos about prefix
+	 */
+	if (!out->allTheSame)
+		out->hasPrefix = false;
+
+	out->nNodes = 8; /* magick constant */
+	out->nodeDatums = palloc(sizeof(Datum) * out->nNodes);
+
+	for(i=0; i<out->nNodes; i++)
+		
+	out->mapTuplesToNodes = palloc(sizeof(int) * in->nTuples);
+
+	for(i=0; i<in->nTuples; i++)
+		out->mapTuplesToNodes[i] = i % out->nNodes;
+
+	/* fakes */
+	out->nodeDatums = palloc(sizeof(Datum) * out->nNodes);
+	out->leafTupleDatums = palloc(sizeof(Datum) * in->nTuples);
+}
+
+/*
+ * Converts list of leafs tuples pointed by current to one inner tuple 
+ * and several lists of leaf tuples. On exit, current contains
+ * address of that inner tuple
+ */
+static bool
+doPickSplit(Relation index, SpGistState *state, PageDesc *current, PageDesc *parent, 
+			SpGistLeafTuple newLeafTuple, int *level)
+{
+	spgPickSplitIn		in;
+	spgPickSplitOut 	out;
+	int					i,
+						startI,
+						n = 0;
+	SpGistInnerTuple	innerTuple;
+	IndexTuple			*nodes,
+						node;
+	Buffer				*leafBuffers,
+						newCurrentBuffer = InvalidBuffer;
+	ItemPointerData		*heapPtrs;
+	int					*leafSizes,
+						*leafNs;
+	OffsetNumber		*toDelete,
+						redirectTuplePos = InvalidOffsetNumber;
+	SpGistLeafTuple		*newLeafs;
+	int					freeSpace;
+	bool				allTheSame = false;
+	double				fillfactor = RelationGetFillFactor(index, 80) / 100.0;
+	XLogRecData			rdata[9];
+	int					nRdata = 2;
+	spgxlogPickSplit	xlrec;
+	spgxlogPSOneNode	*xln;
+	char				*data, *ptr;
+	PageDesc			saveCurrent;
+	int					nToDelete, nToInsert;
+
+	saveCurrent.buffer = InvalidBuffer;
+
+	/*
+	 * allocate temp arrays with max possible size
+	 */
+
+	n = SpGistPageGetMaxOffset(current->page) + 1;
+	heapPtrs 	= palloc(sizeof(ItemPointerData) * n);
+	leafSizes 	= palloc0(sizeof(int) * n);
+	leafNs 		= palloc0(sizeof(int) * n);
+	toDelete 	= palloc(sizeof(OffsetNumber) * n);
+	in.datums 	= palloc(sizeof(Datum) * n);
+	nodes 		= palloc(sizeof(IndexTuple) * n);
+	leafBuffers	= palloc(sizeof(Buffer) * n);
+	newLeafs 	= palloc(sizeof(SpGistLeafTuple) * n);
+
+	xlrec.node = index->rd_node;
+	STORE_STATE(state, xlrec.stateSrc);
+
+	i = current->offnum;
+	n = 0;
+
+
+	/*
+	 * Form list of leaf tuples which will be distributed
+	 * as split result
+	 */
+	while(i != InvalidOffsetNumber)
+	{
+		SpGistLeafTuple	it;
+
+		Assert(i>=FirstOffsetNumber && i<=SpGistPageGetMaxOffset(current->page));
+		it = (SpGistLeafTuple) PageGetItem(current->page, PageGetItemId(current->page, i));
+		Assert(it->isDead == 0);
+		Assert(ItemPointerIsValid(&it->heapPtr));
+
+		in.datums[n] = SGLTDATUM(it, state);
+		heapPtrs[n] = it->heapPtr;
+		toDelete[n] = i;
+
+		n++;
+		i = it->nextOffset;
+	}
+	nToInsert = nToDelete = in.nTuples = n;
+	in.level = *level;
+
+	/*
+	 * we will not actually insert new tuple because new picksplit
+	 * is possible due to too large value, but we will try to 
+	 * to allocate enough space for it
+	 */
+	in.datums[in.nTuples] = SGLTDATUM(newLeafTuple, state);
+	heapPtrs[in.nTuples] = newLeafTuple->heapPtr;
+	in.nTuples++;
+
+	if (parent->buffer != InvalidBuffer) 
+	{
+		/* 
+		 * parent could be 'allTheSame' node, child will be too! 
+		 */
+		parent->page = BufferGetPage(parent->buffer);
+		innerTuple = (SpGistInnerTuple) PageGetItem(parent->page,
+													PageGetItemId(parent->page, parent->offnum));
+
+		if (innerTuple->allTheSame)
+			allTheSame = true;
+	}
+
+	/*
+	 * Actual split by user-defined method.
+	 */
+	memset(&out, 0, sizeof(out));
+	if (allTheSame == false)
+	{
+		FunctionCall2(
+			&state->picksplitFn,
+			PointerGetDatum(&in),
+			PointerGetDatum(&out)
+		);
+
+		*level += out.addLevel;
+	}
+
+	if (out.allTheSame || allTheSame)
+	{
+		randomSplit(&in, &out);
+		allTheSame = true;
+	}
+
+	/*
+	 * Form nodes of inner tuple and inner tuple itself
+	 * and remember formed nodes
+	 */
+	for(i=0; i<out.nNodes; i++)
+	{
+		bool	isnull = (allTheSame) ? true : false;
+
+		nodes[i] = index_form_tuple(state->nodeTupDesc, out.nodeDatums + i, &isnull);
+		ItemPointerSetInvalid(&nodes[i]->t_tid);
+	}
+	innerTuple = spgFormInnerTuple(state,
+									out.hasPrefix, out.prefixDatum,
+									out.nNodes, nodes);
+	innerTuple->allTheSame = !!allTheSame;
+	SGITITERATE(innerTuple, i, node)
+		nodes[i] = node;
+
+	xlrec.newPage = false;
+	xlrec.nNodes = out.nNodes;
+	xlrec.nDelete = 0;
+	xln = palloc0(sizeof(*xln) * xlrec.nNodes); 
+	data = ptr = palloc(BLCKSZ /* shold be enough to store all old leaf tuples */ + newLeafTuple->size);
+
+	ACCEPT_RDATA_DATA(&xlrec, sizeof(xlrec), 0);
+	ACCEPT_RDATA_DATA(innerTuple, innerTuple->size, 1);
+
+	/*
+	 * Count sizes of leafs of each nodes, all leafs of one node
+	 * should be stored at one page, but we want to store leafs of several node
+	 * on one page it it possible to minimize number of required pages
+	 */
+	for(i=0; i<in.nTuples; i++)
+	{
+		newLeafs[i] = spgFormLeafTuple(state, heapPtrs + i, out.leafTupleDatums[i], (allTheSame) ? true : false);
+
+		n = out.mapTuplesToNodes[i];
+		if (n >= out.nNodes || n < 0)
+			elog(ERROR, "Inconsistent result of picksplitFn");
+
+		leafSizes[n] += newLeafs[i]->size + sizeof(ItemIdData);
+		leafNs[n]++;
+	}
+
+	/*
+	 * Before crit section we need to allocate buffer if any.
+	 *
+	 * Conditions should correlate with conditions below in setup innerTuple
+	 * section
+	 */
+
+	if (parent->buffer != InvalidBuffer)
+	{
+		if (!(BufferGetBlockNumber(parent->buffer) != SPGIST_HEAD_BLKNO &&
+				SpGistPageGetFreeSpace(state, parent->page, 1) >= innerTuple->size + sizeof(ItemIdData)))
+		{
+			parent->blkno = BufferGetBlockNumber(parent->buffer);  
+			newCurrentBuffer = SpGistGetBuffer(index, ENCODE_INNER_PARITY(parent->blkno + 1),
+												innerTuple->size + sizeof(ItemIdData), &xlrec.newPage);
+		}
+	}
+
+
+	START_CRIT_SECTION();
+
+	/*
+	 * Cleanup current buffer, don't touch it's a root because
+	 * root become an inner page
+	 */
+	startI = 0;
+	leafBuffers[0] = InvalidBuffer;
+	if (BufferGetBlockNumber(current->buffer) != SPGIST_HEAD_BLKNO)
+	{
+		/*
+		 * Init buffer only if there isn't any other tuples and only during build because
+		 * we need to set a redirection tuple for concurrent scan
+		 */
+		if (state->isBuild && 
+			nToDelete + SpGistPageGetOpaque(current->page)->nDead == SpGistPageGetMaxOffset(current->page))
+		{
+			SpGistInitBuffer(current->buffer, SPGIST_LEAF);
+			leafBuffers[0] = current->buffer;
+			xln[0].cleanup = true;
+		}
+		else
+		{
+			int sumN = 0,
+				sumSize = 0;
+
+			xlrec.blknoSrc = BufferGetBlockNumber(current->buffer);
+			xlrec.nDelete = nToDelete;				
+			ACCEPT_RDATA_DATA(toDelete, sizeof(*toDelete) * nToDelete, nRdata);
+			Assert(xln[0].cleanup == 0);
+			nRdata++;
+
+			if (state->isBuild == false)
+			{
+				/*
+				 * Need to set redirect tuple (it will point to new inner tuple)
+				 * but right now its place is not known yet. So, set it to "impossible"
+				 * value and remeber its position to update tuple later
+				 *
+				 * but we will store in WAL invalid value - during recovery there is no
+				 * concurrent searches
+				 */
+				redirectTuplePos = toDelete[0];
+				spgPageIndexMultiDead(state, current->page, toDelete, nToDelete,
+									SPGIST_METAPAGE_BLKNO, FirstOffsetNumber);
+			}
+			else
+			{
+				/*
+				 * During index build there is not concurrent searches, so,
+				 * we don't need to set redirection tuple
+				 */
+				spgPageIndexMultiDead(state, current->page, toDelete, nToDelete,
+									InvalidBlockNumber, InvalidOffsetNumber);
+			}
+
+			for(i=0; i<out.nNodes; i++)
+			{
+				sumN += leafNs[i];
+				sumSize += leafSizes[i];
+
+				if (sumSize > SpGistPageGetFreeSpace(state, current->page, sumN) || 
+					BLCKSZ - SpGistPageGetFreeSpace(state, current->page, sumN) > fillfactor * BLCKSZ)
+				{
+					leafBuffers[i] = InvalidBuffer;
+					break;
+				}
+				else
+				{
+					leafBuffers[i] = current->buffer;
+				}
+			}
+
+			startI = i;
+		}
+	}
+
+	/*
+	 * Find buffers to store leafs
+	 */
+	freeSpace = 0;
+	for(i=startI; i<out.nNodes; i++)
+	{
+		Buffer	nbuf = InvalidBuffer;
+
+		if (i==startI)
+		{
+			nbuf = leafBuffers[i];
+
+			if (i==0 && nbuf == current->buffer)
+			{
+				Assert(SpGistPageGetMaxOffset(current->page) == InvalidOffsetNumber);
+				freeSpace = PageGetFreeSpace(current->page);
+			}
+		}
+		else
+		{
+			Assert(i>0);
+			nbuf = leafBuffers[i-1];
+		}
+	
+		if (nbuf == InvalidBuffer || freeSpace < leafSizes[i] || BLCKSZ - freeSpace > fillfactor * BLCKSZ)
+		{
+			nbuf = SpGistNewBuffer(index);
+			SpGistInitBuffer(nbuf, SPGIST_LEAF);
+			/* new buffer hasn't deleted tuples yet */
+			freeSpace = PageGetFreeSpace(BufferGetPage(nbuf));
+			xln[i].cleanup = true;
+		}
+
+		/*
+		 * freeSpace could become a negative due to too huge a new leaf tuple.
+		 * If so, we don't put it on page and do following pisksplit
+		 */
+		freeSpace -= leafSizes[i];
+		leafBuffers[i] = nbuf;
+	}
+
+	/*
+	 * Put leafs on pages
+	 */
+	for(i=0; i<in.nTuples; i++)
+	{
+		OffsetNumber	newoffset;
+		SpGistLeafTuple	it = newLeafs[i];
+
+		n = out.mapTuplesToNodes[i];
+
+		it->nextOffset = (ItemPointerIsValid(&nodes[n]->t_tid)) ? 
+					ItemPointerGetOffsetNumber(&nodes[n]->t_tid) : InvalidOffsetNumber;
+
+		newoffset = SpGistPageAddNewItem(state, BufferGetPage(leafBuffers[n]), (Item)it, it->size, NULL);
+		if (i == in.nTuples - 1) {
+			Assert(i == nToInsert);
+			/*
+			 * Actually, it's possible that there is not enough space to install new leaf tuple.
+			 * in this case following split is needed.
+			 */
+			if (newoffset != InvalidOffsetNumber)
+			{
+				nToInsert++;
+				newLeafTuple = NULL;
+			}
+			else
+			{
+				break;
+			}
+		}
+		else
+		{
+			Assert(newoffset != InvalidOffsetNumber);
+		}
+
+		memcpy(ptr, newLeafs[i], newLeafs[i]->size);
+		ptr += newLeafs[i]->size;
+
+		ItemPointerSet(&nodes[n]->t_tid, 
+						BufferGetBlockNumber(leafBuffers[n]), newoffset);
+	}
+
+	/*
+	 * MarkDirty leaf buffers except current
+	 */
+	for(i=0; i<out.nNodes; i++)
+	{
+		xln[i].blkno = BufferGetBlockNumber(leafBuffers[i]);
+
+		if (i > 0 && leafBuffers[i] == leafBuffers[i-1])
+			continue;
+		
+		SpGistSetLastUsedPage(index, leafBuffers[i]);
+
+		MarkBufferDirty(leafBuffers[i]);
+	}
+
+	xlrec.nTuples = nToInsert;
+	ACCEPT_RDATA_DATA(xln, sizeof(*xln) * out.nNodes, nRdata);
+	nRdata++;
+	ACCEPT_RDATA_DATA(out.mapTuplesToNodes, sizeof(*out.mapTuplesToNodes) * nToInsert, nRdata);
+	nRdata++;
+	ACCEPT_RDATA_DATA(data, ptr - data, nRdata);
+	nRdata++;
+	ACCEPT_RDATA_BUFFER(current->buffer, nRdata); 
+	nRdata++;
+
+	parent->page = (parent->buffer == InvalidBuffer) ? NULL : BufferGetPage(parent->buffer);
+	saveCurrent = *current;
+	
+	/*
+	 * setup innerTuple
+	 */
+
+	if (parent->buffer != InvalidBuffer && BufferGetBlockNumber(parent->buffer) != SPGIST_HEAD_BLKNO && 
+			SpGistPageGetFreeSpace(state, parent->page, 1) >= innerTuple->size + sizeof(ItemIdData))
+	{
+		ACCEPT_RDATA_BUFFER(parent->buffer, nRdata); nRdata++;
+
+		Assert(current->buffer != parent->buffer);
+		/*
+		 * it's possible to store new inner tuple on parent page
+		 */
+		current->buffer = parent->buffer;
+		current->page = parent->page;
+		xlrec.blknoInner = current->blkno = BufferGetBlockNumber(parent->buffer);
+		xlrec.offnumInner = current->offnum = SpGistPageAddNewItem(state, parent->page,
+												(Item)innerTuple, innerTuple->size, NULL);
+		Assert(current->offnum != InvalidOffsetNumber);
+
+		/*
+		 * Update parent node link 
+		 */
+		xlrec.blknoParent = xlrec.blknoInner;
+		xlrec.offnumParent = parent->offnum;
+		xlrec.nodeI = parent->node;
+		saveNodeLink(index, parent, current->blkno, current->offnum);
+
+		/*
+		 * Save redirection and release
+		 */
+		if (redirectTuplePos != InvalidOffsetNumber)
+			setRedirectionTuple(&saveCurrent, redirectTuplePos, current->blkno, current->offnum);
+		SpGistSetLastUsedPage(index, saveCurrent.buffer);
+		MarkBufferDirty(saveCurrent.buffer);
+		/*
+		 * will return to newly inserted tuple on next iteration
+		 */
+	} 
+	else if (parent->buffer != InvalidBuffer)
+	{
+		/*
+		 * new inner tuple will be stored on a new page,
+		 * but next iteration will start from a that page
+		 *
+		 * Put tuple to page with next triple parity, the page should be allocated
+		 * before critical section above.
+		 */
+
+		xlrec.blknoParent = parent->blkno = BufferGetBlockNumber(parent->buffer);
+		xlrec.offnumParent = parent->offnum;
+		xlrec.nodeI = parent->node;
+
+		Assert(newCurrentBuffer != InvalidBuffer);
+		current->buffer = newCurrentBuffer;
+		current->page = BufferGetPage(current->buffer);
+		xlrec.blknoInner = current->blkno = BufferGetBlockNumber(current->buffer);
+		xlrec.offnumInner = current->offnum = SpGistPageAddNewItem(state, current->page, 
+																	(Item)innerTuple, innerTuple->size, NULL);
+		Assert(current->offnum != InvalidOffsetNumber);
+
+		SpGistSetLastUsedPage(index, current->buffer);
+		MarkBufferDirty(current->buffer);
+
+		saveNodeLink(index, parent, current->blkno, current->offnum);
+
+		ACCEPT_RDATA_BUFFER(current->buffer, nRdata); nRdata++;
+		ACCEPT_RDATA_BUFFER(parent->buffer, nRdata); nRdata++;
+
+		/*
+		 * Save redirection and release
+		 */
+		if (redirectTuplePos != InvalidOffsetNumber)
+			setRedirectionTuple(&saveCurrent, redirectTuplePos, current->blkno, current->offnum);
+		SpGistSetLastUsedPage(index, saveCurrent.buffer);
+		MarkBufferDirty(saveCurrent.buffer);
+	}
+	else 
+	{
+		/* root was a leaf */
+		Assert(BufferGetBlockNumber(current->buffer) == SPGIST_HEAD_BLKNO);
+		Assert(redirectTuplePos == InvalidOffsetNumber);
+		SpGistInitBuffer(current->buffer, 0);
+		xlrec.newPage = true;
+
+		ACCEPT_RDATA_BUFFER(current->buffer, nRdata); nRdata++;
+		xlrec.blknoInner = SPGIST_HEAD_BLKNO;
+		xlrec.offnumInner = current->offnum = PageAddItem(current->page, (Item)innerTuple, innerTuple->size,
+								InvalidOffsetNumber, false, false);
+		Assert(current->offnum == FirstOffsetNumber);
+		MarkBufferDirty(current->buffer);
+
+		xlrec.blknoParent = InvalidBlockNumber;
+
+		/*
+		 * We will iterate from root tuple again
+		 */
+		current->offnum = FirstOffsetNumber;
+
+		saveCurrent.buffer = InvalidBuffer;
+	}
+
+	if (RelationNeedsWAL(index))
+	{
+		XLogRecPtr	recptr;
+		Page		page;
+
+		recptr = XLogInsert(RM_SPGIST_ID, XLOG_SPGIST_PICKSPLIT, rdata);
+
+		for(i=0; i<out.nNodes; i++)
+		{
+			Page page = BufferGetPage(leafBuffers[i]);
+
+			PageSetLSN(page, recptr);
+			PageSetTLI(page, ThisTimeLineID);
+		}
+
+		page = BufferGetPage(current->buffer);
+		PageSetLSN(page, recptr);
+		PageSetTLI(page, ThisTimeLineID);
+
+		if (parent->buffer != InvalidBuffer)
+		{
+			page = BufferGetPage(parent->buffer);
+			PageSetLSN(page, recptr);
+			PageSetTLI(page, ThisTimeLineID);
+		}
+	}
+
+	END_CRIT_SECTION();
+
+	if (saveCurrent.buffer != InvalidBuffer)
+		UnlockReleaseBuffer(saveCurrent.buffer);
+
+	for(i=0; i<out.nNodes; i++)
+	{
+		if (leafBuffers[i] == saveCurrent.buffer)
+			continue;
+
+		if (i > 0 && leafBuffers[i] == leafBuffers[i-1])
+			continue;
+		UnlockReleaseBuffer(leafBuffers[i]);
+	}
+
+	if (newLeafTuple == NULL)
+	{
+		/*
+		 * we already install new tuple
+		 */
+
+		UnlockReleaseBuffer(current->buffer);
+		if (parent->buffer != InvalidBuffer && parent->buffer != current->buffer)
+			UnlockReleaseBuffer(parent->buffer);
+
+		return true;
+	}
+
+	return false;
+}
+
+static void
+spgMatchNodeAction(SpGistState *state, SpGistInnerTuple innerTuple, 
+					PageDesc *current, PageDesc *parent, int nodeN)
+{
+	int				 	i;
+	IndexTuple		 	node;
+	
+	if (parent->buffer != InvalidBuffer && parent->buffer != current->buffer)
+		UnlockReleaseBuffer(parent->buffer);
+
+	parent->blkno = current->blkno;
+	parent->buffer = current->buffer;
+	parent->offnum = current->offnum;
+	parent->node = nodeN;
+
+	SGITITERATE(innerTuple, i, node)
+	{
+		if (i == parent->node)
+		{
+			if (ItemPointerIsValid(&node->t_tid))
+			{
+				current->blkno = ItemPointerGetBlockNumber(&node->t_tid);
+				current->offnum = ItemPointerGetOffsetNumber(&node->t_tid);
+			}
+			else
+			{
+				/*
+				 * loop again for a new page
+				 */
+				current->blkno = InvalidBlockNumber;
+				current->offnum = InvalidOffsetNumber;
+			}
+			break;
+		}
+	}
+
+	current->buffer = InvalidBuffer;
+	current->page = NULL;
+	parent->page = NULL;
+
+	Assert(i == parent->node);
+}
+
+static void
+spgAddNodeAction(Relation index, SpGistState *state, SpGistInnerTuple innerTuple, 
+					PageDesc *current, PageDesc *parent, 
+					int nodeN, Datum nodeDatum)
+{
+	XLogRecData		rdata[5];
+	spgxlogAddNode	xlrec;
+	SpGistInnerTuple newInnerTuple = addNode(state, innerTuple, nodeDatum, nodeN);
+
+	ACCEPT_RDATA_DATA(&xlrec, sizeof(xlrec), 0);
+	ACCEPT_RDATA_DATA(newInnerTuple, newInnerTuple->size, 1);
+	ACCEPT_RDATA_BUFFER(current->buffer, 2);
+
+	xlrec.node = index->rd_node;
+	STORE_STATE(state, xlrec.stateSrc);
+	xlrec.blkno = current->blkno;
+	xlrec.offnum = current->offnum;
+
+	xlrec.blknoNew = InvalidBlockNumber;
+	xlrec.blknoParent = InvalidBlockNumber;
+	xlrec.newPage = false;
+
+	if (PageGetFreeSpace(current->page) >= 
+			newInnerTuple->size - innerTuple->size)
+	{
+		/*
+		 * replace by new version
+		 */
+		START_CRIT_SECTION();
+
+		PageIndexTupleDelete(current->page, current->offnum);
+		current->offnum = PageAddItem(current->page, (Item)newInnerTuple, newInnerTuple->size,
+										current->offnum, false, false);
+		Assert(current->offnum != InvalidOffsetNumber);
+		Assert(xlrec.offnum == current->offnum);
+
+		SpGistSetLastUsedPage(index, current->buffer);
+		MarkBufferDirty(current->buffer);
+
+		if (RelationNeedsWAL(index))
+		{
+			XLogRecPtr      recptr;
+
+			recptr = XLogInsert(RM_SPGIST_ID, XLOG_SPGIST_ADD_NODE, rdata);
+
+			PageSetLSN(current->page, recptr);
+			PageSetTLI(current->page, ThisTimeLineID);
+		}
+
+		END_CRIT_SECTION();
+	}
+	else
+	{
+		/* move tuple to another page, and update parent */
+		SpGistFakeTuple	ft;
+		PageDesc		saveCurrent;
+
+		Assert(current->blkno != SPGIST_HEAD_BLKNO);
+		Assert(parent->buffer != InvalidBuffer);
+
+		saveCurrent = *current;
+	
+		xlrec.blknoParent = BufferGetBlockNumber(parent->buffer);
+		xlrec.offnumParent = parent->offnum;
+		xlrec.nodeI = parent->node;
+
+		/*
+		 * search new buffer with the same parity
+		 */
+		current->buffer = SpGistGetBuffer(index, ENCODE_INNER_PARITY(current->blkno), 
+										newInnerTuple->size + sizeof(ItemIdData), &xlrec.newPage);
+
+		current->page = BufferGetPage(current->buffer);
+
+		xlrec.blknoNew = current->blkno = BufferGetBlockNumber(current->buffer);
+
+		Assert(xlrec.blknoNew != xlrec.blknoParent);
+		ACCEPT_RDATA_BUFFER(current->buffer, 3);
+		ACCEPT_RDATA_BUFFER(parent->buffer, 4);
+
+		START_CRIT_SECTION();
+
+		xlrec.offnumNew = current->offnum = SpGistPageAddNewItem(state, current->page, 
+												(Item)newInnerTuple, newInnerTuple->size, NULL);
+		Assert(current->offnum != InvalidOffsetNumber);
+
+		MarkBufferDirty(current->buffer);
+
+		saveNodeLink(index, parent, current->blkno, current->offnum);
+
+		/*
+		 * Update old place. Note, for concurrency reason
+		 * we need to store redirection tuple instead of dead
+		 */
+		ft = spgFormFakeTuple(state,
+								state->isBuild ? InvalidBlockNumber : current->blkno,
+								state->isBuild ? InvalidOffsetNumber : current->offnum);
+
+		/*
+		 * replace old tuple with empty new. We could not delete
+		 * tuple to prevent wrong links from another parents.
+		 */
+		PageIndexTupleDelete(saveCurrent.page, saveCurrent.offnum);
+		saveCurrent.offnum = PageAddItem(saveCurrent.page, (Item)ft, ft->size,
+										saveCurrent.offnum, false, false);
+		Assert(saveCurrent.offnum != InvalidOffsetNumber);
+		if (state->isBuild)
+		{
+			SpGistPageGetOpaque(saveCurrent.page)->nDead++;
+			xlrec.setRedirect = false;
+		}
+		else
+		{
+			SpGistPageGetOpaque(saveCurrent.page)->nRedirection++;
+			xlrec.setRedirect = true;;
+		}
+
+		MarkBufferDirty(saveCurrent.buffer);
+
+		if (RelationNeedsWAL(index))
+		{
+			XLogRecPtr      recptr;
+
+			recptr = XLogInsert(RM_SPGIST_ID, XLOG_SPGIST_ADD_NODE, rdata);
+
+			parent->page = BufferGetPage(parent->buffer);
+			PageSetLSN(current->page, recptr);
+			PageSetTLI(current->page, ThisTimeLineID);
+			PageSetLSN(parent->page, recptr);
+			PageSetTLI(parent->page, ThisTimeLineID);
+			PageSetLSN(saveCurrent.page, recptr);
+			PageSetTLI(saveCurrent.page, ThisTimeLineID);
+			
+		}
+
+		END_CRIT_SECTION();
+
+		if (parent->buffer != saveCurrent.buffer)
+			UnlockReleaseBuffer(saveCurrent.buffer);
+	}
+
+	parent->page = NULL;
+}
+
+static void
+spgSplitNodeAction(Relation index, SpGistState *state, SpGistInnerTuple innerTuple, 
+					PageDesc *current, spgChooseOut *out)
+{
+	SpGistInnerTuple	prefixTuple, postfixTuple;
+	IndexTuple			*nodes,
+						node;
+	bool				isnull = false;
+	BlockNumber			postfixBlkno;
+	OffsetNumber		postfixOffset;
+	int					i;
+	XLogRecData			rdata[5];
+	spgxlogSplitTuple	xlrec;
+	Buffer				newBuffer = InvalidBuffer;
+
+	/*
+	 * form pointer frim prefixTuple to postfixTuples
+	 */
+	node = index_form_tuple(state->nodeTupDesc, 
+							&out->result.splitTuple.nodeDatum, &isnull);
+
+	prefixTuple = spgFormInnerTuple(state,  
+									out->result.splitTuple.prefixHasPrefix,
+									out->result.splitTuple.prefixPrefixDatum,
+									1, &node);
+
+	if (prefixTuple->size > innerTuple->size)
+		elog(ERROR, "prefix of key could not be greater than key");
+
+	/*
+	 * all nodes of innterTuple will be moved to postfix
+	 */
+	nodes = palloc(sizeof(IndexTuple) * innerTuple->nNodes);
+	SGITITERATE(innerTuple, i, node)
+		nodes[i] = node;
+
+	postfixTuple = spgFormInnerTuple(state,
+									out->result.splitTuple.postfixHasPrefix,
+									out->result.splitTuple.postfixPrefixDatum,
+									innerTuple->nNodes, nodes);
+	postfixTuple->allTheSame = innerTuple->allTheSame;
+
+	xlrec.node = index->rd_node;
+	ACCEPT_RDATA_DATA(&xlrec, sizeof(xlrec), 0);
+	ACCEPT_RDATA_DATA(prefixTuple, prefixTuple->size, 1);
+	ACCEPT_RDATA_DATA(postfixTuple, postfixTuple->size, 2);
+	ACCEPT_RDATA_BUFFER(current->buffer, 3);
+
+	if (SpGistPageGetFreeSpace(state, current->page, 1) < 
+			prefixTuple->size /* replaces innerTuple */ +
+			postfixTuple->size + sizeof(ItemIdData) /* it will be a new tuple */ - 
+			innerTuple->size)
+	{
+		/*
+		 * Choose page with next triple parity because postfix tuple is a child
+		 * of prefix one
+		 */
+		newBuffer = SpGistGetBuffer(index, ENCODE_INNER_PARITY(current->blkno + 1),
+									postfixTuple->size + sizeof(ItemIdData), &xlrec.newPage);
+		ACCEPT_RDATA_BUFFER(newBuffer, 4);
+	}
+
+	START_CRIT_SECTION();
+
+	/*
+	 * Replace old tuple by prefix tuple
+	 */
+	PageIndexTupleDelete(current->page, current->offnum);
+	xlrec.offnumPrefix = current->offnum = PageAddItem(current->page, (Item)prefixTuple, 
+									prefixTuple->size, current->offnum, false, false);
+	Assert(current->offnum != InvalidOffsetNumber);
+	xlrec.blknoPrefix = BufferGetBlockNumber(current->buffer);
+
+	/*
+	 * put postfix tuple
+	 */
+	if (newBuffer == InvalidBuffer)
+	{
+		xlrec.blknoPostfix = postfixBlkno = current->blkno;
+		xlrec.offnumPostfix = postfixOffset = SpGistPageAddNewItem(state, current->page, 
+												(Item)postfixTuple, postfixTuple->size, NULL);
+		Assert(postfixOffset != InvalidOffsetNumber);
+	}
+	else
+	{
+		xlrec.blknoPostfix = postfixBlkno = BufferGetBlockNumber(newBuffer);
+
+		xlrec.offnumPostfix = postfixOffset = SpGistPageAddNewItem(state, BufferGetPage(newBuffer), 
+												(Item)postfixTuple, postfixTuple->size, NULL);
+		Assert(postfixOffset != InvalidOffsetNumber);
+		MarkBufferDirty(newBuffer);
+	}
+
+	/*
+	 * and set pointer in both stored and in-memory prefix tuple to postfix tuple
+	 */
+	prefixTuple = (SpGistInnerTuple) PageGetItem(current->page,
+										PageGetItemId(current->page, current->offnum));
+	updateNodeLink(prefixTuple, 0, postfixBlkno, postfixOffset);
+
+	MarkBufferDirty(current->buffer);
+
+	if (RelationNeedsWAL(index))
+	{
+		XLogRecPtr		recptr;
+
+		recptr = XLogInsert(RM_SPGIST_ID, XLOG_SPGIST_SPLIT_TUPLE, rdata);
+
+		PageSetLSN(current->page, recptr);
+		PageSetTLI(current->page, ThisTimeLineID);
+
+		if (newBuffer != InvalidBuffer)
+		{
+			PageSetLSN(BufferGetPage(newBuffer), recptr);
+			PageSetTLI(BufferGetPage(newBuffer), ThisTimeLineID);
+		}
+	}
+
+	END_CRIT_SECTION();
+
+	SpGistSetLastUsedPage(index, current->buffer);
+
+	if (newBuffer != InvalidBuffer) {
+		SpGistSetLastUsedPage(index, newBuffer);
+		UnlockReleaseBuffer(newBuffer);
+	}
+}
+
+void
+spgdoinsert(Relation index, SpGistState *state, ItemPointer heapPtr, Datum datum)
+{
+	int				level = 0;
+	Datum			leafDatum = datum;
+	bool			leafDatumIsNull = false;
+	int				leafSize = 0;
+	PageDesc		current,
+					parent;
+
+	/*
+	 * XXX it could be changed in future by splitting value for a several inner tuple
+	 * with non-empty prefix.
+	 *
+	 * Right now it's possible to store long value if we have already suitable path in tree
+	 * but allowing to store such values occures dependency insertion from order.
+	 */
+	if (getTypeLength(&state->attType, datum) + SGLTHDRSZ + sizeof(ItemIdData) > SPGIST_PAGE_CAPACITY)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+			errmsg("index row size %lu exceeds maximum %lu for index \"%s\"",
+					(unsigned long) (getTypeLength(&state->attType, datum) + SGLTHDRSZ),
+					(unsigned long) (SPGIST_PAGE_CAPACITY - sizeof(ItemIdData)),
+					RelationGetRelationName(index)),
+			errhint("Values larger than a buffer page cannot be indexed.\n")));
+
+	current.blkno = SPGIST_HEAD_BLKNO;
+	current.buffer = InvalidBuffer;
+	current.page = NULL;
+	current.offnum = FirstOffsetNumber;
+	current.node = -1;
+
+	parent.blkno = InvalidBlockNumber;
+	parent.buffer = InvalidBuffer;
+	parent.page = NULL;
+	parent.offnum = InvalidOffsetNumber;
+	parent.node = -1;
+
+	for(;;) {
+		bool	isNew = false;
+
+		if (current.blkno == InvalidBlockNumber)
+		{
+			/*
+			 * create a leaf page
+			 */
+			current.buffer = SpGistGetBuffer(index, GBUF_LEAF, leafSize, &isNew);
+			current.blkno = BufferGetBlockNumber(current.buffer);
+		}
+		else if (parent.buffer == InvalidBuffer || current.blkno != BufferGetBlockNumber(parent.buffer))
+		{
+			current.buffer = ReadBuffer(index, current.blkno);
+			LockBuffer(current.buffer, BUFFER_LOCK_EXCLUSIVE);
+		}
+		else
+		{
+			/*
+			 * inner tuple could be stored on the same page as parent one
+			 */
+			current.buffer = parent.buffer;
+		}
+		current.page = BufferGetPage(current.buffer);
+
+		if (SpGistPageIsLeaf(current.page))
+		{
+			int				nToSplit, sizeToSplit;
+			SpGistLeafTuple	leafTuple = spgFormLeafTuple(state, heapPtr, leafDatum, leafDatumIsNull);
+
+			if (SpGistPageGetFreeSpace(state, current.page, 1) >= leafTuple->size + sizeof(ItemIdData)) {
+				addLeafTuple(index, state, leafTuple, &current, &parent, isNew);
+
+				break; /* go away */
+			} 
+			else if ((sizeToSplit = checkSplitConditions(index, state, &current, &nToSplit)) < SPGIST_PAGE_CAPACITY / 2 && 
+						nToSplit < 64 &&
+						/* warranty it's doable */
+						leafTuple->size + sizeof(ItemIdData) + sizeToSplit <= SPGIST_PAGE_CAPACITY )
+			{
+				moveLeafs(index, state, &current, &parent, leafTuple);
+
+				break; /* go away */
+			} 
+			else 
+			{ /* picksplit */
+				if (doPickSplit(index, state, &current, &parent, leafTuple, &level))
+					break; /* doPickSplit installs new tuples */
+				/*
+				 * current describes new inner tuple
+				 */
+				goto process_inner_tuple;
+			}
+		}
+		else /* non leaf */
+		{
+			SpGistInnerTuple innerTuple;
+			spgChooseIn		in;
+			spgChooseOut	out;
+			IndexTuple		node;
+			int				i;
+
+			/*
+			 * spgAddNode and spgSplitTuple will return here to 
+			 * do common work with spgMatchNode
+			 */
+process_inner_tuple:
+			innerTuple = (SpGistInnerTuple) PageGetItem(current.page,
+														PageGetItemId(current.page, current.offnum));
+			in.datum = datum;
+			in.level = level;
+			in.allTheSame = !!innerTuple->allTheSame;
+			in.hasPrefix = !!innerTuple->prefixSize;
+			in.prefixDatum = SGITDATUM(innerTuple, state);
+
+			memset(&out, 0, sizeof(out));
+			/* out.resultType = spgMatchNode;  == 0 */
+
+			if (in.allTheSame == false)
+			{
+				in.nNodes = innerTuple->nNodes;
+				in.nodeDatums = palloc(sizeof(Datum) * in.nNodes);
+				SGITITERATE(innerTuple, i, node)
+				{
+					bool	isnull;
+
+					in.nodeDatums[i] = index_getattr(node, 1, state->nodeTupDesc, &isnull);
+				}
+			}
+			else
+			{
+				in.nNodes = 0;
+				in.nodeDatums = NULL;
+			}
+
+			FunctionCall2(
+				&state->chooseFn,
+				PointerGetDatum(&in),
+				PointerGetDatum(&out)
+			);
+
+			if (in.allTheSame)
+			{
+				/*
+				 * for in.allTheSame == true answer could not be a spgAddNode,
+				 * spgMatchNode means exact match without following suffix
+				 */
+				Assert(out.resultType != spgAddNode);
+				if (out.resultType == spgMatchNode)
+				{
+					out.result.matchNode.nodeN = random() % innerTuple->nNodes;
+					leafDatumIsNull = true;
+				}
+			}
+
+			switch(out.resultType) 
+			{
+				case spgMatchNode:
+					spgMatchNodeAction(state, innerTuple, &current, &parent, out.result.matchNode.nodeN);
+
+					level += out.result.matchNode.levelAdd;
+					leafDatum = out.result.matchNode.restDatum;
+					leafSize = SGLTHDRSZ + sizeof(ItemIdData) + 
+						((leafDatumIsNull) ? 0 : getTypeLength(&state->attType, leafDatum) + sizeof(ItemIdData)); 
+
+					break;
+				case spgAddNode:
+					spgAddNodeAction(index, state, innerTuple, &current, &parent, 
+									out.result.addNode.nodeN, out.result.addNode.nodeDatum);
+
+					/* actually, we will go to spgMatchNode case */
+					goto process_inner_tuple;
+					break;
+				case spgSplitTuple:
+					spgSplitNodeAction(index, state, innerTuple, &current, &out); 
+
+					/* actually, we will go to spgMatchNode case */
+					goto process_inner_tuple;
+					break;
+				default:
+					elog(ERROR, "Unknown choose result");
+			}
+		}
+	} /* for */
+}
diff --git a/src/backend/access/spgist/spginsert.c b/src/backend/access/spgist/spginsert.c
new file mode 100644
index 0000000..cc12c51
--- /dev/null
+++ b/src/backend/access/spgist/spginsert.c
@@ -0,0 +1,158 @@
+/*-------------------------------------------------------------------------
+ *
+ * spgdoinsert.c
+ *    creation routines
+ *
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *          src/backend/access/spgist/spginsert.c
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/spgist_private.h"
+#include "catalog/index.h"
+#include "miscadmin.h"
+#include "storage/bufmgr.h"
+#include "storage/indexfsm.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+
+typedef struct
+{
+	SpGistState      spgstate;
+	MemoryContext   tmpCtx;
+} SpGistBuildState;
+
+Datum
+spgbuildempty(PG_FUNCTION_ARGS)
+{
+	elog(ERROR, "unsupported");
+	PG_RETURN_VOID();
+}
+
+static void
+spgistBuildCallback(Relation index, HeapTuple htup, Datum *values,
+                    bool *isnull, bool tupleIsAlive, void *state)
+{
+	SpGistBuildState *buildstate = (SpGistBuildState*)state;
+
+	if (*isnull == false)
+	{
+		MemoryContext oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
+
+		spgdoinsert(index, &buildstate->spgstate, &htup->t_self, *values);
+
+		MemoryContextSwitchTo(oldCtx);
+		MemoryContextReset(buildstate->tmpCtx);
+	}
+}
+
+Datum
+spgbuild(PG_FUNCTION_ARGS)
+{
+	Relation    heap = (Relation) PG_GETARG_POINTER(0);
+	Relation    index = (Relation) PG_GETARG_POINTER(1);
+	IndexInfo  *indexInfo = (IndexInfo *) PG_GETARG_POINTER(2);
+	IndexBuildResult *result;
+	double      reltuples;
+	SpGistBuildState buildstate;
+	Buffer      MetaBuffer, buffer;
+
+	if (RelationGetNumberOfBlocks(index) != 0)
+		elog(ERROR, "index \"%s\" already contains data",
+					RelationGetRelationName(index));
+
+	/* initialize the meta page */
+	MetaBuffer = SpGistNewBuffer(index);
+	buffer = SpGistNewBuffer(index);
+
+	START_CRIT_SECTION();
+	SpGistInitMetabuffer(MetaBuffer);
+	MarkBufferDirty(MetaBuffer);
+	SpGistInitBuffer(buffer, SPGIST_LEAF);
+	MarkBufferDirty(buffer);
+
+	if (RelationNeedsWAL(index))
+	{
+		XLogRecPtr	recptr;
+		XLogRecData	rdata;
+
+		rdata.data = (char *) &(index->rd_node);
+		rdata.len = sizeof(RelFileNode);
+		rdata.buffer = InvalidBuffer;
+		rdata.next = NULL;
+
+		recptr = XLogInsert(RM_SPGIST_ID, XLOG_SPGIST_CREATE_INDEX, &rdata);
+		PageSetLSN(BufferGetPage(MetaBuffer), recptr);
+		PageSetTLI(BufferGetPage(MetaBuffer), ThisTimeLineID);
+		PageSetLSN(BufferGetPage(buffer), recptr);
+		PageSetTLI(BufferGetPage(buffer), ThisTimeLineID);
+	}
+		
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(MetaBuffer);
+	UnlockReleaseBuffer(buffer);
+
+	initSpGistState(&buildstate.spgstate, index);
+
+	buildstate.tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
+											"SpGist build temporary context",
+											ALLOCSET_DEFAULT_MINSIZE,
+											ALLOCSET_DEFAULT_INITSIZE,
+											ALLOCSET_DEFAULT_MAXSIZE);
+
+
+	buildstate.spgstate.isBuild = true;
+	reltuples = IndexBuildHeapScan(heap, index, indexInfo, true,
+									spgistBuildCallback, (void *) &buildstate);
+	MemoryContextDelete(buildstate.tmpCtx);
+	SpGistUpdateMetaPage(index);
+
+	result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult));
+	result->heap_tuples = result->index_tuples = reltuples;
+
+	PG_RETURN_POINTER(result);
+}
+
+Datum
+spginsert(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);
+	IndexUniqueCheck checkUnique = (IndexUniqueCheck) PG_GETARG_INT32(5);
+#endif
+	SpGistState       spgstate;
+	MemoryContext   oldCtx;
+	MemoryContext   insertCtx;
+
+	insertCtx = AllocSetContextCreate(CurrentMemoryContext,
+										"SpGist insert temporary context",
+										ALLOCSET_DEFAULT_MINSIZE,
+										ALLOCSET_DEFAULT_INITSIZE,
+										ALLOCSET_DEFAULT_MAXSIZE);
+	oldCtx = MemoryContextSwitchTo(insertCtx);
+
+	initSpGistState(&spgstate, index);
+
+	if (*isnull == false);
+		spgdoinsert(index, &spgstate, ht_ctid, *values);
+
+	MemoryContextSwitchTo(oldCtx);
+	MemoryContextDelete(insertCtx);
+
+	SpGistUpdateMetaPage(index);
+
+	PG_RETURN_BOOL(false);
+}
+
diff --git a/src/backend/access/spgist/spgkdtreeproc.c b/src/backend/access/spgist/spgkdtreeproc.c
new file mode 100644
index 0000000..17280ad
--- /dev/null
+++ b/src/backend/access/spgist/spgkdtreeproc.c
@@ -0,0 +1,282 @@
+/*-------------------------------------------------------------------------
+ *
+ * spgkdtreeproc.c
+ *    implementation of k-d-tree over points for SP-GiST
+ *
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *          src/backend/access/spgist/spgquadtreeproc.c
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "access/gist.h" /* strategies */
+#include "access/spgist.h"
+#include "catalog/pg_type.h"
+#include "utils/geo_decls.h"
+#include "utils/builtins.h"
+
+Datum
+spg_kd_config(PG_FUNCTION_ARGS)
+{
+    SpGistOpClassProp   *cfg = palloc(sizeof(*cfg));
+
+	cfg->prefixType = FLOAT8OID;
+	cfg->nodeType = INT2OID;
+	PG_RETURN_POINTER(cfg);
+}
+
+static int
+getSide(double coord, bool isX, Point *tst) {
+	double	tstcoord = (isX) ? tst->x : tst->y;
+
+	if (FPeq(coord, tstcoord))
+		return 0;
+	return (FPgt(coord, tstcoord)) ? 1 : -1;
+}
+
+Datum
+spg_kd_choose(PG_FUNCTION_ARGS)
+{
+	spgChooseIn     *in = (spgChooseIn*)PG_GETARG_POINTER(0);
+	spgChooseOut    *out = (spgChooseOut*)PG_GETARG_POINTER(1);
+	Point           *inPoint = DatumGetPointP(in->datum),
+					*d;
+	double			coord;
+
+	if (in->allTheSame) {
+		out->resultType = spgMatchNode;
+		/* other fields will be set by core */
+		PG_RETURN_VOID();
+	}
+
+	Assert(in->hasPrefix);
+
+	coord = DatumGetFloat8(in->prefixDatum);
+
+	Assert(in->allTheSame || in->nNodes == 2);
+
+	d = palloc(sizeof(*d));
+	*d = *inPoint;
+
+	out->resultType = spgMatchNode;
+	out->result.matchNode.nodeN = (getSide(coord, in->level % 2, inPoint) > 0) ? 0 : 1;
+	out->result.matchNode.levelAdd = 1;
+	out->result.matchNode.restDatum = PointPGetDatum(d);
+
+	PG_RETURN_VOID();
+}
+
+typedef struct SortedPoint {
+	Point	*p;
+	int 	i;
+} SortedPoint;
+
+static int
+x_cmp(const void *a, const void *b, void *arg)
+{
+    SortedPoint   *pa = (SortedPoint*)a;
+	SortedPoint   *pb = (SortedPoint*)b;
+		   
+	if (pa->p->x == pb->p->x)
+		return 0;
+	return (pa->p->x > pb->p->x) ? 1 : -1;
+}
+
+static int
+y_cmp(const void *a, const void *b, void *arg)
+{
+    SortedPoint   *pa = (SortedPoint*)a;
+	SortedPoint   *pb = (SortedPoint*)b;
+		   
+	if (pa->p->y == pb->p->y)
+		return 0;
+	return (pa->p->y > pb->p->y) ? 1 : -1;
+}
+
+
+Datum
+spg_kd_picksplit(PG_FUNCTION_ARGS)
+{
+    spgPickSplitIn  *in = (spgPickSplitIn*)PG_GETARG_POINTER(0);
+	spgPickSplitOut *out = (spgPickSplitOut*)PG_GETARG_POINTER(1);
+	int             i;
+	SortedPoint    	*sorted;
+	double			coord;
+
+	sorted = palloc(sizeof(*sorted) * in->nTuples);
+	for(i=0; i<in->nTuples; i++)
+	{
+		sorted[i].p = DatumGetPointP(in->datums[i]);
+		sorted[i].i = i;
+	}
+
+	qsort_arg(sorted, in->nTuples, sizeof(*sorted), (in->level) % 2 ? x_cmp : y_cmp, NULL);
+	coord = ((in->level) % 2) ? sorted[in->nTuples>>1].p->x : sorted[in->nTuples>>1].p->y;
+
+	out->hasPrefix = true;
+	out->prefixDatum = Float8GetDatum(coord);
+
+	out->nNodes = 2;
+	out->nodeDatums = palloc(sizeof(Datum) * 2);
+	for(i=0; i<2; i++)
+		out->nodeDatums[i] = Int16GetDatum((int2)i);
+
+	out->mapTuplesToNodes = palloc(sizeof(int) * in->nTuples);
+	out->leafTupleDatums = palloc(sizeof(Datum) * in->nTuples);
+
+	for(i=0; i<in->nTuples; i++)
+	{
+		int		n = sorted[i].i;
+		Point   *p = DatumGetPointP(in->datums[n]), *op;
+
+		out->mapTuplesToNodes[ n ] = (i <= (in->nTuples>>1)) ? 0 : 1;
+
+		op = palloc(sizeof(*op));
+		*op = *p;
+		out->leafTupleDatums[ n ] = PointPGetDatum(op);
+	}
+
+	out->addLevel = 1;
+	out->allTheSame = false;
+	PG_RETURN_VOID();
+}
+
+/*
+ * spg_kd_leaf_consistent() is the same as spg_quad_leaf_consistent()
+ */
+
+Datum
+spg_kd_inner_consistent(PG_FUNCTION_ARGS)
+{
+    spgInnerConsistentIn    *in = (spgInnerConsistentIn*)PG_GETARG_POINTER(0);
+	spgInnerConsistentOut   *out = (spgInnerConsistentOut*)PG_GETARG_POINTER(1);
+	Point                   *query;
+	BOX                     *boxQuery;
+	double					coord;
+
+	query = DatumGetPointP(in->query);
+	boxQuery = DatumGetBoxP(in->query);
+	Assert(in->hasPrefix);
+	coord = DatumGetFloat8(in->prefixDatum);
+
+	out->levelAdd = 1;
+	out->nodeNumbers = palloc(sizeof(*out->nodeNumbers) * 2);
+	out->nNodes = 0;
+
+	switch(in->strategy)
+	{
+		case RTLeftStrategyNumber:
+			out->nNodes = 1;
+			out->nodeNumbers[0] = 0;
+
+			if ((in->level % 2) == 0 || FPgt(query->x, coord))
+			{
+				out->nodeNumbers[1] = 1;
+				out->nNodes++;
+			}
+			break;
+		case RTRightStrategyNumber:
+			out->nNodes = 1;
+			out->nodeNumbers[0] = 1;
+
+			if ((in->level % 2) == 0 || FPlt(query->x, coord))
+			{
+				out->nodeNumbers[1] = 0;
+				out->nNodes++;
+			}
+			break;
+		case RTSameStrategyNumber:
+			if (in->level % 2)
+			{
+				if (FPle(query->x, coord))
+				{
+					out->nodeNumbers[out->nNodes] = 0;
+					out->nNodes++;
+				}
+				if (FPge(query->x, coord))
+				{
+					out->nodeNumbers[out->nNodes] = 1;
+					out->nNodes++;
+				}
+			}
+			else
+			{
+				if (FPle(query->y, coord))
+				{
+					out->nodeNumbers[out->nNodes] = 0;
+					out->nNodes++;
+				}
+				if (FPge(query->y, coord))
+				{
+					out->nodeNumbers[out->nNodes] = 1;
+					out->nNodes++;
+				}
+			}
+			break;
+		case RTBelowStrategyNumber:
+			out->nNodes = 1;
+			out->nodeNumbers[0] = 0;
+
+			if ((in->level % 2) == 1 || FPgt(query->y, coord))
+			{
+				out->nodeNumbers[1] = 1;
+				out->nNodes++;
+			}
+			break;
+		case RTAboveStrategyNumber:
+			out->nNodes = 1;
+			out->nodeNumbers[0] = 1;
+
+			if ((in->level % 2) == 1 || FPlt(query->y, coord))
+			{
+				out->nodeNumbers[1] = 0;
+				out->nNodes++;
+			}
+			break;
+		case RTContainsStrategyNumber:
+		case RTOldContainsStrategyNumber:
+		case RTContainedByStrategyNumber:
+		case RTOldContainedByStrategyNumber:
+			out->nNodes = 1;
+
+			if (in->level % 2)
+			{
+				if (FPlt(boxQuery->high.x, coord))
+					out->nodeNumbers[0] = 0;
+				else if (FPgt(boxQuery->low.x, coord))
+					out->nodeNumbers[0] = 1;
+				else
+				{
+					out->nodeNumbers[0] = 0;
+					out->nodeNumbers[1] = 1;
+					out->nNodes = 2;
+				}
+			}
+			else
+			{
+				if (FPlt(boxQuery->high.y, coord))
+					out->nodeNumbers[0] = 0;
+				else if (FPgt(boxQuery->low.y, coord))
+					out->nodeNumbers[0] = 1;
+				else
+				{
+					out->nodeNumbers[0] = 0;
+					out->nodeNumbers[1] = 1;
+					out->nNodes = 2;
+				}
+			}
+			break;
+		default:
+			elog(ERROR, "Unknown strategy number: %d", in->strategy);
+	}
+
+	PG_RETURN_VOID();
+}
+
+
diff --git a/src/backend/access/spgist/spgquadtreeproc.c b/src/backend/access/spgist/spgquadtreeproc.c
new file mode 100644
index 0000000..bb5ec43
--- /dev/null
+++ b/src/backend/access/spgist/spgquadtreeproc.c
@@ -0,0 +1,338 @@
+/*-------------------------------------------------------------------------
+ *
+ * spgquadtreeproc.c
+ *    implementation of quad tree over points for SP-GiST
+ *
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *          src/backend/access/spgist/spgquadtreeproc.c
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "access/gist.h" /* strategies */
+#include "access/spgist.h"
+#include "catalog/pg_type.h"
+#include "utils/geo_decls.h"
+#include "utils/builtins.h"
+
+Datum
+spg_quad_config(PG_FUNCTION_ARGS)
+{
+	SpGistOpClassProp	*cfg = palloc(sizeof(*cfg));
+
+	cfg->prefixType = POINTOID;
+	cfg->nodeType = INT2OID;
+	PG_RETURN_POINTER(cfg);
+}
+
+#define SPTEST(f, c, t)					\
+	DatumGetBool(DirectFunctionCall2(	\
+				point_##f,				\
+				PointPGetDatum(c),		\
+				PointPGetDatum(t)))		\
+
+/*
+ * Quadrants:
+ *
+ *   4  |  1     
+ *  ----+-----
+ *   3  |  2   
+ */
+
+static int2
+getQuadrant(Point *centroid, Point *tst)
+{
+	if ( (SPTEST(above, tst, centroid) || SPTEST(horiz, tst, centroid)) &&
+		 (SPTEST(right, tst, centroid) || SPTEST(vert, tst, centroid)) )
+		return 1;
+
+	if ( SPTEST(below, tst, centroid) &&
+		 (SPTEST(right, tst, centroid) || SPTEST(vert, tst, centroid)) )
+		return 2;
+
+	if ( (SPTEST(below, tst, centroid) || SPTEST(horiz, tst, centroid))&&
+		 SPTEST(left, tst, centroid) )
+		return 3;
+
+	if ( SPTEST(above, tst, centroid) && SPTEST(left, tst, centroid) )
+		return 4;
+
+	elog(ERROR, "getQuadrant: could not be here");
+	return 0;
+}
+
+
+Datum
+spg_quad_choose(PG_FUNCTION_ARGS)
+{
+	spgChooseIn		*in = (spgChooseIn*)PG_GETARG_POINTER(0);
+	spgChooseOut	*out = (spgChooseOut*)PG_GETARG_POINTER(1);
+	Point			*inPoint = DatumGetPointP(in->datum),
+					*centroid;
+	Point			*d;
+
+	if (in->allTheSame) { 
+		out->resultType = spgMatchNode;
+		/* other fields will be set by core */
+		PG_RETURN_VOID();
+	}
+
+	Assert(in->hasPrefix);
+
+	centroid = DatumGetPointP(in->prefixDatum);
+
+	Assert(in->allTheSame || in->nNodes == 4);
+
+	d = palloc(sizeof(*d));
+	*d = *inPoint;
+
+	out->resultType = spgMatchNode;
+	out->result.matchNode.nodeN = getQuadrant(centroid, inPoint) - 1;
+	out->result.matchNode.levelAdd = 0;
+	out->result.matchNode.restDatum = PointPGetDatum(d);
+
+	PG_RETURN_VOID();
+}
+
+#ifdef USE_MEDIAN
+static int
+x_cmp(const void *a, const void *b, void *arg)
+{
+	Point	*pa = *(Point**)a;
+	Point	*pb = *(Point**)b;
+
+	if (pa->x == pb->x)
+		return 0;
+	return (pa->x > pb->x) ? 1 : -1;
+}
+
+static int
+y_cmp(const void *a, const void *b, void *arg)
+{
+	Point	*pa = *(Point**)a;
+	Point	*pb = *(Point**)b;
+
+	if (pa->y == pb->y)
+		return 0;
+	return (pa->y > pb->y) ? 1 : -1;
+}
+#endif
+
+Datum
+spg_quad_picksplit(PG_FUNCTION_ARGS)
+{
+	spgPickSplitIn	*in = (spgPickSplitIn*)PG_GETARG_POINTER(0);
+	spgPickSplitOut	*out = (spgPickSplitOut*)PG_GETARG_POINTER(1);
+	int 			i;
+	Point			*centroid;
+	int				nQuadrants[] = {0, 0, 0, 0};
+
+#ifdef USE_MEDIAN
+	Point			**sorted;
+
+	sorted = palloc(sizeof(*sorted) * in->nTuples);
+	for(i=0; i<in->nTuples; i++)
+		sorted[i] = DatumGetPointP(in->datums[i]);
+
+	centroid = palloc(sizeof(*centroid));
+
+	/*
+	 * count centroid as median
+	 */
+	qsort_arg(sorted, in->nTuples, sizeof(*sorted), x_cmp, NULL);
+	centroid->x = sorted[i>>1]->x;
+	qsort_arg(sorted, in->nTuples, sizeof(*sorted), y_cmp, NULL);
+	centroid->y = sorted[i>>1]->y;
+#else
+	centroid = palloc0(sizeof(*centroid));
+
+	for(i=0; i<in->nTuples; i++)
+	{
+		centroid->x += DatumGetPointP(in->datums[i])->x;
+		centroid->y += DatumGetPointP(in->datums[i])->y;
+	}
+	
+	centroid->x /= in->nTuples;
+	centroid->y /= in->nTuples;
+#endif
+
+
+	out->hasPrefix = true;
+	out->prefixDatum = PointPGetDatum(centroid);
+
+	out->nNodes = 4;
+	out->nodeDatums = palloc(sizeof(Datum) * 4);
+	for(i=0; i<4; i++)
+		out->nodeDatums[i] = Int16GetDatum((int2)i);
+	out->mapTuplesToNodes = palloc(sizeof(int) * in->nTuples);
+	out->leafTupleDatums = palloc(sizeof(Datum) * in->nTuples);
+
+	for(i=0; i<in->nTuples; i++)
+	{
+		Point   *p = DatumGetPointP(in->datums[i]), *op;
+		int2	quadrant = getQuadrant(centroid, p) - 1;
+
+		op = palloc(sizeof(*op));
+		*op = *p;
+
+		out->leafTupleDatums[ i ] = PointPGetDatum(op);
+		out->mapTuplesToNodes[ i ] = quadrant;
+		nQuadrants[quadrant]++;
+	}
+
+	out->allTheSame = false;
+	for(i=0; i<4; i++)
+		if (nQuadrants[i] == in->nTuples)
+			out->allTheSame = true;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+spg_quad_leaf_consistent(PG_FUNCTION_ARGS)
+{
+	/*int		level = PG_GETARG_INT32(0);*/
+	Point	*query = PG_GETARG_POINT_P(1);
+	StrategyNumber    strategy = PG_GETARG_INT32(2);
+	Point	*datum = PG_GETARG_POINT_P(3);
+	bool	res = 0;
+
+	switch(strategy)
+	{
+		case RTLeftStrategyNumber:
+			res = SPTEST(left, datum, query);
+			break;
+		case RTRightStrategyNumber:
+			res = SPTEST(right, datum, query);
+			break;
+		case RTSameStrategyNumber:
+			res = SPTEST(eq, datum, query);
+			break;
+		case RTBelowStrategyNumber:
+			res = SPTEST(below, datum, query);
+			break;
+		case RTAboveStrategyNumber:
+			res = SPTEST(above, datum, query);
+			break;
+		case RTContainsStrategyNumber:
+		case RTOldContainsStrategyNumber:
+		case RTContainedByStrategyNumber:
+		case RTOldContainedByStrategyNumber:
+			res = DatumGetBool(DirectFunctionCall2(box_contain_pt, 
+													PG_GETARG_DATUM(1),
+													PG_GETARG_DATUM(3)));
+			break;
+		default:
+			elog(ERROR, "Unknown strategy number: %d", strategy);
+
+	}
+
+	PG_RETURN_BOOL(res);
+}
+
+static void
+setNodes(spgInnerConsistentOut *out, bool isAll, int first, int second)
+{
+	if (isAll)
+	{
+		out->nNodes = 4;
+		out->nodeNumbers[0] = 0;
+		out->nodeNumbers[1] = 1;
+		out->nodeNumbers[2] = 2;
+		out->nodeNumbers[3] = 3;
+	}
+	else
+	{
+		out->nNodes = 2;
+		out->nodeNumbers[0] = first - 1;
+		out->nodeNumbers[1] = second - 1;
+	}
+}
+
+
+Datum
+spg_quad_inner_consistent(PG_FUNCTION_ARGS)
+{
+	spgInnerConsistentIn	*in = (spgInnerConsistentIn*)PG_GETARG_POINTER(0);
+	spgInnerConsistentOut	*out = (spgInnerConsistentOut*)PG_GETARG_POINTER(1);
+	Point					*query,
+							*centroid;
+	BOX						*boxQuery;
+
+	query = DatumGetPointP(in->query);
+	boxQuery = DatumGetBoxP(in->query);
+	Assert(in->hasPrefix);
+	centroid = DatumGetPointP(in->prefixDatum);
+
+	out->levelAdd = 0;
+	out->nodeNumbers = palloc(sizeof(*out->nodeNumbers) * 4);
+
+	switch(in->strategy)
+	{
+		case RTLeftStrategyNumber:
+			setNodes(out, SPTEST(left, centroid, query), 3, 4);
+			break;
+		case RTRightStrategyNumber:
+			setNodes(out, SPTEST(right, centroid, query), 1, 2);
+			break;
+		case RTSameStrategyNumber:
+			out->nNodes = 1;
+			out->nodeNumbers[0] = getQuadrant(centroid, query) - 1;
+			break;
+		case RTBelowStrategyNumber:
+			setNodes(out, SPTEST(below, centroid, query), 2, 3);
+			break;
+		case RTAboveStrategyNumber:
+			setNodes(out, SPTEST(above, centroid, query), 1, 4);
+			break;
+		case RTContainsStrategyNumber:
+		case RTOldContainsStrategyNumber:
+		case RTContainedByStrategyNumber:
+		case RTOldContainedByStrategyNumber:
+			if (DatumGetBool(DirectFunctionCall2(box_contain_pt,
+												PointerGetDatum(boxQuery),
+												PointerGetDatum(centroid))))
+			{
+				setNodes(out, true, 0, 0);
+			}
+			else
+			{
+				Point 	p;
+				int		i, r = 0;
+
+				p = boxQuery->low;
+				r |= 1 << (getQuadrant(centroid, &p) - 1);
+
+				p.y = boxQuery->high.y;
+				r |= 1 << (getQuadrant(centroid, &p) - 1);
+
+				p = boxQuery->high;
+				r |= 1 << (getQuadrant(centroid, &p) - 1);
+
+				p.x = boxQuery->low.x;
+				r |= 1 << (getQuadrant(centroid, &p) - 1);
+
+				out->nNodes = 0;
+				for(i=0; i<4; i++)
+				{
+					if ( r & (1 << i) )
+					{
+						out->nodeNumbers[ out->nNodes ] = i;
+						out->nNodes++;
+					}
+				}
+			}
+			break;
+		default:
+			elog(ERROR, "Unknown strategy number: %d", in->strategy);
+	}
+
+	PG_RETURN_VOID();
+}
+
diff --git a/src/backend/access/spgist/spgscan.c b/src/backend/access/spgist/spgscan.c
new file mode 100644
index 0000000..21c480a
--- /dev/null
+++ b/src/backend/access/spgist/spgscan.c
@@ -0,0 +1,452 @@
+/*-------------------------------------------------------------------------
+ *
+ * spgscan.c
+ *    routines for scanning SP-GiST indexes
+ *
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *          src/backend/access/spgist/spgscan.c
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "pgstat.h"
+#include "miscadmin.h"
+#include "access/relscan.h"
+#include "access/spgist_private.h"
+#include "storage/bufmgr.h"
+#include "storage/lmgr.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+
+typedef struct TuplePointer {
+	Datum				reconstructedValue;
+	int					level;
+	bool				theSame;
+	bool				recheckTheSame;
+	ItemPointerData		ptr;
+} TuplePointer;
+
+static void
+resetSpGistScanOpaque(SpGistScanOpaque so)
+{
+	TuplePointer *startPtr = palloc(sizeof(TuplePointer));
+
+	ItemPointerSet(&startPtr->ptr, SPGIST_HEAD_BLKNO, FirstOffsetNumber);
+	startPtr->level = 0;
+	startPtr->reconstructedValue = (Datum)0;
+
+	list_free(so->scanStack);
+	so->scanStack = lappend(NIL, startPtr);
+	so->gettuple.nPtrs = so->gettuple.iPtr = 0;
+}
+
+Datum
+spgbeginscan(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;
+	SpGistScanOpaque 	so;
+
+	scan = RelationGetIndexScan(rel, keysz, 0);
+
+	so = (SpGistScanOpaque) palloc0(sizeof(SpGistScanOpaqueData));
+	initSpGistState(&so->state, scan->indexRelation);
+	so->tempCxt = AllocSetContextCreate(CurrentMemoryContext,
+										"SpGist search temporary context",
+										ALLOCSET_DEFAULT_MINSIZE,
+										ALLOCSET_DEFAULT_INITSIZE,
+										ALLOCSET_DEFAULT_MAXSIZE);
+	resetSpGistScanOpaque(so);
+	scan->opaque = so;
+
+	PG_RETURN_POINTER(scan);
+}
+
+Datum
+spgrescan(PG_FUNCTION_ARGS)
+{
+    IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
+	SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque;
+	ScanKey     scankey = (ScanKey) PG_GETARG_POINTER(1);
+
+	if (scankey && scan->numberOfKeys > 0)
+	{
+		memmove(scan->keyData, scankey,
+				scan->numberOfKeys * sizeof(ScanKeyData));
+	}
+
+	resetSpGistScanOpaque(so);
+
+	PG_RETURN_VOID();
+}
+
+Datum
+spgendscan(PG_FUNCTION_ARGS)
+{
+	IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
+	SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque;
+
+	MemoryContextDelete(so->tempCxt);
+
+	PG_RETURN_VOID();
+}
+
+Datum
+spgmarkpos(PG_FUNCTION_ARGS)
+{
+    elog(ERROR, "SpGist does not support mark/restore");
+	PG_RETURN_VOID();
+}
+
+Datum
+spgrestrpos(PG_FUNCTION_ARGS)
+{
+	elog(ERROR, "SpGist does not support mark/restore");
+	PG_RETURN_VOID();
+}
+
+static bool
+spgLeafTest(SpGistScanOpaque so, ScanKeyData *keyData, int numberOfKeys, Datum leafDatum, int level, 
+			Datum	reconstructedValue, bool *recheck)
+{
+	bool 			result = true;
+	MemoryContext	oldCtx;
+	int				i;
+
+	*recheck = false;
+
+	oldCtx = MemoryContextSwitchTo(so->tempCxt);
+	for(i=0; i<numberOfKeys && result; i++)
+	{
+		bool			recheckI = false;
+
+		result = DatumGetBool(FunctionCall6Coll(&so->state.leafConsistentFn,
+											keyData[i].sk_collation,
+											Int32GetDatum(level),
+											keyData[i].sk_argument,
+											Int32GetDatum(keyData[i].sk_strategy),
+											leafDatum,
+											reconstructedValue,
+											PointerGetDatum(&recheckI)));
+		*recheck |= recheckI;
+	}
+	MemoryContextSwitchTo(oldCtx);
+
+	return result;
+}
+
+static void
+spgWalk(Relation index, SpGistScanOpaque so, ScanKeyData *keyData, int numberOfKeys, 
+		bool (*storeRes)(SpGistScanOpaque, ItemPointer, bool))
+{
+	Buffer			buffer = InvalidBuffer;
+	Page			page;
+	TuplePointer	*tuplePtr;
+	bool			continueLoop = true;
+
+	/*
+	 * continueLoop controls will stop after processing page or not.
+	 */
+
+	while(continueLoop)	
+	{
+		/*
+		 * there is no more pages to scan
+		 */
+		if (list_length(so->scanStack) == 0)
+		{
+			if (buffer != InvalidBuffer)
+				UnlockReleaseBuffer(buffer);
+			return;
+		}
+
+		tuplePtr = (TuplePointer*)linitial(so->scanStack);
+		so->scanStack = list_delete_first(so->scanStack);
+
+redirect:
+		if (buffer == InvalidBuffer)
+		{
+			buffer = ReadBuffer(index, ItemPointerGetBlockNumber(&tuplePtr->ptr));
+			LockBuffer(buffer, BUFFER_LOCK_SHARE);
+		} 
+		else if (ItemPointerGetBlockNumber(&tuplePtr->ptr) != BufferGetBlockNumber(buffer))
+		{
+			UnlockReleaseBuffer(buffer);
+			buffer = ReadBuffer(index, ItemPointerGetBlockNumber(&tuplePtr->ptr));
+			LockBuffer(buffer, BUFFER_LOCK_SHARE);
+		}
+		else
+		{
+			/*
+			 * New pointer points to the same page
+			 */
+		}
+
+		page = BufferGetPage(buffer);
+
+		if (SpGistPageIsLeaf(page))
+		{
+			SpGistLeafTuple     leafTuple;
+			OffsetNumber		offset = ItemPointerGetOffsetNumber(&tuplePtr->ptr);
+			bool				recheck = false;
+
+			if (ItemPointerGetBlockNumber(&tuplePtr->ptr) == SPGIST_HEAD_BLKNO)
+			{
+				for(offset=FirstOffsetNumber; offset<=SpGistPageGetMaxOffset(page); offset ++)
+				{
+					leafTuple = (SpGistLeafTuple) PageGetItem(page, PageGetItemId(page, offset));
+					Assert(leafTuple->isDead == 0 && leafTuple->isRedirect == 0);
+					Assert(ItemPointerIsValid(&leafTuple->heapPtr));
+					if (spgLeafTest(so, keyData, numberOfKeys, SGLTDATUM(leafTuple, &so->state), 
+									tuplePtr->level, tuplePtr->reconstructedValue, &recheck))
+						storeRes(so, &leafTuple->heapPtr, recheck);
+				}
+			}
+			else
+			{
+				while(offset != InvalidOffsetNumber)
+				{
+					Assert(offset >= FirstOffsetNumber && offset <= SpGistPageGetMaxOffset(page));
+					leafTuple = (SpGistLeafTuple) PageGetItem(page, PageGetItemId(page, offset));
+					Assert(leafTuple->isDead == 0);
+					if (leafTuple->isRedirect)
+					{
+						/* redirection tuple should be first */
+						Assert(offset == ItemPointerGetOffsetNumber(&tuplePtr->ptr));
+						tuplePtr->ptr = ((SpGistFakeTuple)leafTuple)->pointer;
+						Assert(ItemPointerGetBlockNumber(&tuplePtr->ptr) != SPGIST_METAPAGE_BLKNO);
+						goto redirect;
+					}
+
+					Assert(ItemPointerIsValid(&leafTuple->heapPtr));
+					recheck = tuplePtr->recheckTheSame; 
+					if (tuplePtr->theSame || spgLeafTest(so, keyData, numberOfKeys, SGLTDATUM(leafTuple, &so->state), 
+									tuplePtr->level, tuplePtr->reconstructedValue, &recheck))
+						continueLoop = storeRes(so, &leafTuple->heapPtr, recheck);
+					offset = leafTuple->nextOffset;
+
+				}
+			}
+		}
+		else /* page is inner */
+		{
+			SpGistInnerTuple	innerTuple;
+			OffsetNumber		offset = ItemPointerGetOffsetNumber(&tuplePtr->ptr);
+			IndexTuple			node;
+			int					i;
+
+			innerTuple = (SpGistInnerTuple) PageGetItem(page, PageGetItemId(page, offset));
+			Assert(innerTuple->isDead == 0);
+			
+			if (innerTuple->isRedirect)
+			{
+				tuplePtr->ptr = ((SpGistFakeTuple)innerTuple)->pointer;
+				Assert(ItemPointerGetBlockNumber(&tuplePtr->ptr) != SPGIST_METAPAGE_BLKNO);
+				goto redirect;
+			}
+
+			if (numberOfKeys == 0)
+			{
+				/*
+				 * full index scan
+				 */
+				SGITITERATE(innerTuple, i, node)
+					if (ItemPointerIsValid(&node->t_tid))
+					{
+						TuplePointer	*newPtr = palloc(sizeof(TuplePointer));
+						newPtr->ptr = node->t_tid;
+						newPtr->level = -1;
+						so->scanStack = lcons(newPtr, so->scanStack);
+					}
+			}
+			else
+			{
+				spgInnerConsistentIn	in;
+				spgInnerConsistentOut	out;
+				IndexTuple				*nodes;
+				MemoryContext			oldCtx;
+				int						*andMap;
+				int 					j,
+										levelAdd = -1,
+										nMatches = 0;
+				Datum					*reconstructedValues;
+											
+				in.level = tuplePtr->level;
+				in.reconstructedValue = tuplePtr->reconstructedValue;
+				in.hasPrefix = !!innerTuple->prefixSize;
+				in.nNodes = innerTuple->nNodes;
+				in.prefixDatum = SGITDATUM(innerTuple, &so->state);
+
+				oldCtx = MemoryContextSwitchTo(so->tempCxt);
+				nodes = palloc(sizeof(IndexTuple) * in.nNodes);
+				in.nodeDatums = palloc(sizeof(Datum) * in.nNodes);
+				andMap = palloc0(sizeof(*andMap) * in.nNodes);
+				reconstructedValues = palloc0(sizeof(*reconstructedValues) * in.nNodes);
+
+				SGITITERATE(innerTuple, i, node)
+				{
+					bool    isnull;
+
+					nodes[i] = node;
+					in.nodeDatums[i] = index_getattr(node, 1, so->state.nodeTupDesc, &isnull);
+				}
+			
+				if (innerTuple->allTheSame == false)
+				{
+					tuplePtr->recheckTheSame = false;
+
+					for(j=0; j<numberOfKeys; j++)
+					{
+						in.strategy = keyData[j].sk_strategy;
+						in.query = keyData[j].sk_argument;
+						out.reconstructedValues = NULL;
+
+						FunctionCall2Coll(&so->state.innerConsistentFn,
+										keyData[j].sk_collation,
+										PointerGetDatum(&in),
+										PointerGetDatum(&out));
+
+						nMatches = 0;
+						for(i=0; i<out.nNodes; i++)
+						{
+							andMap[out.nodeNumbers[i]]++;
+							if (andMap[out.nodeNumbers[i]] == j + 1)
+								nMatches++;
+							if (out.reconstructedValues)
+								reconstructedValues[out.nodeNumbers[i]] = out.reconstructedValues[i];
+						}
+
+						if (nMatches == 0)
+							break;
+
+						if (levelAdd < 0)
+							levelAdd = out.levelAdd;
+						else if (levelAdd != out.levelAdd)
+							elog(ERROR, "inconsistent results of two calls of innerConsistentFn on the same node");
+					}
+				}
+				else /* innerTuple->allTheSame */
+				{
+					if (tuplePtr->theSame /* next levels of the same value */ ||
+							spgLeafTest(so, keyData, numberOfKeys, in.prefixDatum, 
+										tuplePtr->level, tuplePtr->reconstructedValue, &tuplePtr->recheckTheSame))
+					{
+						nMatches = 1;
+						for(i=0; i<in.nNodes; i++)
+							andMap[i] = numberOfKeys;
+					}
+				}
+
+				MemoryContextSwitchTo(oldCtx);
+
+				for(i=0; nMatches > 0 && i<in.nNodes; i++)
+				{
+					if (andMap[i] == numberOfKeys && ItemPointerIsValid(&nodes[i]->t_tid))
+					{
+						TuplePointer	*newPtr = palloc(sizeof(TuplePointer));
+
+						newPtr->ptr = nodes[i]->t_tid;
+						newPtr->level = tuplePtr->level + levelAdd;
+						newPtr->reconstructedValue = datumCopy(reconstructedValues[i],
+																so->state.attType.attbyval,
+																so->state.attType.attlen);
+						newPtr->theSame = innerTuple->allTheSame;
+						newPtr->recheckTheSame = tuplePtr->recheckTheSame;
+
+						so->scanStack = lcons(newPtr, so->scanStack);
+					}
+				}
+			}
+		}
+
+		if (so->state.attType.attbyval == false && DatumGetPointer(tuplePtr->reconstructedValue) != NULL)
+			pfree(DatumGetPointer(tuplePtr->reconstructedValue));
+		pfree(tuplePtr);
+		MemoryContextReset(so->tempCxt);
+	}
+
+	if (buffer != InvalidBuffer)
+		UnlockReleaseBuffer(buffer);
+
+	return;
+}
+
+static bool
+storeBitmap(SpGistScanOpaque so, ItemPointer heapPtr, bool recheck)
+{
+	tbm_add_tuples(so->bitmapscan.tbm, heapPtr, 1, recheck);
+	so->bitmapscan.ntids++;
+
+	/*
+	 * Continue walking on tree
+	 */
+	return true;
+}
+
+Datum
+spggetbitmap(PG_FUNCTION_ARGS)
+{
+	IndexScanDesc 			scan = (IndexScanDesc) PG_GETARG_POINTER(0);
+	TIDBitmap  				*tbm = (TIDBitmap *) PG_GETARG_POINTER(1);
+	SpGistScanOpaque 		so = (SpGistScanOpaque) scan->opaque;
+
+	so->bitmapscan.tbm = tbm;
+	so->bitmapscan.ntids = 0;
+
+	spgWalk(scan->indexRelation, so, scan->keyData, scan->numberOfKeys, storeBitmap);
+	
+	PG_RETURN_INT64(so->bitmapscan.ntids);
+}
+
+static bool
+storeGettuple(SpGistScanOpaque so, ItemPointer heapPtr, bool recheck)
+{
+	so->gettuple.heapPtrs[so->gettuple.nPtrs] = *heapPtr;
+	so->gettuple.recheck[so->gettuple.nPtrs] = recheck;
+	so->gettuple.nPtrs++;
+
+	/*
+	 * stop walking on tree when current page is finished
+	 */
+	return false;
+}
+
+Datum
+spggettuple(PG_FUNCTION_ARGS)
+{
+	IndexScanDesc		scan = (IndexScanDesc) PG_GETARG_POINTER(0);
+	ScanDirection		dir = (ScanDirection) PG_GETARG_INT32(1);
+	SpGistScanOpaque	so = (SpGistScanOpaque) scan->opaque;
+
+	if (dir != ForwardScanDirection)
+		elog(ERROR, "SPGiST only supports forward scan direction");
+
+	for(;;)
+	{
+		if (so->gettuple.iPtr < so->gettuple.nPtrs)
+		{
+			/* continuing to return tuples from a leaf page */
+			scan->xs_ctup.t_self = so->gettuple.heapPtrs[so->gettuple.iPtr];
+			scan->xs_recheck = so->gettuple.recheck[so->gettuple.iPtr];
+			so->gettuple.iPtr++;
+			PG_RETURN_BOOL(true);
+		}
+
+		so->gettuple.iPtr = so->gettuple.nPtrs = 0;
+		spgWalk(scan->indexRelation, so, scan->keyData, scan->numberOfKeys, storeGettuple);
+
+		if (so->gettuple.nPtrs == 0)
+			break;
+	}
+
+	PG_RETURN_BOOL(false);
+}
+
diff --git a/src/backend/access/spgist/spgtextproc.c b/src/backend/access/spgist/spgtextproc.c
new file mode 100644
index 0000000..6027bca
--- /dev/null
+++ b/src/backend/access/spgist/spgtextproc.c
@@ -0,0 +1,497 @@
+/*-------------------------------------------------------------------------
+ *
+ * spgtextproc.c
+ *    implementaion of compressed suffix tree 
+ *
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *          src/backend/access/spgist/spgtextproc.c
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "access/spgist.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
+#include "mb/pg_wchar.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+
+Datum
+spg_text_config(PG_FUNCTION_ARGS)
+{
+	SpGistOpClassProp	*cfg = palloc(sizeof(*cfg));
+
+	cfg->prefixType = TEXTOID;
+	cfg->nodeType = CHAROID;
+	PG_RETURN_POINTER(cfg);
+}
+
+/*
+ * forms compressed datum if it's possible
+ */
+static Datum
+formTextDatum(char *data, int datalen)
+{
+	char	*p;
+
+	p = palloc(datalen + VARHDRSZ);
+
+	if (datalen + VARHDRSZ_SHORT <= VARATT_SHORT_MAX)
+	{
+		if (p && datalen)
+			memcpy(p + VARHDRSZ_SHORT, data, datalen);
+		SET_VARSIZE_SHORT(p, datalen + VARHDRSZ_SHORT);
+	}
+	else
+	{
+		memcpy(p + VARHDRSZ, data, datalen);
+		SET_VARSIZE(p, datalen + VARHDRSZ);
+	}
+
+	return PointerGetDatum(p);
+}
+
+static int
+commonPrefix(char *a, char *b, int lena, int lenb)
+{
+	int i = 0;
+
+	while(i < lena && i < lenb && *a == *b)
+	{
+		a++;
+		b++;
+		i++;
+	}
+
+	return i;
+}
+
+static bool
+searchChar(Datum* nodeDatums, int nNodes, uint8 c, int *i)
+{
+	int 	StopLow = 0,
+			StopHigh = nNodes,
+			StopMiddle;
+
+	while(StopLow < StopHigh)
+	{
+		StopMiddle = (StopLow + StopHigh) >> 1;
+	
+		if (c < DatumGetUInt8(nodeDatums[StopMiddle]))
+			StopHigh = StopMiddle;
+		else if (c > DatumGetUInt8(nodeDatums[StopMiddle]))
+			StopLow = StopMiddle + 1;
+		else
+		{
+			*i = StopMiddle;
+			return true;
+		}
+	}
+
+	*i = StopHigh;
+	return false;
+}
+
+Datum
+spg_text_choose(PG_FUNCTION_ARGS)
+{
+	spgChooseIn		*in = (spgChooseIn*)PG_GETARG_POINTER(0);
+	spgChooseOut	*out = (spgChooseOut*)PG_GETARG_POINTER(1);
+	text			*inText = DatumGetTextP(in->datum);
+	int				inSize = VARSIZE_ANY_EXHDR(inText);
+	uint8			nodeChar = '\0';
+	int 			i = 0;
+	int				common = 0;
+
+	if (in->hasPrefix)
+	{
+		text		*prefixText = DatumGetTextP(in->prefixDatum);
+		int			prefixSize = VARSIZE_ANY_EXHDR(prefixText);
+
+		common = commonPrefix(VARDATA_ANY(inText) + in->level,
+							  VARDATA_ANY(prefixText),
+							  inSize - in->level,
+							  prefixSize);
+
+		if (common == prefixSize)
+		{
+			if (inSize - in->level > common)
+				nodeChar = *(uint8*)(VARDATA_ANY(inText) + in->level + common);
+			else
+				nodeChar = '\0';
+		}
+		else
+		{
+			out->resultType = spgSplitTuple;
+
+			if (common == 0)
+			{
+				out->result.splitTuple.prefixHasPrefix = false;
+				out->result.splitTuple.nodeDatum = UInt8GetDatum(*VARDATA_ANY(prefixText));
+			}
+			else
+			{
+				out->result.splitTuple.prefixHasPrefix = true;
+				out->result.splitTuple.prefixPrefixDatum = formTextDatum(VARDATA_ANY(prefixText), common);
+				out->result.splitTuple.nodeDatum = UInt8GetDatum(*(VARDATA_ANY(prefixText) + common));
+			}
+
+			if (prefixSize - common == 1)
+			{
+				out->result.splitTuple.postfixHasPrefix = false;
+			}
+			else
+			{
+				out->result.splitTuple.postfixHasPrefix = true;
+				out->result.splitTuple.postfixPrefixDatum = formTextDatum(VARDATA_ANY(prefixText) + common + 1, 
+																			prefixSize - common - 1);
+			}
+
+			PG_RETURN_VOID();
+		}
+	}
+	else if (inSize > in->level) 
+	{
+		nodeChar = *(uint8*)(VARDATA_ANY(inText) + in->level);
+	}
+	else
+	{
+		nodeChar = '\0';
+	}
+
+	if (in->allTheSame && nodeChar != '\0')
+	{
+		out->resultType = spgSplitTuple;
+
+		out->result.splitTuple.prefixHasPrefix = false;
+		out->result.splitTuple.nodeDatum = UInt8GetDatum('\0');
+
+		out->result.splitTuple.postfixHasPrefix = false;
+	}
+	else if((in->allTheSame && nodeChar == '\0') || searchChar(in->nodeDatums, in->nNodes, nodeChar, &i))
+	{
+		out->resultType = spgMatchNode;
+		out->result.matchNode.nodeN = i;
+		out->result.matchNode.levelAdd = common + 1;
+		out->result.matchNode.restDatum = formTextDatum(VARDATA_ANY(inText) + in->level + common + 1,
+														(nodeChar == '\0') ? 0 : (inSize - in->level - common - 1));
+	}
+	else
+	{
+		out->resultType = spgAddNode;
+		out->result.addNode.nodeDatum = UInt8GetDatum(nodeChar);
+		out->result.addNode.nodeN = i;
+	}
+
+	PG_RETURN_VOID();
+}
+
+typedef struct nodePtr
+{
+	Datum	d;
+	int 	i;
+	uint8	c;
+} nodePtr;
+
+static int
+cmpNodePtr(const void *a, const void *b)
+{
+	if ( ((nodePtr*)a)->c == ((nodePtr*)b)->c )
+		return 0;
+	return ( ((nodePtr*)a)->c > ((nodePtr*)b)->c ) ? 1 : -1;
+}
+
+Datum
+spg_text_picksplit(PG_FUNCTION_ARGS)
+{
+	spgPickSplitIn	*in = (spgPickSplitIn*)PG_GETARG_POINTER(0);
+	spgPickSplitOut	*out = (spgPickSplitOut*)PG_GETARG_POINTER(1);
+	int 			i, common = 0x7fffffff;
+	Datum			op;
+	nodePtr			*nodes;
+
+	for(i=1; i<in->nTuples && common > 0; i++)
+	{
+		int tmp = commonPrefix(VARDATA_ANY(in->datums[0]),
+						  		VARDATA_ANY(in->datums[i]),
+						  		VARSIZE_ANY_EXHDR(in->datums[0]),
+						  		VARSIZE_ANY_EXHDR(in->datums[i]));
+		if ( tmp < common )
+			common = tmp;
+	}
+
+	if(common == 0)
+	{
+		out->hasPrefix = false;
+	}
+	else
+	{
+		out->hasPrefix = true;
+		out->prefixDatum = formTextDatum(VARDATA_ANY(in->datums[0]), common);
+	}
+
+	nodes = palloc(sizeof(*nodes) * in->nTuples);
+	for(i=0; i<in->nTuples; i++)
+	{
+		if (common == VARSIZE_ANY_EXHDR(in->datums[i]))
+			nodes[i].c = '\0';
+		else
+			nodes[i].c = *(uint8*)(VARDATA_ANY(in->datums[i]) + common);
+		nodes[i].i = i;
+		nodes[i].d = in->datums[i];
+	}
+
+	qsort(nodes, in->nTuples, sizeof(*nodes), cmpNodePtr); 
+
+	out->nNodes = 0;
+	out->nodeDatums = palloc(sizeof(Datum) * in->nTuples);
+	out->mapTuplesToNodes = palloc(sizeof(int) * in->nTuples);
+	out->leafTupleDatums = palloc(sizeof(Datum) * in->nTuples);
+
+	for(i=0; i<in->nTuples; i++)
+	{
+		if (i==0 || nodes[i].c != nodes[i-1].c)
+		{
+			out->nodeDatums[out->nNodes] = UInt8GetDatum(nodes[i].c);
+			out->nNodes++;
+		}
+
+		op = formTextDatum( VARDATA_ANY(nodes[i].d) + common + 1,
+							(nodes[i].c == '\0') ? 0 : (VARSIZE_ANY_EXHDR(nodes[i].d) - common - 1));
+
+		out->leafTupleDatums[ nodes[i].i ] = op;
+		out->mapTuplesToNodes[ nodes[i].i ] = out->nNodes - 1;
+	}
+
+	if (out->nNodes == 1)
+		out->allTheSame = true;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+spg_text_leaf_consistent(PG_FUNCTION_ARGS)
+{
+	int				level = PG_GETARG_INT32(0);
+	text			*query = PG_GETARG_TEXT_P(1);
+	StrategyNumber	strategy = PG_GETARG_INT32(2);
+	Datum			datum = PG_GETARG_DATUM(3),
+					reconstrDatum = PG_GETARG_DATUM(4);
+	text			*leafValue = NULL,
+					*reconstrValue = NULL;
+	char			*fullValue;
+	int				fullLen;
+	int				r;
+
+	if (datum)
+		leafValue = DatumGetTextP(datum);
+	if (reconstrDatum)
+		reconstrValue = DatumGetTextP(reconstrDatum);
+
+	Assert((reconstrValue == NULL && level == 0) || VARSIZE_ANY_EXHDR(reconstrValue) == level);
+
+	if (reconstrValue && VARSIZE(reconstrValue) > VARHDRSZ && ((char*)VARDATA(reconstrValue))[level-1] == '\0')
+		level --;
+
+	if (!leafValue)
+		leafValue = DatumGetTextP(formTextDatum(NULL, 0));
+	
+	fullLen = level + VARSIZE_ANY_EXHDR(leafValue);
+
+	if (strategy == BTEqualStrategyNumber && VARSIZE_ANY_EXHDR(query) != fullLen)
+		PG_RETURN_BOOL(false);
+
+	if (VARSIZE_ANY_EXHDR(leafValue) == 0)
+	{
+		fullValue = VARDATA(reconstrValue);
+	}
+	else
+	{
+		fullValue = palloc(fullLen);
+		if (level)
+			memcpy(fullValue, VARDATA(reconstrValue), level);
+		if (VARSIZE_ANY_EXHDR(leafValue) > 0)
+			memcpy(fullValue + level, VARDATA_ANY(leafValue), VARSIZE_ANY_EXHDR(leafValue));
+	}
+
+	if (strategy > 10)
+	{
+		strategy -= 10;
+
+		Assert(pg_verifymbstr(fullValue, fullLen, true));
+
+		r = varstr_cmp( fullValue, Min(VARSIZE_ANY_EXHDR(query), fullLen),
+						VARDATA_ANY(query), Min(VARSIZE_ANY_EXHDR(query), fullLen),
+						PG_GET_COLLATION() );
+	} 
+	else 
+	{
+		r = memcmp(fullValue, VARDATA_ANY(query), Min(VARSIZE_ANY_EXHDR(query), fullLen));
+	}
+
+	if (r == 0)
+	{
+		if (VARSIZE_ANY_EXHDR(query) > fullLen)
+			r = -1;
+		else if (VARSIZE_ANY_EXHDR(query) < fullLen)
+			r = 1;
+	}
+
+	switch(strategy)
+	{
+		case BTLessStrategyNumber:
+			r = (r < 0);
+			break;
+		case BTLessEqualStrategyNumber:
+			r = (r <= 0);
+			break;
+		case BTEqualStrategyNumber:
+			r = (r == 0);
+			break;
+		case BTGreaterEqualStrategyNumber:
+			r = (r >= 0);
+			break;
+		case BTGreaterStrategyNumber:
+			r = (r > 0);
+			break;
+		default:
+			elog(ERROR, "Unknown strategy number: %d", strategy);	
+	}
+
+	PG_RETURN_BOOL(r);
+}
+
+Datum
+spg_text_inner_consistent(PG_FUNCTION_ARGS)
+{
+	spgInnerConsistentIn	*in = (spgInnerConsistentIn*)PG_GETARG_POINTER(0);
+	spgInnerConsistentOut	*out = (spgInnerConsistentOut*)PG_GETARG_POINTER(1);
+	text					*inText;
+	int						inSize;
+	int						i;
+	text					*reconstrValue = NULL;
+	int						reconstrLen = 0;
+	text					*prefixText = NULL;
+	int						prefixSize = 0;	
+
+	Assert((DatumGetPointer(in->reconstructedValue) == NULL && in->level == 0) || 
+			VARSIZE_ANY_EXHDR(in->reconstructedValue) == in->level);
+
+	inText = DatumGetTextP(in->query);
+	inSize = VARSIZE_ANY_EXHDR(inText);
+
+	reconstrLen = in->level + 1;
+	if (in->hasPrefix)
+	{
+		prefixText = DatumGetTextP(in->prefixDatum);
+		prefixSize = VARSIZE_ANY_EXHDR(prefixText);
+		reconstrLen += prefixSize;
+	}
+
+	reconstrValue = palloc(VARHDRSZ + reconstrLen);
+	SET_VARSIZE(reconstrValue, VARHDRSZ + reconstrLen);
+
+	if (in->level)
+		memcpy(((char*)VARDATA(reconstrValue)), VARDATA(in->reconstructedValue), in->level);
+	if (prefixSize)
+		memcpy(((char*)VARDATA(reconstrValue)) + in->level, VARDATA_ANY(prefixText), prefixSize);
+
+	out->levelAdd = prefixSize + 1;
+
+	out->nodeNumbers = palloc(sizeof(*out->nodeNumbers) * in->nNodes);
+	out->reconstructedValues = palloc(sizeof(Datum) * in->nNodes);
+	out->nNodes = 0;
+
+	for(i=0; i<in->nNodes; i++)
+	{
+		int 			r = 0;
+		bool			res = false;
+		bool			cmpDone = true;
+		uint8			nodeChar = DatumGetUInt8(in->nodeDatums[i]);
+		StrategyNumber	strategy = in->strategy;
+		bool			isCClollation = false;
+
+		((char*)VARDATA(reconstrValue))[reconstrLen - 1] = DatumGetUInt8(in->nodeDatums[i]);
+
+		/*
+		 * do not take into account '\0' at the end
+		 */
+		if (nodeChar == '\0')
+			reconstrLen -= 1;
+
+		if (in->strategy > 10)
+		{
+			strategy -= 10;
+
+			if (pg_verifymbstr(VARDATA(reconstrValue), reconstrLen, true))
+			{
+				r = varstr_cmp( VARDATA(reconstrValue), Min(inSize, reconstrLen),
+								VARDATA_ANY(inText), Min(inSize, reconstrLen),
+								PG_GET_COLLATION() );
+				isCClollation = (PG_GET_COLLATION() == C_COLLATION_OID);
+			}
+			else
+			{
+				cmpDone = false;
+			}
+		} 
+		else 
+		{
+			r = memcmp(VARDATA(reconstrValue), VARDATA_ANY(inText), Min(inSize, reconstrLen));
+		}
+
+		if (cmpDone)
+		{
+
+			/*
+			 * with non-c collation we need to traverse whole tree :(
+			 */
+			switch(strategy)
+			{
+				case BTLessStrategyNumber:
+				case BTLessEqualStrategyNumber:
+					if (r <= 0 || isCClollation)
+						res = true;
+					break;
+				case BTEqualStrategyNumber:
+					if (isCClollation ||
+							(r == 0 && ((nodeChar == '\0' && inSize == reconstrLen) || 
+									(nodeChar != '\0' && inSize >= reconstrLen))))
+						res = true;
+					break;
+				case BTGreaterEqualStrategyNumber:
+				case BTGreaterStrategyNumber:
+					if (r >= 0 || isCClollation)
+						res = true;
+					break;
+				default:
+					elog(ERROR, "Unknown strategy number: %d", in->strategy);
+			}
+		}
+		else
+		{
+			/*
+			 * actual check will be done on next level
+			 */
+			res = true;
+		}
+
+		if (nodeChar == '\0')
+			reconstrLen += 1;
+
+		if (res)
+		{
+			out->nodeNumbers[out->nNodes] = i;
+			out->reconstructedValues[out->nNodes] = datumCopy(PointerGetDatum(reconstrValue), false, -1);
+			out->nNodes++;
+		}
+	}
+
+	PG_RETURN_VOID();
+}
+
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
new file mode 100644
index 0000000..a9beac9
--- /dev/null
+++ b/src/backend/access/spgist/spgutils.c
@@ -0,0 +1,735 @@
+/*-------------------------------------------------------------------------
+ *
+ * spgutils.c
+ *    various support functions for SP-GiST
+ *
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *          src/backend/access/spgist/spgutils.c
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/spgist_private.h"
+#include "access/xact.h"
+#include "catalog/index.h"
+#include "catalog/namespace.h"
+#include "storage/lmgr.h"
+#include "miscadmin.h"
+#include "storage/bufmgr.h"
+#include "storage/indexfsm.h"
+#include "utils/builtins.h"
+#include "utils/memutils.h"
+#include "access/reloptions.h"
+#include "storage/freespace.h"
+#include "storage/indexfsm.h"
+#include "utils/lsyscache.h"
+
+static void
+fillTypeDesc(SpGistTypeDesc *desc, Oid type)
+{
+	desc->type = type;
+
+	if (type != InvalidOid)
+		get_typlenbyval(type, &desc->attlen, &desc->attbyval);
+}
+
+void
+initSpGistState(SpGistState *state, Relation index)
+{
+	RegProcedure	propOid;
+
+	Assert(index->rd_att->natts == 1);
+
+	propOid = index_getprocid(index, 1, SPGIST_PROP_PROC);
+
+	state->prop = *(SpGistOpClassProp*)DatumGetPointer(OidFunctionCall0Coll(propOid, InvalidOid));
+
+	fillTypeDesc(&state->attType, index->rd_opcintype[0]);
+	fillTypeDesc(&state->attNodeType, state->prop.nodeType);
+	fillTypeDesc(&state->attPrefixType, state->prop.prefixType);
+
+	fmgr_info_copy(&(state->chooseFn),
+					index_getprocinfo(index, 1, SPGIST_CHOOSE_PROC),
+					CurrentMemoryContext);
+	fmgr_info_copy(&(state->picksplitFn),
+					index_getprocinfo(index, 1, SPGIST_PICKSPLIT_PROC),
+					CurrentMemoryContext);
+	fmgr_info_copy(&(state->leafConsistentFn),
+					index_getprocinfo(index, 1, SPGIST_LEAFCONS_PROC),
+					CurrentMemoryContext);
+	fmgr_info_copy(&(state->innerConsistentFn),
+					index_getprocinfo(index, 1, SPGIST_INNERCONS_PROC),
+					CurrentMemoryContext);
+
+	state->nodeTupDesc = CreateTemplateTupleDesc(1, false);
+	TupleDescInitEntry(state->nodeTupDesc, (AttrNumber) 1, NULL,
+						state->attNodeType.type, -1, 0);
+
+	if (state->attType.attlen > 0)
+	{
+		state->fakeTupleSize = SGLTHDRSZ + MAXALIGN(state->attType.attlen);
+
+		Assert(state->fakeTupleSize >= SGFTSIZE);
+	}
+	else
+		state->fakeTupleSize = SGFTSIZE;
+
+	state->fakeTupleStorage = palloc(state->fakeTupleSize);
+
+	state->isBuild = false;
+}
+
+/*
+ * 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 SpGistInitBuffer
+ */
+		
+Buffer
+SpGistNewBuffer(Relation index)
+{
+	Buffer      buffer;
+	bool        needLock;
+				 
+	/* First, try to get a page from FSM */
+	for (;;)
+	{
+		BlockNumber blkno = GetFreeIndexPage(index);
+
+		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 (SpGistPageIsDeleted(page))
+				return buffer;  /* OK to use */
+
+			LockBuffer(buffer, BUFFER_LOCK_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, BUFFER_LOCK_EXCLUSIVE);
+
+	if (needLock)
+		UnlockRelationForExtension(index, ExclusiveLock);
+
+	return buffer;
+}
+
+static SpGistCache*
+spgGetCache(Relation index)
+{
+	SpGistCache *cache;
+
+	if (index->rd_amcache == NULL)
+	{
+		Buffer				metabuffer;
+		SpGistMetaPageData	*metadata;
+
+		cache = MemoryContextAlloc(index->rd_indexcxt,
+									sizeof(SpGistCache));
+
+		metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO);
+		LockBuffer(metabuffer, BUFFER_LOCK_SHARE);
+
+		metadata = SpGistPageGetMeta(BufferGetPage(metabuffer));
+		*cache = metadata->lastUsedPages;
+
+		UnlockReleaseBuffer(metabuffer);
+
+		index->rd_amcache = cache;
+	}
+	else
+	{
+		cache = (SpGistCache*)index->rd_amcache;	
+	}
+
+	return cache;
+}
+
+void
+SpGistUpdateMetaPage(Relation index)
+{
+	SpGistCache *cache = (SpGistCache*)index->rd_amcache;
+
+	if (cache != NULL)
+	{
+		Buffer				metabuffer;
+		SpGistMetaPageData	*metadata;
+
+		metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO);
+
+		if (ConditionalLockBuffer(metabuffer))
+		{
+			/*
+			 * Updating meta page isn't critical for index working, so 
+			 * 1 ConditionalLockBuffer to improve concurrency 
+			 * 2 don't WAL metabuffer changes to decrease WAL traffic
+			 */
+
+			metadata = SpGistPageGetMeta(BufferGetPage(metabuffer));
+			metadata->lastUsedPages = *cache;
+
+			MarkBufferDirty(metabuffer);
+			UnlockReleaseBuffer(metabuffer);
+		}
+		else
+		{
+			ReleaseBuffer(metabuffer);
+		}
+	}
+}
+
+#define GET_LUP(c, f) 	( ((f) & GBUF_INNER) ? \
+							((c)->innerPage + ((f) % 3)) \
+							: &(c)->leafPage )
+
+static Buffer
+allocNewBuffer(Relation index, int flags, int needSpace)
+{
+	SpGistCache *cache = spgGetCache(index);
+
+	for(;;)
+	{
+		Buffer				buffer;
+
+		buffer = SpGistNewBuffer(index);
+		SpGistInitBuffer(buffer, (flags & GBUF_INNER) ?  0 : SPGIST_LEAF);
+
+		if (flags & GBUF_INNER)
+		{
+			BlockNumber	blkno = BufferGetBlockNumber(buffer);
+
+			cache->innerPage[blkno % 3].blkno = BufferGetBlockNumber(buffer);
+			cache->innerPage[blkno % 3].freeSpace = PageGetFreeSpace(BufferGetPage(buffer));
+
+			if (GET_LUP(cache, flags) == cache->innerPage + blkno % 3)
+			{
+				cache->innerPage[blkno % 3].freeSpace -= needSpace;
+				return buffer;
+			}
+			else
+			{
+				/* 
+				 * new buffer doesn't have a right parity, try again
+				 */
+				UnlockReleaseBuffer(buffer);
+			}
+		}
+		else
+		{
+			cache->leafPage.blkno = BufferGetBlockNumber(buffer);
+			cache->leafPage.freeSpace = PageGetFreeSpace(BufferGetPage(buffer)) - needSpace;
+
+			return buffer;
+		}
+	}
+}
+
+Buffer
+SpGistGetBuffer(Relation index, int flags, int needSpace, bool *isNew)
+{
+	SpGistCache 		*cache = spgGetCache(index);
+	SpGistLastUsedPage	*lup;
+
+	if (needSpace >= SPGIST_PAGE_CAPACITY)
+		elog(ERROR, "desired size is too big");
+
+	lup = GET_LUP(cache, flags);
+
+	/*
+	 * check if we have buffer with required parity
+	 */
+	if (lup->blkno == InvalidBlockNumber)
+	{
+		*isNew = true;
+		return allocNewBuffer(index, flags, needSpace);
+	}
+
+	/*
+	 * Ok, we have, check the space
+	 */
+	if (lup->freeSpace >= needSpace)
+	{
+		Buffer				buffer;
+		Page				page;
+
+		Assert(lup->blkno != SPGIST_HEAD_BLKNO);
+
+		buffer = ReadBuffer(index, lup->blkno);
+
+		if (!ConditionalLockBuffer(buffer))
+		{
+			/*
+			 * buffer is locked by another process, so return a new 
+			 * buffer
+			 */
+			ReleaseBuffer(buffer);
+			*isNew = true;
+			return allocNewBuffer(index, flags, needSpace);
+		}
+
+		page = BufferGetPage(buffer);
+
+		if (PageIsNew(page) || SpGistPageIsDeleted(page))
+		{
+			SpGistInitBuffer(buffer, (flags & GBUF_INNER) ?  0 : SPGIST_LEAF);
+			lup->freeSpace = PageGetFreeSpace(page) - needSpace;
+			*isNew = true;
+			return buffer;
+		} 
+
+		if (PageGetFreeSpace(page) >= needSpace)
+		{
+			lup->freeSpace = PageGetFreeSpace(page) - needSpace;
+			*isNew = false;
+			return buffer;
+		}
+
+		/*
+		 * fallback to allocation of new buffer
+		 */
+		UnlockReleaseBuffer(buffer);
+	}
+
+	*isNew = true;
+	return allocNewBuffer(index, flags, needSpace);
+}
+
+void
+SpGistSetLastUsedPage(Relation index, Buffer buffer)
+{
+	SpGistCache 		*cache = spgGetCache(index);
+	SpGistLastUsedPage	*lup;
+	int					freeSpace;
+	Page				page = BufferGetPage(buffer);
+	BlockNumber			blkno = BufferGetBlockNumber(buffer);
+	int					flags;
+
+	if (blkno == SPGIST_HEAD_BLKNO)
+		return;
+
+	if (SpGistPageIsLeaf(page))
+		flags = GBUF_LEAF;
+	else
+		flags = GBUF_INNER | (blkno % 3);
+
+	lup = GET_LUP(cache, flags);
+
+	freeSpace = PageGetFreeSpace(page);
+	if (lup->blkno == InvalidBlockNumber || lup->freeSpace < freeSpace)
+	{
+		lup->blkno = BufferGetBlockNumber(buffer);
+		lup->freeSpace = freeSpace;
+	}
+}
+
+void
+SpGistInitBuffer(Buffer b, uint16 f)
+{
+	SpGistInitPage(BufferGetPage(b), f, BufferGetPageSize(b));
+}
+
+void
+SpGistInitPage(Page page, uint16 f, Size pageSize)
+{
+	SpGistPageOpaque opaque;
+	PageInit(page, pageSize, sizeof(SpGistPageOpaqueData));
+	opaque = SpGistPageGetOpaque(page);
+	memset(opaque, 0, sizeof(SpGistPageOpaqueData));
+	opaque->flags = f;
+}
+
+void
+SpGistInitMetabuffer(Buffer b)
+{
+	SpGistMetaPageData   *metadata;
+	Page                page = BufferGetPage(b);
+	SpGistInitPage(page, SPGIST_META, BufferGetPageSize(b));
+	metadata = SpGistPageGetMeta(page);
+	memset(metadata, 0, sizeof(SpGistMetaPageData));
+	metadata->magickNumber = SPGIST_MAGIC_NUMBER;
+
+	metadata->lastUsedPages.innerPage[0].blkno = 
+			metadata->lastUsedPages.innerPage[1].blkno = 
+			metadata->lastUsedPages.innerPage[2].blkno =  
+			metadata->lastUsedPages.leafPage.blkno = InvalidBlockNumber;
+}
+
+Datum
+spgoptions(PG_FUNCTION_ARGS)
+{
+    Datum       reloptions = PG_GETARG_DATUM(0);
+	bool        validate = PG_GETARG_BOOL(1);
+	bytea      *result;
+
+	result = default_reloptions(reloptions, validate, RELOPT_KIND_GIST);
+
+	if (result)
+		PG_RETURN_BYTEA_P(result);
+	PG_RETURN_NULL();
+}
+
+unsigned int
+getTypeLength(SpGistTypeDesc *att, Datum datum)
+{
+	unsigned int	size;
+	
+	if (att->attbyval)
+		size = sizeof(datum);
+	else if (att->attlen > 0)
+		size = att->attlen;
+	else
+		size = VARSIZE_ANY(datum);
+
+	return MAXALIGN(size);
+}
+
+static unsigned int
+memcpyDatum(void *in, SpGistTypeDesc *att, Datum datum)
+{
+	unsigned int 	size;
+	
+	if (att->attbyval)
+	{
+		size = sizeof(datum);
+		memcpy(in, &datum, size);
+	}
+	else
+	{
+		size = (att->attlen > 0) ? att->attlen : VARSIZE_ANY(datum);
+		Assert(size < 0xffff);
+		memcpy(in, DatumGetPointer(datum), size);
+	}
+
+	return MAXALIGN(size);
+}
+
+SpGistLeafTuple
+spgFormLeafTuple(SpGistState *state, ItemPointer heapPtr, Datum datum, bool isnull)
+{
+	SpGistLeafTuple	tup;
+	unsigned int	size = SGLTHDRSZ + ((isnull) ? 0 : getTypeLength(&state->attType, datum));
+
+	if (size + sizeof(ItemIdData) > SPGIST_PAGE_CAPACITY)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+			errmsg("index row size %lu exceeds maximum %lu",
+					(unsigned long) (size),
+					(unsigned long) (SPGIST_PAGE_CAPACITY - sizeof(ItemIdData))),
+			errhint("Values larger than a buffer page cannot be indexed.\n")));
+
+	if (size < state->fakeTupleSize)
+		size = state->fakeTupleSize;
+
+	tup = palloc0(size);
+
+	tup->heapPtr = *heapPtr;
+	tup->nextOffset = InvalidOffsetNumber;
+	tup->size = size;
+
+	if (!isnull)
+		memcpyDatum(SGLTDATAPTR(tup), &state->attType, datum);
+
+	return tup;
+}
+
+SpGistInnerTuple
+spgFormInnerTuple(SpGistState *state, bool hasPrefix, Datum prefix, int nNodes, IndexTuple *nodes)
+{
+	SpGistInnerTuple	tup;
+	unsigned int 		size;
+	int 				i;
+	char				*ptr;
+
+	size = SGITHDRSZ;
+	if (hasPrefix)
+		size += getTypeLength(&state->attPrefixType, prefix);
+
+	for(i=0; i<nNodes; i++)
+		size += IndexTupleSize(nodes[i]);
+
+ 	/*
+	 * Inner tuple should be compact enough to fit a page
+	 */
+	if (size + sizeof(ItemIdData) > SPGIST_PAGE_CAPACITY)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+			errmsg("inner tuple size %lu exceeds maximum %lu",
+					(unsigned long) (size),
+					(unsigned long) (SPGIST_PAGE_CAPACITY - sizeof(ItemIdData))),
+			errhint("Inner tuple should be compact enough to fit a page.\n")));
+
+
+	tup = palloc0(size);
+
+	tup->nNodes = nNodes;
+	tup->size = size;
+
+	if (hasPrefix)
+	{
+		tup->prefixSize = getTypeLength(&state->attPrefixType, prefix);
+		tup->prefixSize = MAXALIGN(tup->prefixSize);
+		memcpyDatum(SGITDATAPTR(tup), &state->attPrefixType, prefix);
+	}
+
+	ptr= (char*)SGITNODEPTR(tup);
+
+	for(i=0; i<nNodes; i++)
+	{
+		IndexTuple	node = nodes[i];
+
+		memcpy(ptr, node, IndexTupleSize(node));
+		ptr += IndexTupleSize(node);
+	}
+
+	return tup;
+}
+
+SpGistFakeTuple
+spgFormFakeTuple(SpGistState *state, BlockNumber blkno, OffsetNumber offnum)
+{
+	/*
+	 * spgFormFakeTuple is called from critical sections, so 
+	 * do not use palloc
+	 */
+	SpGistFakeTuple	tuple = (SpGistFakeTuple)state->fakeTupleStorage;
+
+	tuple->size = state->fakeTupleSize;
+
+	if (blkno == InvalidBlockNumber)
+	{
+		tuple->isDead = 1;
+		tuple->isRedirect = 0;
+		ItemPointerSetInvalid(&tuple->pointer);
+	}
+	else
+	{
+		tuple->isDead = 0;
+		tuple->isRedirect = 1;
+		ItemPointerSet(&tuple->pointer, blkno, offnum);
+		/*
+		 * remember current xid (could be invalid) to allow vacuum process
+		 * to known permission to delete this redirect tuple
+		 */
+		tuple->xid = GetTopTransactionIdIfAny(); 
+	}
+
+	return tuple;
+}
+
+OffsetNumber
+SpGistPageAddNewItem(SpGistState *state, Page page, Item item, Size size, OffsetNumber *startOffset)
+{
+	SpGistPageOpaque	opaque = SpGistPageGetOpaque(page);
+
+	if (opaque->nDead > 0 &&
+			 PageGetFreeSpace(page) + state->fakeTupleSize >= MAXALIGN(size))
+	{
+		OffsetNumber 	i, 
+						maxoff,
+						offnum = InvalidOffsetNumber;
+
+		maxoff = SpGistPageGetMaxOffset(page);
+
+		/*
+		 * startOffset is a just optimizations for bulk insertion
+		 */
+
+		for(;;)
+		{
+			for(i=(startOffset && *startOffset!=InvalidOffsetNumber) ? *startOffset : FirstOffsetNumber; 
+					i<=maxoff; i++)
+			{
+				SpGistFakeTuple it = (SpGistFakeTuple) PageGetItem(page, PageGetItemId(page, i));
+
+				if (it->isDead)
+				{
+					offnum = i;
+					break;
+				}
+			}
+
+			/*
+			 * Go away if we found dead tuple
+			 */
+			if (offnum != InvalidOffsetNumber)
+				break;
+
+			if (startOffset && *startOffset != InvalidOffsetNumber)
+			{
+				/*
+				 * re-search from begining
+				 */
+				*startOffset = InvalidOffsetNumber;
+				continue;
+			}
+
+			break;
+		}
+
+		Assert(offnum >= FirstOffsetNumber && offnum <= maxoff);
+		if (SpGistPageIsLeaf(page) && state->attType.attlen > 0)
+		{
+			Item  oldone = PageGetItem(page, PageGetItemId(page, offnum));
+			
+			memcpy(oldone, item, size);
+		}
+		else
+		{
+			PageIndexTupleDelete(page, offnum);
+
+			offnum = PageAddItem(page, item, size, offnum, false, false);
+		}
+
+		if (offnum != InvalidOffsetNumber)
+		{
+			Assert(opaque->nDead > 0);
+			opaque->nDead--;
+			if (startOffset)
+				*startOffset = offnum + 1;
+		}
+
+		return offnum;
+	} else {
+		return PageAddItem(page, item, size, InvalidOffsetNumber, false, false);
+	}
+}
+
+Datum
+spgstat(PG_FUNCTION_ARGS)
+{
+    text    	*name=PG_GETARG_TEXT_P(0);
+	char 		*relname=text_to_cstring(name);
+	RangeVar   	*relvar;
+	Relation    index;
+	List       	*relname_list;
+	Oid			relOid;
+	BlockNumber	blkno = SPGIST_HEAD_BLKNO;
+	BlockNumber	totalPages = 0,
+				innerPages = 0,
+				leafPages = 0,
+				emptyPages = 0,
+				deletedPages = 0;
+	double		usedSpace = 0.0;
+	char		res[1024];
+	int			bufferSize = -1;
+	int64		innerTuples = 0,
+				leafTuples = 0,
+				nLeafDead = 0,
+				nInnerDead = 0,
+				nLeafRedirect = 0,
+				nInnerRedirect = 0;
+
+	relname_list = stringToQualifiedNameList(relname);
+	relvar = makeRangeVarFromNameList(relname_list);
+	relOid = RangeVarGetRelid(relvar, NoLock, false);
+	index = index_open(relOid, AccessExclusiveLock);
+
+	if ( index->rd_am == NULL )
+		elog(ERROR, "Relation %s.%s is not an index",
+					get_namespace_name(RelationGetNamespace(index)),
+					RelationGetRelationName(index) );
+	totalPages = RelationGetNumberOfBlocks(index);
+
+	for(blkno=SPGIST_HEAD_BLKNO; blkno<totalPages; blkno++)
+	{
+		Buffer	buffer;
+		Page	page;
+
+		buffer = ReadBuffer(index, blkno);
+		LockBuffer(buffer, BUFFER_LOCK_SHARE);
+
+		page = BufferGetPage(buffer);
+
+		if (SpGistPageIsDeleted(page))
+		{
+			deletedPages++;
+			UnlockReleaseBuffer(buffer);
+			continue;
+		}
+
+		if (SpGistPageIsLeaf(page))
+		{
+			leafPages++;
+			leafTuples += SpGistPageGetMaxOffset(page);
+			nLeafDead += SpGistPageGetOpaque(page)->nDead;
+			nLeafRedirect += SpGistPageGetOpaque(page)->nRedirection;
+		}
+		else
+		{
+			innerPages++;
+			innerTuples += SpGistPageGetMaxOffset(page);
+			nInnerDead += SpGistPageGetOpaque(page)->nDead;
+			nInnerRedirect += SpGistPageGetOpaque(page)->nRedirection;
+		}
+
+		if (bufferSize < 0)
+			bufferSize = BufferGetPageSize(buffer) - MAXALIGN(sizeof(SpGistPageOpaqueData)) -
+							SizeOfPageHeaderData;
+
+		usedSpace += bufferSize - (PageGetFreeSpace(page) + sizeof(ItemIdData));
+
+		if (PageGetFreeSpace(page) + sizeof(ItemIdData) == bufferSize)
+			emptyPages++;
+
+		UnlockReleaseBuffer(buffer);
+	}
+
+	index_close(index, AccessExclusiveLock);
+
+	totalPages--; /* metapage */
+
+	snprintf(res, sizeof(res),
+		"totalPages:      %u\n"
+		"deletedPages:    %u\n"
+		"innerPages:      %u\n"
+		"leafPages:       %u\n"
+		"emptyPages:      %u\n"
+		"usedSpace:       %.2f kbytes\n"
+		"freeSpace:       %.2f kbytes\n"
+		"fillRatio:       %.2f%c\n"
+		"leafTuples:      "INT64_FORMAT"\n"
+		"deadLeafTuples:  "INT64_FORMAT"\n"
+		"deadInnerTuples: "INT64_FORMAT"\n"
+		"nLeafRedirect:   "INT64_FORMAT"\n"
+		"nInnerRedirect:  "INT64_FORMAT"\n"
+		"innerTuples:     "INT64_FORMAT,
+			totalPages, deletedPages, innerPages, leafPages, emptyPages,
+			usedSpace / 1024.0,
+			(( (double) bufferSize ) * ( (double) totalPages ) - usedSpace) / 1024,
+			100.0 * ( usedSpace / (( (double) bufferSize ) * ( (double) totalPages )) ),
+			'%',
+			leafTuples, nLeafDead, nInnerDead, 
+			nLeafRedirect, nInnerRedirect,
+			innerTuples
+	);
+
+	PG_RETURN_TEXT_P(CStringGetTextDatum(res));
+}
+
diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c
new file mode 100644
index 0000000..f1ffc6e
--- /dev/null
+++ b/src/backend/access/spgist/spgvacuum.c
@@ -0,0 +1,626 @@
+/*-------------------------------------------------------------------------
+ *
+ * spgvacuum.c
+ *    vacuum for SP-GiST
+ *
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *          src/backend/access/spgist/spgvacuum.c
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/spgist_private.h"
+#include "access/transam.h"
+#include "catalog/storage.h"
+#include "commands/vacuum.h"
+#include "miscadmin.h"
+#include "postmaster/autovacuum.h"
+#include "storage/bufmgr.h"
+#include "storage/indexfsm.h"
+#include "storage/lmgr.h"
+#include "storage/procarray.h"
+
+typedef struct WalkElem {
+	BlockNumber		blkno;
+	OffsetNumber	offnum;
+	int				nodeN;
+
+	Buffer			buffer;
+	Page			page;
+	XLogRecPtr		lsn;
+
+	IndexTuple		nodePtr;
+	int				nodeI;
+} WalkElem;
+
+static WalkElem*
+makeWalkElem(BlockNumber blkno, OffsetNumber offnum, XLogRecPtr lsn) {
+	WalkElem *elem = palloc(sizeof(*elem));
+
+	elem->blkno = blkno;
+	elem->offnum = offnum;
+
+	elem->nodeN = 0;
+	elem->nodeI = -1;
+	elem->nodePtr = NULL;
+	elem->buffer = InvalidBuffer;
+	elem->page = NULL;
+	elem->lsn = lsn;
+
+	return elem;
+}
+
+static void
+UnlockCurrentBuffer(List *path, WalkElem *current)
+{
+	ListCell			*cell;
+	
+	UnlockReleaseBuffer(current->buffer);
+
+	/*
+	 * buffer could be pinned somewhere else
+	 */
+	foreach(cell, path)
+	{
+		WalkElem *e = (WalkElem*)lfirst(cell);
+
+		if (e != current && e->blkno == current->blkno)
+			e->buffer = InvalidBuffer; 
+	}
+
+	current->buffer = InvalidBuffer;
+}
+
+static void
+vacuumRedirectAndDead(Relation index, Buffer buffer, TransactionId OldestXmin)
+{
+	OffsetNumber    	i,
+						lastDead = InvalidOffsetNumber;
+	bool				hasNonDead = false;
+	bool				hasUpdate = false;
+	SpGistPageOpaque    opaque;
+	Page				page = BufferGetPage(buffer);
+	OffsetNumber		*itemnos = palloc(sizeof(OffsetNumber) * SpGistPageGetMaxOffset(page));
+	OffsetNumber		*itemToDead = palloc(sizeof(OffsetNumber) * SpGistPageGetMaxOffset(page));
+	spgxlogVacuumFake	xlrec;
+	XLogRecData			rdata[3];
+
+	opaque = SpGistPageGetOpaque(page);
+
+	/*
+	 * it's safe to lost vacuumMark in recovery
+	 */
+	opaque->vacuumMark = OldestXmin;
+	MarkBufferDirty(buffer);
+
+	ACCEPT_RDATA_BUFFER(buffer, 0);
+	ACCEPT_RDATA_DATA(&xlrec, sizeof(xlrec), 1);
+
+	xlrec.node = index->rd_node;
+	xlrec.blkno = BufferGetBlockNumber(buffer);
+	xlrec.nToDead = 0;
+
+	START_CRIT_SECTION();
+	/*
+	 * scan backward to find dead tuples at end of page
+	 * to remove them completely
+	 */
+	for(i=SpGistPageGetMaxOffset(page);
+		i>=FirstOffsetNumber && (hasNonDead == false || opaque->nRedirection > 0);
+		i--)
+	{
+		SpGistFakeTuple lt;
+
+		lt = (SpGistFakeTuple) PageGetItem(page, PageGetItemId(page, i));
+
+		if (lt->isRedirect && TransactionIdPrecedes(lt->xid, OldestXmin))
+		{
+			lt->isRedirect = 0;
+			Assert(lt->isDead == 0);
+			Assert(opaque->nRedirection > 0);
+			opaque->nRedirection--;
+
+			lt->isDead = 1;
+			opaque->nDead++;
+
+			itemToDead[xlrec.nToDead] = i;
+			xlrec.nToDead++;
+
+			ItemPointerSetInvalid(&lt->pointer);
+
+			hasUpdate = true;
+		}
+
+		if (lt->isDead)
+		{
+			Assert(lt->isRedirect == 0);
+			if (hasNonDead == false)
+				lastDead = i;
+		} 
+		else
+		{
+			hasNonDead = true;
+		}
+	}
+
+	/*
+	 * dead tuples at the end of page could be safely removed
+	 */
+	if (lastDead != InvalidOffsetNumber)
+	{
+		/*
+		 * do not store this to rdata because it's easy to replay
+		 */
+
+		for(i=lastDead; i<=SpGistPageGetMaxOffset(page); i++)
+			itemnos[i - lastDead] = i;
+
+		Assert(opaque->nDead >= SpGistPageGetMaxOffset(page) - lastDead + 1);
+		opaque->nDead -= SpGistPageGetMaxOffset(page) - lastDead + 1;
+		PageIndexMultiDelete(page, itemnos, SpGistPageGetMaxOffset(page) - lastDead + 1);
+
+		hasUpdate = true;
+	}
+
+	xlrec.lastDead = lastDead;
+
+	if (hasUpdate && RelationNeedsWAL(index))
+	{
+		XLogRecPtr      recptr;
+
+		if (xlrec.nToDead)
+			ACCEPT_RDATA_DATA(itemToDead, sizeof(*itemToDead) * xlrec.nToDead, 2);
+
+		recptr = XLogInsert(RM_SPGIST_ID, XLOG_SPGIST_VACUUM_FAKE, rdata);
+
+		PageSetLSN(page, recptr);
+		PageSetTLI(page, ThisTimeLineID);
+	}
+
+	END_CRIT_SECTION();
+
+	pfree(itemToDead);
+	pfree(itemnos);
+
+	SpGistSetLastUsedPage(index, buffer);
+}
+
+typedef struct BulkDeleteState {
+	SpGistState					spgstate;
+	TransactionId				OldestXmin;
+	IndexBulkDeleteResult		*stats;
+	IndexBulkDeleteCallback		callback;;
+	void						*callback_state;
+} BulkDeleteState;
+
+static void
+vacuumLeafPage(BulkDeleteState *bds, WalkElem *current,
+				spgxlogVacuum *xlrec, OffsetNumber *toDead, OffsetNumber *toAlive)
+{
+	OffsetNumber 	i = current->offnum;
+
+	while(i != InvalidOffsetNumber)
+	{
+		SpGistLeafTuple lt;
+
+		Assert(i >= FirstOffsetNumber && i <= SpGistPageGetMaxOffset(current->page));
+		lt = (SpGistLeafTuple) PageGetItem(current->page, PageGetItemId(current->page, i));
+		Assert(lt->isDead == 0 && lt->isRedirect == 0);
+		Assert(ItemPointerIsValid(&lt->heapPtr));
+
+		if (bds->callback(&lt->heapPtr, bds->callback_state))
+		{
+			bds->stats->tuples_removed += 1;
+			toDead[xlrec->nDead] = i;
+			xlrec->nDead++;
+		}
+		else
+		{
+			bds->stats->num_index_tuples++;
+			toAlive[xlrec->nAlive] = i;
+			xlrec->nAlive++;
+		}
+
+		i = lt->nextOffset;
+	}
+}
+
+static void
+vacuumLeafRoot(BulkDeleteState *bds, WalkElem *current,
+				spgxlogVacuum *xlrec, OffsetNumber *toDead)
+{
+	OffsetNumber    i;
+
+	Assert(current->blkno == SPGIST_HEAD_BLKNO);
+
+	for(i=FirstOffsetNumber; i<=SpGistPageGetMaxOffset(current->page); i++)
+	{
+		SpGistLeafTuple lt;
+
+		lt = (SpGistLeafTuple) PageGetItem(current->page, PageGetItemId(current->page, i));
+		Assert(lt->isDead == 0 && lt->isRedirect == 0);
+		Assert(ItemPointerIsValid(&lt->heapPtr));
+
+		if (bds->callback(&lt->heapPtr, bds->callback_state))
+		{
+			bds->stats->tuples_removed += 1;
+			toDead[xlrec->nDead] = i;
+			xlrec->nDead++;
+		}
+		else
+		{
+			bds->stats->num_index_tuples++;
+		}
+	}
+}
+
+static void
+doVacuumLeaf(Relation index, BulkDeleteState *bds, WalkElem *current, WalkElem *parent,
+				spgxlogVacuum *xlrec, OffsetNumber *toDead, OffsetNumber *toAlive)
+{
+	XLogRecData		rdata[5];
+
+	ACCEPT_RDATA_DATA(xlrec, sizeof(*xlrec), 0);
+	ACCEPT_RDATA_DATA(toDead, sizeof(*toDead) * xlrec->nDead, 1);
+	ACCEPT_RDATA_DATA(toAlive, sizeof(*toAlive) * xlrec->nAlive, 2);
+	ACCEPT_RDATA_BUFFER(current->buffer, 3);
+
+	xlrec->blkno = current->blkno;
+	xlrec->blknoParent = InvalidBlockNumber;
+
+	START_CRIT_SECTION();
+
+	if (current->blkno == SPGIST_HEAD_BLKNO)
+	{
+		PageIndexMultiDelete(current->page, toDead, xlrec->nDead); 
+	}
+	else
+	{
+		int i;
+
+		Assert(parent->blkno != current->blkno);
+		Assert(BufferIsPinned(parent->buffer));
+		/* how to assert that buffer is locked? */
+		Assert(ConditionalLockBuffer(parent->buffer) == false);
+		Assert(ItemPointerGetBlockNumber(&parent->nodePtr->t_tid) == current->blkno); 
+		Assert(ItemPointerGetOffsetNumber(&parent->nodePtr->t_tid) == current->offnum);
+
+		spgPageIndexMultiDead(&bds->spgstate, current->page, toDead, xlrec->nDead,
+											InvalidBlockNumber, InvalidOffsetNumber);
+
+		for(i=0; i<xlrec->nAlive; i++)
+		{
+			SpGistLeafTuple lt = (SpGistLeafTuple) PageGetItem(current->page, 
+																PageGetItemId(current->page, toAlive[i]));
+
+			lt->nextOffset = (i == xlrec->nAlive-1) ? InvalidOffsetNumber : toAlive[i+1];
+		}
+
+		if (xlrec->nAlive == 0 || toAlive[0] != current->offnum)
+		{
+			ACCEPT_RDATA_BUFFER(current->buffer, 4);
+			xlrec->blknoParent = parent->blkno;
+			xlrec->offnumParent = parent->offnum;
+			xlrec->nodeI = parent->nodeI;
+
+			if (xlrec->nAlive == 0)
+				ItemPointerSetInvalid(&parent->nodePtr->t_tid);
+			else
+				ItemPointerSetOffsetNumber(&parent->nodePtr->t_tid, toAlive[0]);
+			MarkBufferDirty(parent->buffer);
+		}
+	}
+
+	MarkBufferDirty(current->buffer);
+
+	if (RelationNeedsWAL(index))
+	{
+		XLogRecPtr	recptr;
+
+		recptr = XLogInsert(RM_SPGIST_ID, XLOG_SPGIST_VACUUM, rdata);
+
+		PageSetLSN(current->page, recptr);
+		PageSetTLI(current->page, ThisTimeLineID);
+
+		if (xlrec->blknoParent != InvalidBlockNumber)
+		{
+			PageSetLSN(parent->page, recptr);
+			PageSetTLI(parent->page, ThisTimeLineID);
+		}
+	}
+
+	END_CRIT_SECTION();
+}
+
+Datum
+spgbulkdelete(PG_FUNCTION_ARGS)
+{
+	IndexVacuumInfo 		*info = (IndexVacuumInfo *) PG_GETARG_POINTER(0);
+	IndexBulkDeleteResult 	*stats = (IndexBulkDeleteResult *) PG_GETARG_POINTER(1);
+	Relation    			index = info->index;
+	List					*path; /* path in tree from root */
+	BulkDeleteState			bds;
+	XLogRecPtr				InvalidLSN = {0, 0};
+	OffsetNumber			toDead[ BLCKSZ / (SGITHDRSZ + sizeof(ItemIdData)) ],
+							toAlive[ BLCKSZ / (SGITHDRSZ + sizeof(ItemIdData)) ];
+	spgxlogVacuum			xlrec;
+
+	initSpGistState(&bds.spgstate, index);
+	bds.OldestXmin = GetOldestXmin(true, false);
+
+	if (stats == NULL)
+		stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
+	bds.stats = stats;
+
+	bds.callback = (IndexBulkDeleteCallback) PG_GETARG_POINTER(2);
+	bds.callback_state = (void *) PG_GETARG_POINTER(3);
+
+	path = lcons(makeWalkElem(SPGIST_HEAD_BLKNO, FirstOffsetNumber, InvalidLSN), NIL);
+
+	/*
+	 * While we vacuum a leaf page we always keep exclusive lock on parent page
+	 * because chain of leaf tuple could be changed and, so, we will need to update parent's link
+	 */
+
+	while(list_length(path) > 0)
+	{
+		SpGistPageOpaque 	opaque;
+		WalkElem			*current = (WalkElem*)linitial(path),
+							*parent = (list_length(path) > 1) ? (WalkElem*)lsecond(path) : NULL;
+
+		if (current->buffer == InvalidBuffer)
+		{
+			if (parent && parent->blkno == current->blkno && parent->buffer != InvalidBuffer)
+			{
+				current->buffer = parent->buffer;
+				Assert(BufferIsPinned(current->buffer) && ConditionalLockBuffer(current->buffer) == false);
+			}
+			else
+			{
+				current->buffer = ReadBuffer(index, current->blkno);
+				LockBuffer(current->buffer, BUFFER_LOCK_EXCLUSIVE);
+			}
+		}
+
+		current->page = BufferGetPage(current->buffer);
+		opaque = SpGistPageGetOpaque(current->page);
+
+		if ((opaque->nRedirection > 0 || opaque->nDead > 0) && 
+				TransactionIdPrecedes(opaque->vacuumMark, bds.OldestXmin))
+			vacuumRedirectAndDead(index, current->buffer, bds.OldestXmin);
+
+		/*
+		 * vacuumMark could be not saved but it isn't big deal actually
+		 */
+		opaque->vacuumMark = bds.OldestXmin;
+
+		if (SpGistPageIsLeaf(current->page))
+		{
+			xlrec.node = index->rd_node;
+			xlrec.nDead = xlrec.nAlive = 0;
+			STORE_STATE(&bds.spgstate, xlrec.stateSrc);
+
+			if (parent)
+				vacuumLeafPage(&bds, current, &xlrec, toDead, toAlive);
+			else
+				vacuumLeafRoot(&bds, current, &xlrec, toDead);
+
+			if (xlrec.nDead)
+				doVacuumLeaf(index, &bds, current, parent, &xlrec, toDead, toAlive);
+
+			SpGistSetLastUsedPage(index, current->buffer);
+			UnlockReleaseBuffer(current->buffer);
+			pfree(current);
+			path = list_delete_first(path);
+		}
+		else /* inner page */
+		{
+			SpGistInnerTuple 	innerTuple;
+			int 				i;
+			IndexTuple			node;
+			bool				isRedirect = false;
+
+			Assert(parent == NULL || parent->buffer == InvalidBuffer || BufferIsPinned(parent->buffer));
+			/* how to assert that buffer is locked? */
+			Assert(BufferIsPinned(current->buffer) && ConditionalLockBuffer(current->buffer) == false);
+
+			innerTuple = (SpGistInnerTuple) PageGetItem(current->page, 
+														PageGetItemId(current->page, current->offnum));
+
+			if (XLByteLT(current->lsn, PageGetLSN(current->page)))
+			{
+				/*
+				 * page has been changed while we are walking on subtree
+				 */
+				Assert(innerTuple->isDead == 0);
+
+				if (innerTuple->isRedirect)
+				{
+					SpGistFakeTuple	ft = (SpGistFakeTuple)innerTuple;
+
+					Assert(current->blkno != SPGIST_HEAD_BLKNO);
+					isRedirect = true;
+
+					/* 
+					 * replace current by redirection value 
+					 */
+					path = list_delete_first(path);
+					path = lcons(makeWalkElem(ItemPointerGetBlockNumber(&ft->pointer), 
+											  ItemPointerGetOffsetNumber(&ft->pointer),
+											  InvalidLSN), path);
+				}
+				else
+				{
+					/*
+					 * rescan current tuple
+					 */
+					current->nodeN = 0;
+				}
+
+			}
+			else
+			{
+				Assert(innerTuple->isDead == 0 && innerTuple->isRedirect == 0);
+			}
+
+			current->lsn = PageGetLSN(current->page);
+unlockpage:
+			if (isRedirect || current->nodeN >= innerTuple->nNodes) 
+			{
+				/*
+				 * tuple finished, go up
+				 */
+
+				if (parent && parent->blkno == current->blkno)
+				{
+					/*
+					 * do not unlock page because parent is placed on the same page
+					 */
+					if (parent->buffer == InvalidBuffer)
+						parent->buffer = current->buffer;
+				}
+				else
+				{
+					UnlockCurrentBuffer(path, current);
+				}
+
+				pfree(current);
+				if (!isRedirect)
+					path = list_delete_first(path);
+				continue;
+			}
+			else if (parent && parent->blkno != current->blkno && parent->buffer != InvalidBuffer)
+			{
+				/*
+				 * keep current locked: child could be a leaf page. In opposite case
+				 * current becomes parent and will be unlocked here on next iteration
+				 */
+				UnlockCurrentBuffer(path, parent);
+			}
+
+			/*
+			 * get next subtree to walk
+			 */
+
+			SGITITERATE(innerTuple, i, node)
+			{
+				if (i >= current->nodeN)
+				{
+					current->nodeN = i + 1; /* next */
+
+					if (ItemPointerIsValid(&node->t_tid))
+					{
+						path = lcons(makeWalkElem(ItemPointerGetBlockNumber(&node->t_tid), 
+												  ItemPointerGetOffsetNumber(&node->t_tid),
+												  InvalidLSN), path);
+						current->nodePtr = node;
+						current->nodeI = i;
+						break;
+					}
+				}
+			}
+
+			if (current == linitial(path))
+			{
+				/*
+				 * there is not more children
+				 */
+				 goto unlockpage;
+			}
+		}
+	}
+
+	SpGistUpdateMetaPage(index);
+
+    PG_RETURN_POINTER(stats);
+}
+
+Datum
+spgvacuumcleanup(PG_FUNCTION_ARGS)
+{
+    IndexVacuumInfo *info = (IndexVacuumInfo *) PG_GETARG_POINTER(0);
+	IndexBulkDeleteResult *stats = (IndexBulkDeleteResult *) PG_GETARG_POINTER(1);
+	Relation    index = info->index;
+	bool        needLock;
+	BlockNumber npages,
+				blkno;
+	BlockNumber totFreePages;
+	BlockNumber lastBlock = SPGIST_HEAD_BLKNO,
+				lastFilledBlock = SPGIST_HEAD_BLKNO;
+
+	if (info->analyze_only)
+		PG_RETURN_POINTER(stats);
+
+	if (stats == NULL)
+		stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
+
+	needLock = !RELATION_IS_LOCAL(index);
+
+	if (needLock)
+		LockRelationForExtension(index, ExclusiveLock);
+	npages = RelationGetNumberOfBlocks(index);
+	if (needLock)
+		UnlockRelationForExtension(index, ExclusiveLock);
+
+	totFreePages = 0;
+	for (blkno = SPGIST_HEAD_BLKNO; blkno < npages; blkno++)
+	{
+		Buffer      		buffer;
+		Page        		page;
+		SpGistPageOpaque	opaque;
+
+		vacuum_delay_point();
+
+		buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
+									RBM_NORMAL, info->strategy);
+		LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+		page = (Page) BufferGetPage(buffer);
+		opaque = SpGistPageGetOpaque(page); 
+
+		if (blkno != SPGIST_HEAD_BLKNO && (SpGistPageIsDeleted(page) || 
+											SpGistPageGetMaxOffset(page) == InvalidOffsetNumber ||
+											SpGistPageGetMaxOffset(page) == opaque->nDead) )
+		{
+			SpGistPageSetDeleted(page);
+			RecordFreeIndexPage(index, blkno);
+			totFreePages++;
+		}
+		else if (SpGistPageIsLeaf(page))
+		{
+			lastFilledBlock = blkno;
+			stats->num_index_tuples += SpGistPageGetMaxOffset(page) - opaque->nDead - opaque->nRedirection;
+			stats->estimated_count += SpGistPageGetMaxOffset(page) - opaque->nDead - opaque->nRedirection;
+		}
+		else
+		{
+			lastFilledBlock = blkno;
+		}
+
+		UnlockReleaseBuffer(buffer);
+	}
+
+	lastBlock = npages - 1;
+	if (lastBlock > lastFilledBlock)
+	{
+		RelationTruncate(index, lastFilledBlock + 1);
+		stats->pages_removed = lastBlock - lastFilledBlock;
+		totFreePages = totFreePages - stats->pages_removed;
+	}
+
+	IndexFreeSpaceMapVacuum(info->index);
+	stats->pages_free = totFreePages;
+
+	if (needLock)
+		LockRelationForExtension(index, ExclusiveLock);
+	stats->num_pages = RelationGetNumberOfBlocks(index);
+	if (needLock)
+		UnlockRelationForExtension(index, ExclusiveLock);
+
+	PG_RETURN_POINTER(stats);
+}
diff --git a/src/backend/access/spgist/spgxlog.c b/src/backend/access/spgist/spgxlog.c
new file mode 100644
index 0000000..dc16270
--- /dev/null
+++ b/src/backend/access/spgist/spgxlog.c
@@ -0,0 +1,895 @@
+/*-------------------------------------------------------------------------
+ *
+ * spgxlog.c
+ *	  WAL replay logic for SP-GiST.
+ *
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *			 src/backend/access/spgist/spgxlog.c
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/spgist_private.h"
+#include "access/xlogutils.h"
+#include "miscadmin.h"
+#include "storage/bufmgr.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+
+static MemoryContext opCtx;		/* working memory for operations */
+
+static void
+fillFakeState(SpGistState *state, spgxlogState stateSrc)
+{
+	memset(state, 0, sizeof(*state));
+
+	state->attType.attlen = (stateSrc.attType_attlen == 0) ? -1 : stateSrc.attType_attlen;
+	state->isBuild = stateSrc.isBuild;
+	state->fakeTupleSize = stateSrc.fakeTupleSize;
+	state->fakeTupleStorage = palloc0(stateSrc.fakeTupleSize);
+}
+
+static void
+spgRedoCreateIndex(XLogRecPtr lsn, XLogRecord *record)
+{
+    RelFileNode *node = (RelFileNode *) XLogRecGetData(record);
+	Buffer      buffer;
+	Page 		page;
+
+	buffer = XLogReadBuffer(*node, SPGIST_METAPAGE_BLKNO, true);
+	Assert(BufferIsValid(buffer));
+    SpGistInitMetabuffer(buffer);
+	page = (Page) BufferGetPage(buffer);
+	PageSetLSN(page, lsn);
+	PageSetTLI(page, ThisTimeLineID);
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+	
+	buffer = XLogReadBuffer(*node, SPGIST_HEAD_BLKNO, true);
+	Assert(BufferIsValid(buffer));
+	SpGistInitBuffer(buffer, SPGIST_LEAF);
+	page = (Page) BufferGetPage(buffer);
+	PageSetLSN(page, lsn);
+	PageSetTLI(page, ThisTimeLineID);
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+}
+
+static void
+addOrReplaceTuple(Page page, Item tuple, int size, OffsetNumber offset)
+{
+	if (offset <= SpGistPageGetMaxOffset(page))
+	{
+		SpGistFakeTuple	fk = (SpGistFakeTuple)PageGetItem(page, PageGetItemId(page, offset));
+		/* install instead of dead tuple */
+		if ( fk->isDead)
+		{
+			Assert(SpGistPageGetOpaque(page)->nDead > 0);
+			SpGistPageGetOpaque(page)->nDead--;
+		}
+
+		PageIndexTupleDelete(page, offset);
+	}
+
+	Assert(offset <= SpGistPageGetMaxOffset(page) + 1);
+
+	if (offset != PageAddItem(page, tuple, size, offset, false, false))
+		elog(ERROR, "failed to add item to SPGiST index page, size %d bytes (has only %d bytes)", 
+						size, (int)PageGetFreeSpace(page));
+}
+
+static void
+spgRedoAddLeaf(XLogRecPtr lsn, XLogRecord *record)
+{
+	char       		*begin = XLogRecGetData(record);
+	spgxlogAddLeaf	*xldata = (spgxlogAddLeaf*)begin;
+
+	if ((record->xl_info & XLR_BKP_BLOCK_1) == 0)
+	{
+		Buffer	buffer = XLogReadBuffer(xldata->node, xldata->blknoLeaf, xldata->newPage);
+		Page	page;
+
+		Assert(BufferIsValid(buffer));
+		page = BufferGetPage(buffer);
+
+		if (!XLByteLE(lsn, PageGetLSN(page)))
+		{
+			SpGistLeafTuple	leafTuple = (SpGistLeafTuple)(begin + sizeof(spgxlogAddLeaf));
+
+			if (xldata->newPage)
+				SpGistInitBuffer(buffer, SPGIST_LEAF);
+
+			addOrReplaceTuple(page, (Item)leafTuple, leafTuple->size, xldata->offnumLeaf);
+
+			if (xldata->offnumHeadLeaf != InvalidOffsetNumber)
+			{
+				SpGistLeafTuple	head;
+
+				Assert(xldata->blknoParent == InvalidBlockNumber);
+				head = (SpGistLeafTuple)PageGetItem(page, PageGetItemId(page, xldata->offnumHeadLeaf));
+				Assert(head->nextOffset == leafTuple->nextOffset);
+				head->nextOffset = xldata->offnumLeaf;
+			}
+
+			PageSetLSN(page, lsn);
+			PageSetTLI(page, ThisTimeLineID);
+			MarkBufferDirty(buffer);
+		}
+
+		UnlockReleaseBuffer(buffer);
+	}
+
+	if (xldata->blknoParent != InvalidBlockNumber && (record->xl_info & XLR_BKP_BLOCK_2) == 0)
+	{
+		Buffer	buffer = XLogReadBuffer(xldata->node, xldata->blknoParent, false);
+		Page	page;
+
+		Assert(BufferIsValid(buffer));
+		Assert(xldata->offnumHeadLeaf == InvalidOffsetNumber);
+		page = BufferGetPage(buffer);
+
+		if (!XLByteLE(lsn, PageGetLSN(page)))
+		{
+			SpGistInnerTuple	tuple;
+
+			tuple = (SpGistInnerTuple) PageGetItem(page, PageGetItemId(page, xldata->offnumParent));
+
+			updateNodeLink(tuple, xldata->nodeI, xldata->blknoLeaf, xldata->offnumLeaf);
+
+			PageSetLSN(page, lsn);
+			PageSetTLI(page, ThisTimeLineID);
+			MarkBufferDirty(buffer);
+		}
+
+		UnlockReleaseBuffer(buffer);
+	}
+}
+
+static void
+spgRedoMoveLeafs(XLogRecPtr lsn, XLogRecord *record)
+{
+	char       			*begin = XLogRecGetData(record);
+	spgxlogMoveLeafs	*xldata = (spgxlogMoveLeafs*)begin;
+	SpGistState			state;
+
+	fillFakeState(&state, xldata->stateSrc);
+
+	if ((record->xl_info & XLR_BKP_BLOCK_1) == 0)
+	{
+		Buffer	buffer = XLogReadBuffer(xldata->node, xldata->blknoSrc, false);
+		Page	page;
+
+		Assert(BufferIsValid(buffer));
+		page = BufferGetPage(buffer);
+
+		if (!XLByteLE(lsn, PageGetLSN(page)))
+		{
+			OffsetNumber		*movedOffset = (OffsetNumber*)(begin + sizeof(*xldata));
+
+			spgPageIndexMultiDead(&state, page, movedOffset, xldata->nMoves, xldata->blknoDst, xldata->offnumDst); 
+
+			PageSetLSN(page, lsn);
+			PageSetTLI(page, ThisTimeLineID);
+			MarkBufferDirty(buffer);
+		}
+
+		UnlockReleaseBuffer(buffer);
+	}
+
+	if ((record->xl_info & XLR_BKP_BLOCK_2) == 0)
+	{
+		Buffer	buffer = XLogReadBuffer(xldata->node, xldata->blknoDst, xldata->newPage);
+		Page	page;
+
+		Assert(BufferIsValid(buffer));
+		page = BufferGetPage(buffer);
+
+		if (!XLByteLE(lsn, PageGetLSN(page)))
+		{
+			char 			*tuples = begin + sizeof(*xldata) + sizeof(OffsetNumber) * xldata->nMoves;
+			int 			i = 0;
+			SpGistLeafTuple	lt;
+			OffsetNumber	r, 
+							startOffset = InvalidOffsetNumber,
+							prevoff = InvalidOffsetNumber;
+
+			if (xldata->newPage)
+				SpGistInitBuffer(buffer, SPGIST_LEAF);
+
+			for(i=0; i < xldata->nMoves + 1 /* new tuple */; i++)
+			{
+				lt = (SpGistLeafTuple)tuples;
+
+				r = SpGistPageAddNewItem(&state, page, (Item)lt, lt->size, &startOffset);
+				if (r == InvalidOffsetNumber)
+					elog(ERROR, "failed to add item to SPGiST index page, size %d bytes", lt->size);
+				Assert(prevoff == InvalidOffsetNumber || lt->nextOffset == prevoff);
+
+				prevoff = r;
+				tuples += lt->size;
+			}
+
+			PageSetLSN(page, lsn);
+			PageSetTLI(page, ThisTimeLineID);
+			MarkBufferDirty(buffer);
+		}
+
+		UnlockReleaseBuffer(buffer);
+	}
+
+	if ((record->xl_info & XLR_BKP_BLOCK_3) == 0)
+	{
+		Buffer	buffer = XLogReadBuffer(xldata->node, xldata->blknoParent, false);
+		Page	page;
+
+		Assert(BufferIsValid(buffer));
+		page = BufferGetPage(buffer);
+
+		if (!XLByteLE(lsn, PageGetLSN(page)))
+		{
+			SpGistInnerTuple	tuple;
+
+			tuple = (SpGistInnerTuple) PageGetItem(page, PageGetItemId(page, xldata->offnumParent));
+
+			updateNodeLink(tuple, xldata->nodeI, xldata->blknoDst, xldata->offnumDst);
+
+			PageSetLSN(page, lsn);
+			PageSetTLI(page, ThisTimeLineID);
+			MarkBufferDirty(buffer);
+		}
+
+		UnlockReleaseBuffer(buffer);
+	}
+}
+
+static void
+spgRedoAddNode(XLogRecPtr lsn, XLogRecord *record)
+{
+	char       			*begin = XLogRecGetData(record);
+	spgxlogAddNode		*xldata = (spgxlogAddNode*)begin;
+	SpGistState			state;
+
+	fillFakeState(&state, xldata->stateSrc);
+
+	if (xldata->blknoParent == InvalidBlockNumber)
+	{
+		if ((record->xl_info & XLR_BKP_BLOCK_1) == 0)
+		{
+			Buffer	buffer = XLogReadBuffer(xldata->node, xldata->blkno, false);
+			Page	page;
+
+			Assert(BufferIsValid(buffer));
+			page = BufferGetPage(buffer);
+
+			if (!XLByteLE(lsn, PageGetLSN(page)))
+			{
+				SpGistInnerTuple 	innerTuple = (SpGistInnerTuple)(begin + sizeof(*xldata));
+
+				PageIndexTupleDelete(page, xldata->offnum);
+				if (PageAddItem(page, (Item)innerTuple, innerTuple->size,
+														xldata->offnum, false, false) == InvalidOffsetNumber)
+					elog(ERROR, "failed to add item to SPGiST index page, size %d bytes", innerTuple->size);
+
+				PageSetLSN(page, lsn);
+				PageSetTLI(page, ThisTimeLineID);
+				MarkBufferDirty(buffer);
+			}
+
+			UnlockReleaseBuffer(buffer);
+
+			Assert(xldata->blknoNew == InvalidBlockNumber);
+		}
+	}
+	else
+	{
+		if ((record->xl_info & XLR_BKP_BLOCK_1) == 0)
+		{
+			Buffer	buffer = XLogReadBuffer(xldata->node, xldata->blkno, false);
+			Page	page;
+
+			Assert(BufferIsValid(buffer));
+			page = BufferGetPage(buffer);
+
+			if (!XLByteLE(lsn, PageGetLSN(page)))
+			{
+				SpGistFakeTuple	ft;
+
+				ft = spgFormFakeTuple(&state,
+										xldata->setRedirect ? xldata->blknoNew : InvalidBlockNumber,
+										xldata->setRedirect ? xldata->offnumNew : InvalidOffsetNumber);
+
+				PageIndexTupleDelete(page, xldata->offnum);
+				if (PageAddItem(page, (Item)ft, ft->size,
+														xldata->offnum, false, false) == InvalidOffsetNumber)
+					elog(ERROR, "failed to add item to SPGiST index page, size %d bytes", ft->size);
+
+				if (xldata->setRedirect)
+					SpGistPageGetOpaque(page)->nRedirection++;
+				else
+					SpGistPageGetOpaque(page)->nDead++;
+
+				PageSetLSN(page, lsn);
+				PageSetTLI(page, ThisTimeLineID);
+				MarkBufferDirty(buffer);
+			}
+
+			UnlockReleaseBuffer(buffer);
+		}
+
+		if ((record->xl_info & XLR_BKP_BLOCK_2) == 0)
+		{
+			Buffer	buffer = XLogReadBuffer(xldata->node, xldata->blknoNew, xldata->newPage);
+			Page	page;
+
+			Assert(BufferIsValid(buffer));
+			page = BufferGetPage(buffer);
+
+			if (!XLByteLE(lsn, PageGetLSN(page)))
+			{
+				SpGistInnerTuple 	innerTuple = (SpGistInnerTuple)(begin + sizeof(*xldata));
+
+				if (xldata->newPage)
+					SpGistInitBuffer(buffer, 0);
+
+				addOrReplaceTuple(page, (Item)innerTuple, innerTuple->size, xldata->offnumNew);
+
+				PageSetLSN(page, lsn);
+				PageSetTLI(page, ThisTimeLineID);
+				MarkBufferDirty(buffer);
+			}
+
+			UnlockReleaseBuffer(buffer);
+		}
+
+		if ((record->xl_info & XLR_BKP_BLOCK_3) == 0)
+		{
+			Buffer	buffer = XLogReadBuffer(xldata->node, xldata->blknoParent, false);
+			Page	page;
+
+			Assert(BufferIsValid(buffer));
+			page = BufferGetPage(buffer);
+
+			if (!XLByteLE(lsn, PageGetLSN(page)))
+			{
+				SpGistInnerTuple	innerTuple = (SpGistInnerTuple)PageGetItem(page, PageGetItemId(page, xldata->offnumParent));
+
+				updateNodeLink(innerTuple, xldata->nodeI, xldata->blknoNew, xldata->offnumNew);
+
+				PageSetLSN(page, lsn);
+				PageSetTLI(page, ThisTimeLineID);
+				MarkBufferDirty(buffer);
+			}
+
+			UnlockReleaseBuffer(buffer);
+		}
+	}
+}
+
+static void
+spgRedoSplitTuple(XLogRecPtr lsn, XLogRecord *record)
+{
+	char       			*begin = XLogRecGetData(record);
+	spgxlogSplitTuple	*xldata = (spgxlogSplitTuple*)begin;
+	SpGistInnerTuple	prefixTuple = (SpGistInnerTuple)(begin + sizeof(spgxlogSplitTuple));
+	SpGistInnerTuple	postfixTuple = (SpGistInnerTuple)(begin + sizeof(spgxlogSplitTuple) + prefixTuple->size);
+
+	Assert(xldata->blknoPrefix != InvalidBlockNumber);
+	Assert(xldata->blknoPostfix != InvalidBlockNumber);
+	Assert(xldata->offnumPrefix != InvalidOffsetNumber);
+	Assert(xldata->offnumPostfix != InvalidOffsetNumber);
+
+	if ((record->xl_info & XLR_BKP_BLOCK_1) == 0)
+	{
+		Buffer	buffer = XLogReadBuffer(xldata->node, xldata->blknoPrefix, false);
+		Page	page;
+
+		Assert(BufferIsValid(buffer));
+		page = BufferGetPage(buffer);
+
+		if (!XLByteLE(lsn, PageGetLSN(page)))
+		{
+			updateNodeLink(prefixTuple, 0, xldata->blknoPostfix, xldata->offnumPostfix);
+			addOrReplaceTuple(page, (Item)prefixTuple, prefixTuple->size, xldata->offnumPrefix);
+
+			if (xldata->blknoPostfix == xldata->blknoPrefix)
+				addOrReplaceTuple(page, (Item)postfixTuple, postfixTuple->size, xldata->offnumPostfix);
+
+			PageSetLSN(page, lsn);
+			PageSetTLI(page, ThisTimeLineID);
+			MarkBufferDirty(buffer);
+		}
+
+		UnlockReleaseBuffer(buffer);
+	}
+
+	if (xldata->blknoPostfix != xldata->blknoPrefix && (record->xl_info & XLR_BKP_BLOCK_2) == 0)
+	{
+		Buffer	buffer = XLogReadBuffer(xldata->node, xldata->blknoPostfix, xldata->newPage);
+		Page	page;
+
+		Assert(BufferIsValid(buffer));
+		page = BufferGetPage(buffer);
+
+		if (!XLByteLE(lsn, PageGetLSN(page)))
+		{
+			if (xldata->newPage)
+				SpGistInitBuffer(buffer, 0);
+
+			addOrReplaceTuple(page, (Item)postfixTuple, postfixTuple->size, xldata->offnumPostfix);
+
+			PageSetLSN(page, lsn);
+			PageSetTLI(page, ThisTimeLineID);
+			MarkBufferDirty(buffer);
+		}
+
+		UnlockReleaseBuffer(buffer);
+	}
+}
+
+static void
+spgRedoPickSplit(XLogRecPtr lsn, XLogRecord *record)
+{
+	char       			*ptr = XLogRecGetData(record);
+	spgxlogPickSplit	*xldata = (spgxlogPickSplit*)ptr;
+	SpGistInnerTuple	innerTuple = (SpGistInnerTuple)(ptr + sizeof(spgxlogPickSplit));
+	int					i;
+	SpGistState			state;
+	OffsetNumber		*toDelete;
+	spgxlogPSOneNode	*xlns;
+	int					*mapTuplesToNodes;
+	Buffer				*buffers;
+
+	fillFakeState(&state, xldata->stateSrc);
+
+	ptr += sizeof(spgxlogPickSplit) + innerTuple->size;
+	toDelete = (OffsetNumber*)ptr;
+	ptr += sizeof(*toDelete) * xldata->nDelete;
+	xlns = (spgxlogPSOneNode*)ptr;
+	ptr += sizeof(*xlns) * xldata->nNodes;
+	mapTuplesToNodes = (int*)ptr;
+	ptr += sizeof(*mapTuplesToNodes) * xldata->nTuples;
+
+	/* now ptr points to the list of itemPointers */
+
+	if (xldata->blknoParent == InvalidBlockNumber)
+	{
+		Assert(xldata->blknoInner == SPGIST_HEAD_BLKNO);
+
+		if ((record->xl_info & XLR_BKP_BLOCK_2) == 0)
+		{
+			Buffer	buffer = XLogReadBuffer(xldata->node, xldata->blknoInner, xldata->newPage);
+			Page	page;
+
+			Assert(BufferIsValid(buffer));
+			page = BufferGetPage(buffer);
+
+			if (!XLByteLE(lsn, PageGetLSN(page)))
+			{
+				if (xldata->newPage)
+					SpGistInitBuffer(buffer, 0);
+
+				if (PageAddItem(page, (Item)innerTuple, innerTuple->size, InvalidOffsetNumber, false, false) == 
+							InvalidOffsetNumber)
+					elog(ERROR, "failed to add item to SPGiST index page, size %d bytes", innerTuple->size);
+				
+				PageSetLSN(page, lsn);
+				PageSetTLI(page, ThisTimeLineID);
+				MarkBufferDirty(buffer);
+			}
+			UnlockReleaseBuffer(buffer);
+		}
+	}
+	else
+	{
+		if ((record->xl_info & XLR_BKP_BLOCK_2) == 0)
+		{
+			Buffer	buffer = XLogReadBuffer(xldata->node, xldata->blknoInner, xldata->newPage);
+			Page	page;
+
+			Assert(BufferIsValid(buffer));
+			page = BufferGetPage(buffer);
+
+			if (!XLByteLE(lsn, PageGetLSN(page)))
+			{
+				if (xldata->newPage)
+					SpGistInitBuffer(buffer, 0);
+
+				addOrReplaceTuple(page, (Item)innerTuple, innerTuple->size, xldata->offnumInner);
+
+				if (xldata->blknoInner == xldata->blknoParent)
+				{
+					innerTuple = (SpGistInnerTuple)PageGetItem(page, PageGetItemId(page, xldata->offnumParent));
+					updateNodeLink(innerTuple, xldata->nodeI, xldata->blknoInner, xldata->offnumInner);
+				}
+
+				PageSetLSN(page, lsn);
+				PageSetTLI(page, ThisTimeLineID);
+				MarkBufferDirty(buffer);
+			}
+
+			UnlockReleaseBuffer(buffer);
+		}
+
+		if (xldata->blknoInner != xldata->blknoParent && (record->xl_info & XLR_BKP_BLOCK_3) == 0)
+		{
+			Buffer	buffer = XLogReadBuffer(xldata->node, xldata->blknoParent, false);
+			Page	page;
+
+			Assert(BufferIsValid(buffer));
+			page = BufferGetPage(buffer);
+
+			if (!XLByteLE(lsn, PageGetLSN(page)))
+			{
+				innerTuple = (SpGistInnerTuple)PageGetItem(page, PageGetItemId(page, xldata->offnumParent));
+				updateNodeLink(innerTuple, xldata->nodeI, xldata->blknoInner, xldata->offnumInner);
+
+				PageSetLSN(page, lsn);
+				PageSetTLI(page, ThisTimeLineID);
+				MarkBufferDirty(buffer);
+			}
+
+			UnlockReleaseBuffer(buffer);
+		}
+	}
+
+	if (xlns[0].blkno != xldata->blknoSrc && (record->xl_info & XLR_BKP_BLOCK_1) == 0)
+	{
+		Buffer	buffer = XLogReadBuffer(xldata->node, xldata->blknoSrc, false);
+		Page	page;
+
+		Assert(BufferIsValid(buffer));
+		page = BufferGetPage(buffer);
+
+		if (!XLByteLE(lsn, PageGetLSN(page)))
+		{
+			spgPageIndexMultiDead(&state, page, toDelete, xldata->nDelete,
+									SPGIST_METAPAGE_BLKNO, FirstOffsetNumber /* set redirect to invalid */);
+
+			PageSetLSN(page, lsn);
+			PageSetTLI(page, ThisTimeLineID);
+			MarkBufferDirty(buffer);
+		}
+
+		UnlockReleaseBuffer(buffer);
+	}
+
+	buffers = palloc(sizeof(*buffers) * xldata->nNodes);
+	for(i=0; i<xldata->nNodes; i++)
+	{
+		spgxlogPSOneNode    *xln = xlns + i;
+		Page				page;
+
+		if (i > 0 && xln->blkno == (xlns + i - 1)->blkno)
+		{
+			buffers[i] = buffers[i-1];
+			continue;
+		}
+
+		if (xln->blkno == xldata->blknoSrc && (record->xl_info & XLR_BKP_BLOCK_1))
+		{
+			buffers[i] = InvalidBuffer;
+			continue;
+		}
+
+		buffers[i] = XLogReadBuffer(xldata->node, xln->blkno, xln->cleanup && (xln->blkno != xldata->blknoSrc));
+		page = BufferGetPage(buffers[i]);
+
+		if (!XLByteLE(lsn, PageGetLSN(page)))
+		{
+			if (xln->cleanup)
+				SpGistInitBuffer(buffers[i], SPGIST_LEAF);
+			else if (xldata->nDelete && xln->blkno == xldata->blknoSrc)
+				spgPageIndexMultiDead(&state, page, toDelete, xldata->nDelete,
+										SPGIST_METAPAGE_BLKNO, FirstOffsetNumber /* set redirect to invalid */);
+
+		}
+		else
+		{
+			UnlockReleaseBuffer(buffers[i]);
+			buffers[i] = InvalidBuffer;
+		}
+	}
+
+	for(i=0; i<xldata->nTuples; i++)
+	{
+		SpGistLeafTuple		lt = (SpGistLeafTuple)ptr;
+		Page				page;
+		OffsetNumber		o;
+
+		ptr += lt->size;
+
+		if (buffers[mapTuplesToNodes[i]] == InvalidBuffer)
+			continue;
+
+		page = BufferGetPage(buffers[mapTuplesToNodes[i]]);
+
+		Assert(lt->size > 0);
+		if ((o = SpGistPageAddNewItem(&state, page, (Item)lt, lt->size, NULL)) == InvalidOffsetNumber)
+			elog(ERROR, "failed to add item to SPGiST index page, size %d bytes", lt->size);
+	}
+
+	for(i=0; i<xldata->nNodes; i++)
+	{
+		Page				page;
+
+		if (i>0 && buffers[i] == buffers[i-1])
+			continue;
+
+		if (buffers[i] == InvalidBuffer)
+			continue;
+
+		page = BufferGetPage(buffers[i]);
+
+		PageSetLSN(page, lsn);
+		PageSetTLI(page, ThisTimeLineID);
+		MarkBufferDirty(buffers[i]);
+		UnlockReleaseBuffer(buffers[i]);
+	}
+}
+
+static void
+spgRedoVacuumFake(XLogRecPtr lsn, XLogRecord *record)
+{
+	char       			*begin = XLogRecGetData(record);
+	spgxlogVacuumFake	*xldata = (spgxlogVacuumFake*)begin;
+
+	if ((record->xl_info & XLR_BKP_BLOCK_1) == 0)
+	{
+		Buffer	buffer = XLogReadBuffer(xldata->node, xldata->blkno, false);
+		Page	page;
+
+		Assert(BufferIsValid(buffer));
+		page = BufferGetPage(buffer);
+
+		if (!XLByteLE(lsn, PageGetLSN(page)))
+		{
+			OffsetNumber		*itemToDead = (OffsetNumber*)(begin + sizeof(spgxlogVacuumFake));
+			int 				i;
+			SpGistPageOpaque	opaque = SpGistPageGetOpaque(page);
+
+			for(i=0; i<xldata->nToDead; i++)
+			{
+				SpGistFakeTuple lt = (SpGistFakeTuple) PageGetItem(page, PageGetItemId(page, itemToDead[i]));
+
+				Assert(lt->isRedirect);
+
+				lt->isRedirect = 0;
+				lt->isDead = 1;
+
+				ItemPointerSetInvalid(&lt->pointer);
+			}
+
+			Assert(opaque->nRedirection >= xldata->nToDead);
+			opaque->nRedirection -= xldata->nToDead;
+			opaque->nDead += xldata->nToDead;
+
+			/*
+			 * dead tuples at the end of page could be safely removed
+			 */
+			if (xldata->lastDead != InvalidOffsetNumber)
+			{
+				OffsetNumber		*toDelete = palloc(sizeof(OffsetNumber) * SpGistPageGetMaxOffset(page));
+
+				for(i=xldata->lastDead; i<=SpGistPageGetMaxOffset(page); i++)
+					toDelete[i - xldata->lastDead] = i;
+
+				Assert(opaque->nDead >= SpGistPageGetMaxOffset(page) - xldata->lastDead + 1);
+				opaque->nDead -= SpGistPageGetMaxOffset(page) - xldata->lastDead + 1;
+				PageIndexMultiDelete(page, toDelete, SpGistPageGetMaxOffset(page) - xldata->lastDead + 1);
+			}
+
+			PageSetLSN(page, lsn);
+			PageSetTLI(page, ThisTimeLineID);
+			MarkBufferDirty(buffer);
+		}
+
+		UnlockReleaseBuffer(buffer);
+	}
+}
+
+static void
+spgRedoVacuum(XLogRecPtr lsn, XLogRecord *record)
+{
+	char       			*begin = XLogRecGetData(record);
+	spgxlogVacuum		*xldata = (spgxlogVacuum*)begin;
+	OffsetNumber		*toDead = (OffsetNumber*)(begin + sizeof(spgxlogVacuum));
+	OffsetNumber		*toAlive = (OffsetNumber*)(begin + sizeof(spgxlogVacuum) + 
+													sizeof(*toDead) * xldata->nDead);
+	SpGistState			state;
+
+	fillFakeState(&state, xldata->stateSrc);
+
+	if ((record->xl_info & XLR_BKP_BLOCK_1) == 0)
+	{
+		Buffer	buffer = XLogReadBuffer(xldata->node, xldata->blkno, false);
+		Page	page;
+
+		Assert(BufferIsValid(buffer));
+		page = BufferGetPage(buffer);
+
+		if (!XLByteLE(lsn, PageGetLSN(page)))
+		{
+			if (xldata->blkno == SPGIST_HEAD_BLKNO)
+			{
+				PageIndexMultiDelete(page, toDead, xldata->nDead);
+			}
+			else
+			{
+				int i;
+
+				spgPageIndexMultiDead(&state, page, toDead, xldata->nDead, InvalidBlockNumber, InvalidOffsetNumber);
+
+				for(i=0; i<xldata->nAlive; i++)
+				{
+					SpGistLeafTuple lt = (SpGistLeafTuple) PageGetItem(page, PageGetItemId(page, toAlive[i]));
+
+					lt->nextOffset = (i == xldata->nAlive-1) ? InvalidOffsetNumber : toAlive[i+1];
+				}
+			}
+
+			PageSetLSN(page, lsn);
+			PageSetTLI(page, ThisTimeLineID);
+			MarkBufferDirty(buffer);
+		}
+
+		UnlockReleaseBuffer(buffer);
+	}
+
+	if (xldata->blknoParent != InvalidBlockNumber && (record->xl_info & XLR_BKP_BLOCK_2) == 0)
+	{
+		Buffer	buffer = XLogReadBuffer(xldata->node, xldata->blknoParent, false);
+		Page	page;
+
+		Assert(BufferIsValid(buffer));
+		page = BufferGetPage(buffer);
+
+		if (!XLByteLE(lsn, PageGetLSN(page)))
+		{
+			SpGistInnerTuple innerTuple = (SpGistInnerTuple)PageGetItem(page, 
+											PageGetItemId(page, xldata->offnumParent));
+
+			updateNodeLink(innerTuple, xldata->nodeI, 
+							(xldata->nAlive == 0) ? InvalidBlockNumber : xldata->blkno, 
+							(xldata->nAlive == 0) ? InvalidOffsetNumber : toAlive[0]);
+		
+			PageSetLSN(page, lsn);
+			PageSetTLI(page, ThisTimeLineID);
+			MarkBufferDirty(buffer);
+		}
+
+		UnlockReleaseBuffer(buffer);
+	}
+}
+
+void
+spg_redo(XLogRecPtr lsn, XLogRecord *record)
+{
+	uint8		info = record->xl_info & ~XLR_INFO_MASK;
+	MemoryContext oldCxt;
+
+	/*
+	 * GiST indexes do not require any conflict processing. NB: If we ever
+	 *) implement a similar optimization we have in b-tree, and remove killed
+	 * tuples outside VACUUM, we'll need to handle that here.
+	 */
+	RestoreBkpBlocks(lsn, record, false);
+
+	oldCxt = MemoryContextSwitchTo(opCtx);
+	switch (info)
+	{
+		case XLOG_SPGIST_CREATE_INDEX:
+			spgRedoCreateIndex(lsn, record);
+			break;
+		case XLOG_SPGIST_ADD_LEAF:
+			spgRedoAddLeaf(lsn, record);
+			break;
+		case XLOG_SPGIST_MOVE_LEAFS:
+			spgRedoMoveLeafs(lsn, record);
+			break;
+		case XLOG_SPGIST_ADD_NODE:
+			spgRedoAddNode(lsn, record);
+			break;
+		case XLOG_SPGIST_SPLIT_TUPLE:
+			spgRedoSplitTuple(lsn, record);
+			break;
+		case XLOG_SPGIST_PICKSPLIT:
+			spgRedoPickSplit(lsn, record);
+			break;
+		case XLOG_SPGIST_VACUUM_FAKE:
+			spgRedoVacuumFake(lsn, record);
+			break;
+		case XLOG_SPGIST_VACUUM:
+			spgRedoVacuum(lsn, record);
+			break;
+		default:
+			elog(PANIC, "spg_redo: unknown op code %u", info);
+	}
+
+	MemoryContextSwitchTo(oldCxt);
+	MemoryContextReset(opCtx);
+}
+
+static void
+out_target(StringInfo buf, RelFileNode node)
+{
+	appendStringInfo(buf, "rel %u/%u/%u ",
+					 node.spcNode, node.dbNode, node.relNode);
+}
+
+void
+spg_desc(StringInfo buf, uint8 xl_info, char *rec)
+{
+	uint8		info = xl_info & ~XLR_INFO_MASK;
+
+	switch (info)
+	{
+		case XLOG_SPGIST_CREATE_INDEX:
+			appendStringInfo(buf, "create_index: rel %u/%u/%u",
+								((RelFileNode *) rec)->spcNode,
+								((RelFileNode *) rec)->dbNode,
+								((RelFileNode *) rec)->relNode);
+			break;
+		case XLOG_SPGIST_ADD_LEAF:
+			out_target(buf, ((spgxlogAddLeaf*)rec)->node);
+			appendStringInfo(buf, "add leaf to page: %u",
+									((spgxlogAddLeaf*)rec)->blknoLeaf);
+			break;
+		case XLOG_SPGIST_MOVE_LEAFS:
+			out_target(buf, ((spgxlogMoveLeafs*)rec)->node);
+			appendStringInfo(buf, "move %d leafs from page %u to page %u",
+									((spgxlogMoveLeafs*)rec)->nMoves,
+									((spgxlogMoveLeafs*)rec)->blknoSrc,
+									((spgxlogMoveLeafs*)rec)->blknoDst);
+			break;
+		case XLOG_SPGIST_ADD_NODE:
+			out_target(buf, ((spgxlogAddNode*)rec)->node);
+			appendStringInfo(buf, "add node to %u:%u",
+									((spgxlogAddNode*)rec)->blkno,
+									((spgxlogAddNode*)rec)->offnum);
+			break;
+		case XLOG_SPGIST_SPLIT_TUPLE:
+			out_target(buf, ((spgxlogSplitTuple*)rec)->node);
+			appendStringInfo(buf, "split node %u:%u",
+									((spgxlogSplitTuple*)rec)->blknoPrefix,
+									((spgxlogSplitTuple*)rec)->offnumPrefix);
+			break;
+		case XLOG_SPGIST_PICKSPLIT:
+			out_target(buf, ((spgxlogPickSplit*)rec)->node);
+			appendStringInfo(buf, "split leaf page");
+			break;
+		case XLOG_SPGIST_VACUUM_FAKE:
+			out_target(buf, ((spgxlogVacuumFake*)rec)->node);
+			appendStringInfo(buf, "vacuum fake tuples on page %u",
+									((spgxlogVacuumFake*)rec)->blkno);
+			break;
+		case XLOG_SPGIST_VACUUM:
+			out_target(buf, ((spgxlogVacuum*)rec)->node);
+			appendStringInfo(buf, "vacuum leaf tuples on page %u",
+									((spgxlogVacuum*)rec)->blkno);
+			break;
+		default:
+			appendStringInfo(buf, "unknown spggist op code %u", info);
+			break;
+	}
+}
+
+void
+spg_xlog_startup(void)
+{
+	opCtx = AllocSetContextCreate(CurrentMemoryContext,
+								 	"SP-GiST temporary context",
+									ALLOCSET_DEFAULT_MINSIZE,
+									ALLOCSET_DEFAULT_INITSIZE,
+									ALLOCSET_DEFAULT_MAXSIZE);
+}
+
+void
+spg_xlog_cleanup(void)
+{
+	MemoryContextDelete(opCtx);
+}
+
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 6a0a2d9..682558e 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -14,6 +14,7 @@
 #include "access/heapam.h"
 #include "access/multixact.h"
 #include "access/nbtree.h"
+#include "access/spgist_private.h"
 #include "access/xact.h"
 #include "access/xlog_internal.h"
 #include "catalog/storage.h"
@@ -40,5 +41,6 @@ const RmgrData RmgrTable[RM_MAX_ID + 1] = {
 	{"Hash", hash_redo, hash_desc, NULL, NULL, NULL},
 	{"Gin", gin_redo, gin_desc, gin_xlog_startup, gin_xlog_cleanup, gin_safe_restartpoint},
 	{"Gist", gist_redo, gist_desc, gist_xlog_startup, gist_xlog_cleanup, NULL},
-	{"Sequence", seq_redo, seq_desc, NULL, NULL, NULL}
+	{"Sequence", seq_redo, seq_desc, NULL, NULL, NULL},
+	{"SPGist", spg_redo, spg_desc, spg_xlog_startup, spg_xlog_cleanup, NULL}
 };
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index f5660b2..b60fc73 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -6555,6 +6555,26 @@ gistcostestimate(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+Datum
+spgcostestimate(PG_FUNCTION_ARGS)
+{
+	PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0);
+	IndexOptInfo *index = (IndexOptInfo *) PG_GETARG_POINTER(1);
+	List	   *indexQuals = (List *) PG_GETARG_POINTER(2);
+	List	   *indexOrderBys = (List *) PG_GETARG_POINTER(3);
+	RelOptInfo *outer_rel = (RelOptInfo *) PG_GETARG_POINTER(4);
+	Cost	   *indexStartupCost = (Cost *) PG_GETARG_POINTER(5);
+	Cost	   *indexTotalCost = (Cost *) PG_GETARG_POINTER(6);
+	Selectivity *indexSelectivity = (Selectivity *) PG_GETARG_POINTER(7);
+	double	   *indexCorrelation = (double *) PG_GETARG_POINTER(8);
+
+	genericcostestimate(root, index, indexQuals, indexOrderBys, outer_rel, 0.0,
+						indexStartupCost, indexTotalCost,
+						indexSelectivity, indexCorrelation);
+
+	PG_RETURN_VOID();
+}
+
 /* Find the index column matching "op"; return its index, or -1 if no match */
 static int
 find_index_column(Node *op, IndexOptInfo *index)
@@ -6938,3 +6958,4 @@ gincostestimate(PG_FUNCTION_ARGS)
 
 	PG_RETURN_VOID();
 }
+
diff --git a/src/include/access/rmgr.h b/src/include/access/rmgr.h
index 83abba3..75eacc8 100644
--- a/src/include/access/rmgr.h
+++ b/src/include/access/rmgr.h
@@ -32,6 +32,7 @@ typedef uint8 RmgrId;
 #define RM_GIN_ID				13
 #define RM_GIST_ID				14
 #define RM_SEQ_ID				15
-#define RM_MAX_ID				RM_SEQ_ID
+#define RM_SPGIST_ID			16
+#define RM_MAX_ID				RM_SPGIST_ID
 
 #endif   /* RMGR_H */
diff --git a/src/include/access/spgist.h b/src/include/access/spgist.h
new file mode 100644
index 0000000..43cb4c9
--- /dev/null
+++ b/src/include/access/spgist.h
@@ -0,0 +1,160 @@
+#ifndef _SPGIST_H_
+#define _SPGIST_H_
+
+#include "postgres.h"
+#include "fmgr.h"
+#include "xlog.h"
+#include "access/skey.h"
+
+
+#define SPGIST_PROP_PROC		1
+#define SPGIST_CHOOSE_PROC		2
+#define SPGIST_PICKSPLIT_PROC	3
+#define SPGIST_LEAFCONS_PROC	4
+#define SPGIST_INNERCONS_PROC	5
+#define SPGISTNProc         	5
+
+/*
+ * return struct for spgist_config
+ */
+
+typedef struct SpGistOpClassProp 
+{
+	Oid				prefixType;
+	Oid				nodeType;
+} SpGistOpClassProp;
+
+/*
+ * spg_choose method
+ */
+typedef struct spgChooseIn
+{
+	Datum	datum; /* full indexing datum */
+	int		level;
+
+	/*
+	 * Inner tuple data
+	 */
+	bool	allTheSame;
+	bool	hasPrefix;
+	Datum	prefixDatum;
+
+	int		nNodes;
+	Datum	*nodeDatums;
+} spgChooseIn;
+
+typedef enum spgChooseResultType
+{
+	spgMatchNode = 0,
+	spgAddNode,
+	spgSplitTuple
+} spgChooseResultType;
+
+typedef struct spgChooseOut
+{
+	spgChooseResultType	resultType;
+	union {
+		struct {
+			int		nodeN;
+			int		levelAdd;
+			Datum	restDatum;
+		} matchNode;
+		struct {
+			Datum	nodeDatum;
+			int		nodeN;
+		} addNode;
+		struct {
+			/* new inner tuple with one node */
+			bool	prefixHasPrefix;
+			Datum	prefixPrefixDatum;
+			Datum	nodeDatum; /* this node points to postfix */
+
+			/* new next inner tuple with all old nodes */ 
+			bool	postfixHasPrefix;
+			Datum	postfixPrefixDatum;
+		} splitTuple;
+	} result; 
+} spgChooseOut;
+
+/*
+ * spg_picksplit method
+ */
+
+typedef struct spgPickSplitIn
+{
+	int		nTuples;
+	Datum	*datums;
+	int		level;
+} spgPickSplitIn;
+
+typedef struct spgPickSplitOut
+{
+	bool	hasPrefix;
+	Datum	prefixDatum;
+	int		addLevel;
+
+	bool	allTheSame;
+	int		nNodes;
+	Datum	*nodeDatums;
+
+	int		*mapTuplesToNodes;
+	Datum	*leafTupleDatums;
+} spgPickSplitOut;
+
+/*
+ * spg_inner_consistent method
+ */
+
+typedef struct spgInnerConsistentIn
+{
+	StrategyNumber	strategy;
+	Datum			query;
+
+	int				level;
+	Datum			reconstructedValue;
+
+	bool			hasPrefix;
+	Datum			prefixDatum;
+
+	int				nNodes;
+	Datum			*nodeDatums;
+} spgInnerConsistentIn;
+
+typedef struct spgInnerConsistentOut
+{
+	int		nNodes;
+	int		levelAdd; /* including node's levels */
+	int		*nodeNumbers;
+	Datum	*reconstructedValues;
+} spgInnerConsistentOut;
+
+
+/* spgutils.h */
+extern Datum spgoptions(PG_FUNCTION_ARGS);
+
+extern Datum spgstat(PG_FUNCTION_ARGS);
+
+/* spginsert.c */
+extern Datum spgbuild(PG_FUNCTION_ARGS);
+extern Datum spgbuildempty(PG_FUNCTION_ARGS);
+extern Datum spginsert(PG_FUNCTION_ARGS);
+
+/* spgscan.c */
+extern Datum spgbeginscan(PG_FUNCTION_ARGS);
+extern Datum spgendscan(PG_FUNCTION_ARGS);
+extern Datum spgrescan(PG_FUNCTION_ARGS);
+extern Datum spgmarkpos(PG_FUNCTION_ARGS);
+extern Datum spgrestrpos(PG_FUNCTION_ARGS);
+extern Datum spggetbitmap(PG_FUNCTION_ARGS);
+extern Datum spggettuple(PG_FUNCTION_ARGS);
+
+/* spgvacuum.c */
+extern Datum spgbulkdelete(PG_FUNCTION_ARGS);
+extern Datum spgvacuumcleanup(PG_FUNCTION_ARGS);
+
+/* spgxlog.c */
+extern void spg_redo(XLogRecPtr lsn, XLogRecord *record);
+extern void spg_desc(StringInfo buf, uint8 xl_info, char *rec);
+extern void spg_xlog_startup(void);
+extern void spg_xlog_cleanup(void);
+#endif
diff --git a/src/include/access/spgist_private.h b/src/include/access/spgist_private.h
new file mode 100644
index 0000000..373f7f1
--- /dev/null
+++ b/src/include/access/spgist_private.h
@@ -0,0 +1,426 @@
+#ifndef _SPGIST_PRIVATE_H_
+#define _SPGIST_PRIVATE_H_
+
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/itup.h"
+#include "access/spgist.h"
+#include "access/xlog.h"
+#include "fmgr.h"
+
+
+typedef struct SpGistPageOpaqueData
+{
+	TransactionId  	vacuumMark; 
+	unsigned int	
+					nRedirection:13,
+					flags:3,
+					nDead:16;
+} SpGistPageOpaqueData;
+
+typedef SpGistPageOpaqueData *SpGistPageOpaque;
+
+#define SPGIST_META     	(1<<0)
+#define SPGIST_DELETED  	(1<<1)
+#define SPGIST_LEAF   		(1<<2)
+
+#define SpGistPageGetOpaque(page) ( (SpGistPageOpaque) PageGetSpecialPointer(page) )
+#define SpGistPageGetMaxOffset(page) PageGetMaxOffsetNumber(page)
+#define SpGistPageIsMeta(page) ( SpGistPageGetOpaque(page)->flags & SPGIST_META)
+#define SpGistPageIsDeleted(page) ( SpGistPageGetOpaque(page)->flags & SPGIST_DELETED)
+#define SpGistPageSetDeleted(page)    ( SpGistPageGetOpaque(page)->flags |= SPGIST_DELETED)
+#define SpGistPageSetNonDeleted(page) ( SpGistPageGetOpaque(page)->flags &= ~SPGIST_DELETED)
+#define SpGistPageIsLeaf(page) ( SpGistPageGetOpaque(page)->flags & SPGIST_LEAF)
+#define SpGistPageSetLeaf(page)    ( SpGistPageGetOpaque(page)->flags |= SPGIST_LEAF)
+#define SpGistPageSetInner(page) ( SpGistPageGetOpaque(page)->flags &= ~SPGIST_LEAF)
+
+/* XXX inner pages ? */
+#define SpGistPageGetFreeSpace(s, p, n) \
+	(PageGetFreeSpace(p) + \
+		Min(SpGistPageGetOpaque(p)->nDead, n) * \
+			(sizeof(ItemIdData) + (s)->fakeTupleSize)) 
+
+#define SPGIST_METAPAGE_BLKNO    (0)
+#define SPGIST_HEAD_BLKNO        (1)
+
+#define SPGIST_PAGE_CAPACITY	MAXALIGN_DOWN(BLCKSZ - \
+												SizeOfPageHeaderData - \
+												MAXALIGN(sizeof(SpGistPageOpaqueData)))
+
+/*
+ * index->rd_amcache data
+ */
+typedef struct SpGistLastUsedPage {
+	BlockNumber	blkno;
+	int			freeSpace; /* could be wrong ! */
+} SpGistLastUsedPage;
+
+typedef struct SpGistCache {
+	SpGistLastUsedPage	innerPage[3] /* to store triple parity pages */;
+	SpGistLastUsedPage	leafPage;
+} SpGistCache;
+
+/*
+ * metapage
+ */
+
+typedef struct SpGistMetaPageData
+{
+	uint32                  magickNumber;
+	SpGistCache				lastUsedPages;
+} SpGistMetaPageData;
+#define SPGIST_MAGIC_NUMBER (0xBA0BABED)
+
+#define SpGistPageGetMeta(p) \
+    ((SpGistMetaPageData *) PageGetContents(p))
+
+typedef struct SpGistTypeDesc 
+{
+	Oid		type;
+	bool	attbyval;
+	int16	attlen;
+} SpGistTypeDesc;
+
+/*
+ * inner state of index
+ */
+typedef struct SpGistState
+{
+	SpGistOpClassProp	prop;
+	SpGistTypeDesc		attType;
+	SpGistTypeDesc		attNodeType;
+	SpGistTypeDesc		attPrefixType; /* optional */
+	
+	FmgrInfo			chooseFn;
+	FmgrInfo			picksplitFn;
+	FmgrInfo			leafConsistentFn;
+	FmgrInfo			innerConsistentFn;
+
+	TupleDesc			nodeTupDesc;
+
+	int					fakeTupleSize;
+	char				*fakeTupleStorage;
+	bool				isBuild;
+} SpGistState;
+
+typedef struct SpGistScanOpaqueData
+{
+	SpGistState  	state;
+	MemoryContext 	tempCxt;
+	List			*scanStack; /* List of ItemPointers */
+
+	union {
+		struct {
+			TIDBitmap	*tbm;
+			int64		ntids;
+		} bitmapscan;
+		struct {
+			ItemPointerData	heapPtrs[BLCKSZ / sizeof(IndexTupleData) /* just an estimation */];
+			bool			recheck[BLCKSZ / sizeof(IndexTupleData)];
+			int				nPtrs;
+			int				iPtr;
+		} gettuple;
+	};
+} SpGistScanOpaqueData;
+
+typedef SpGistScanOpaqueData *SpGistScanOpaque;
+
+/*
+ * node/tuple types. Note: all three types of tuple
+ * should have the same first two fields!
+ * 
+ * Inner tuple layout:
+ * (bit of presence of prefix)(n nodes)(size)[(prefix)](array of nodes)
+ */
+typedef struct SpGistInnerTupleData
+{
+	unsigned int	isDead:1,
+					isRedirect:1,
+					allTheSame:1,
+					nNodes:13,
+					prefixSize:16;
+	uint16			size;	
+	char			data[1]; /* variable size */
+} SpGistInnerTupleData;
+typedef SpGistInnerTupleData *SpGistInnerTuple;
+
+#define SGITHDRSZ			MAXALIGN(offsetof(SpGistInnerTupleData, data))
+#define  _SGITDATA(x)		( ((char*)(x)) + SGITHDRSZ )
+#define SGITDATAPTR(x)  	( ((x)->prefixSize) ? _SGITDATA(x) : NULL )
+#define SGITDATUM(x, s)		( ((x)->prefixSize) ? \
+								( \
+									((s)->attPrefixType.attbyval) ? \
+									( *(Datum*)_SGITDATA(x) ) \
+									: \
+									PointerGetDatum(_SGITDATA(x)) \
+								) \
+								: (Datum)0 )
+#define SGITNODEPTR(x) 		( (IndexTuple)( _SGITDATA(x) + (x)->prefixSize ) )
+
+#define SGITITERATE(x, i, it)	\
+	for((i) = 0, (it)= SGITNODEPTR((x)) ; \
+		(i) < (x)->nNodes; \
+		(i)++, (it) = (IndexTuple)(((char*)(it)) + IndexTupleSize(it))) 
+
+
+/*
+ * indexed value could be empty (not a NULL) if it's
+ * fully encoded in a tree path
+ */
+typedef struct SpGistLeafTupleData
+{
+	unsigned int	isDead:1,
+					isRedirect:1,
+					size:14,
+					nextOffset:16; /* OffsetNumber actually */
+	ItemPointerData	heapPtr;
+	char			data[1]; /* variable size */
+} SpGistLeafTupleData;
+typedef SpGistLeafTupleData *SpGistLeafTuple;
+
+#define SGLTHDRSZ			MAXALIGN(offsetof(SpGistLeafTupleData, data))
+#define SGLTDATAPTR(x)  	( ((char*)(x)) + SGLTHDRSZ )
+#define SGLTDATUM(x, s)		( ((s)->attType.attbyval) ? \
+								( *(Datum*)SGLTDATAPTR(x) ) \
+								: \
+								PointerGetDatum(SGLTDATAPTR(x)) \
+							)
+
+typedef struct SpGistFakeTupleData
+{
+	unsigned int	isDead:1,
+					isRedirect:1,
+					size:14,
+					unused:16;
+	ItemPointerData	pointer; /* redirection inside index */
+	TransactionId	xid;     /* xid of transaction which inserts redirection tuple */
+} SpGistFakeTupleData;
+typedef SpGistFakeTupleData *SpGistFakeTuple;
+#define SGFTSIZE		MAXALIGN(sizeof(SpGistFakeTupleData))
+
+/*
+ * XLOG stuff
+ *
+ * ACCEPT_RDATA_* could use only statically allocated rdata
+ */
+
+#define ACCEPT_RDATA_DATA(p, s, i)  do {	\
+	Assert(i < lengthof(rdata));			\
+	rdata[(i)].buffer = InvalidBuffer;		\
+	rdata[(i)].buffer_std = true;			\
+	rdata[(i)].data = (char*)(p);			\
+	rdata[(i)].len = (s);					\
+	rdata[(i)].next = NULL;					\
+	if ( (i) > 0 )							\
+		rdata[(i) - 1].next = rdata + (i);	\
+} while(0)
+
+#define ACCEPT_RDATA_BUFFER(b, i)   do {	\
+	Assert(i < lengthof(rdata));			\
+	rdata[(i)].buffer = (b);				\
+	rdata[(i)].buffer_std = true;			\
+	rdata[(i)].data = NULL;					\
+	rdata[(i)].len = 0;						\
+	rdata[(i)].next = NULL;					\
+	if ( (i) > 0 )							\
+		rdata[(i) - 1].next = rdata + (i);	\
+} while(0)
+
+#define XLOG_SPGIST_CREATE_INDEX	0x00
+#define XLOG_SPGIST_ADD_LEAF		0x10
+#define XLOG_SPGIST_MOVE_LEAFS		0x20
+#define XLOG_SPGIST_ADD_NODE		0x30
+#define XLOG_SPGIST_SPLIT_TUPLE		0x40
+#define XLOG_SPGIST_PICKSPLIT		0x50
+#define XLOG_SPGIST_VACUUM_FAKE		0x60
+#define XLOG_SPGIST_VACUUM			0x70
+
+/*
+ * Some redo fuctions need some information is is not accessible
+ * during recovery. Send it into log
+ */
+typedef struct spgxlogState {
+	unsigned int	attType_attlen:16, /* zero means -1 */
+					fakeTupleSize:15,
+					isBuild:1;
+} spgxlogState;
+
+#define STORE_STATE(s, d) do {													\
+	(d).attType_attlen = ((s)->attType.attlen < 0) ? 0 : (s)->attType.attlen;	\
+	(d).fakeTupleSize = (s)->fakeTupleSize;										\
+	(d).isBuild = (s)->isBuild;													\
+} while(0)
+
+typedef struct spgxlogAddLeaf 
+{
+	RelFileNode		node;
+
+	BlockNumber		blknoLeaf;
+	bool			newPage;
+	OffsetNumber	offnumLeaf;
+	OffsetNumber	offnumHeadLeaf;
+
+	BlockNumber		blknoParent;
+	OffsetNumber	offnumParent;
+	uint16			nodeI;
+} spgxlogAddLeaf;
+
+typedef struct spgxlogMoveLeafs
+{
+	RelFileNode		node;
+
+	BlockNumber		blknoSrc;
+	BlockNumber		blknoDst;
+	OffsetNumber	offnumDst;
+	int16			nMoves;
+	bool			newPage;
+
+	BlockNumber		blknoParent;
+	OffsetNumber	offnumParent;
+	uint16			nodeI;
+
+	spgxlogState	stateSrc;
+
+	/*
+	 * list of offset and new leaf tuple are followed
+	 */
+} spgxlogMoveLeafs;
+
+typedef struct spgxlogAddNode {
+	RelFileNode		node;
+
+	BlockNumber		blkno;
+	OffsetNumber	offnum;
+
+	BlockNumber		blknoParent;
+	OffsetNumber	offnumParent;
+	uint16			nodeI;
+
+	BlockNumber		blknoNew;
+	OffsetNumber	offnumNew;
+	bool			setRedirect;
+	bool			newPage;
+
+	spgxlogState	stateSrc;
+
+	/* SpGistInnerTuple follows */
+} spgxlogAddNode;
+
+typedef struct spgxlogSplitTuple {
+	RelFileNode		node;
+
+	BlockNumber		blknoPrefix;
+	OffsetNumber	offnumPrefix;
+
+	BlockNumber		blknoPostfix;
+	OffsetNumber	offnumPostfix;
+	bool			newPage;
+
+	/* both tuples follow */
+} spgxlogSplitTuple;
+
+typedef struct spgxlogPSOneNode {
+	BlockNumber	blkno;
+	bool		cleanup;
+} spgxlogPSOneNode;
+
+typedef struct spgxlogPickSplit {
+	RelFileNode		node;
+	
+	uint16			nTuples;
+	uint16			nNodes;
+	BlockNumber		blknoSrc;
+	uint16			nDelete;
+
+	BlockNumber		blknoInner;
+	OffsetNumber	offnumInner;
+	bool			newPage;
+	
+	BlockNumber		blknoParent;
+	OffsetNumber	offnumParent;
+	uint16			nodeI;
+
+	spgxlogState	stateSrc;
+
+	/*
+	 * data follows:
+	 * inner tuple;
+	 * array of deleted tuples
+	 * array of spgxlogPSOneNode
+	 * map array
+	 * list of leaf tuples
+	 */
+} spgxlogPickSplit;
+
+typedef struct spgxlogVacuumFake {
+	RelFileNode		node;
+
+	BlockNumber		blkno;
+	uint16			nToDead;
+	OffsetNumber	lastDead;
+	
+	/* list of offsets */
+} spgxlogVacuumFake;
+
+typedef struct spgxlogVacuum {
+	RelFileNode		node;
+
+	BlockNumber		blkno;
+	int				nDead;
+	int				nAlive;
+
+	BlockNumber		blknoParent;
+	OffsetNumber	offnumParent;
+	uint16			nodeI;
+
+	spgxlogState	stateSrc;
+
+} spgxlogVacuum;
+
+/* spgutils.h */
+extern void initSpGistState(SpGistState *state, Relation index);
+extern Buffer SpGistNewBuffer(Relation index);
+extern void SpGistInitPage(Page page, uint16 f, Size pageSize);
+extern void SpGistInitBuffer(Buffer b, uint16 f);
+extern void SpGistInitMetabuffer(Buffer b);
+extern void SpGistUpdateMetaPage(Relation index);
+
+extern unsigned int getTypeLength(SpGistTypeDesc *att, Datum datum);
+extern SpGistLeafTuple spgFormLeafTuple(SpGistState *state, ItemPointer heapPtr, Datum datum, bool isnull);
+extern SpGistInnerTuple spgFormInnerTuple(SpGistState *state, bool hasPrefix, Datum prefix, 
+									int nNodes, IndexTuple *nodes);
+extern SpGistFakeTuple spgFormFakeTuple(SpGistState *state, 
+									BlockNumber blkno, OffsetNumber offnum);
+extern OffsetNumber SpGistPageAddNewItem(SpGistState *state, Page page, Item item, 
+								Size size, OffsetNumber *startOffset);
+
+extern void spgPageIndexMultiDead(SpGistState *state, Page page, 
+							OffsetNumber *itemnos, int nitems,
+       			            /* redirect chain */
+							BlockNumber blkno, OffsetNumber offnum);
+
+/*
+ * flags for SpGistGetBuffer. They support three option:
+ * leaf, odd inner, even inner. Leaf/inner is encoded by third
+ * bit, triplesity - first bit to simplify encoding: 
+ * GBUF_INNER | (blkno & GBUF_PARITY_MASK)
+ */
+
+#define GBUF_PARITY_MASK	0x03
+#define GBUF_LEAF			0x00
+#define	GBUF_INNER			0x04
+
+/*
+ * encode flags to get next page in triple parity
+ */
+#define ENCODE_INNER_PARITY(x)	(GBUF_INNER | ((x) % 3))
+
+extern Buffer SpGistGetBuffer(Relation index, int flags, int needSpace, bool *isNew);
+extern void SpGistSetLastUsedPage(Relation index, Buffer buffer);
+
+/* spgdoinsert.c */
+extern void updateNodeLink(SpGistInnerTuple tup, int nodeN, BlockNumber blkno, OffsetNumber offset);
+extern void spgdoinsert(Relation index, SpGistState *state, ItemPointer heapPtr, Datum datum);
+
+#endif
diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h
index 8b075d3..63c8c55 100644
--- a/src/include/catalog/pg_am.h
+++ b/src/include/catalog/pg_am.h
@@ -129,5 +129,8 @@ DESCR("GiST index access method");
 DATA(insert OID = 2742 (  gin	0 5 f f f f t f t f f t f f 0 gininsert ginbeginscan - gingetbitmap ginrescan ginendscan ginmarkpos ginrestrpos ginbuild ginbuildempty ginbulkdelete ginvacuumcleanup gincostestimate ginoptions ));
 DESCR("GIN index access method");
 #define GIN_AM_OID 2742
+DATA(insert OID = 6024 (  spgist	0 5 f f f f f f f f f f f f 2281 spginsert spgbeginscan spggettuple spggetbitmap spgrescan spgendscan spgmarkpos spgrestrpos spgbuild spgbuildempty spgbulkdelete spgvacuumcleanup spgcostestimate spgoptions ));
+DESCR("SP-GiST index access method");
+#define SPGIST_AM_OID 6024
 
 #endif   /* PG_AM_H */
diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h
index 1e8c9a2..b6ca9d6 100644
--- a/src/include/catalog/pg_amop.h
+++ b/src/include/catalog/pg_amop.h
@@ -737,4 +737,39 @@ DATA(insert (	3919   3831 3831 8 s	3892 783 0 ));
 DATA(insert (	3919   3831 2283 16 s	3889 783 0 ));
 DATA(insert (	3919   3831 3831 18 s	3882 783 0 ));
 
+/*
+ * SP-GiST quad_point_ops
+ */
+DATA(insert (	6026   600 600 11 s 506 6024 0 ));
+DATA(insert (	6026   600 600 1 s	507 6024 0 ));
+DATA(insert (	6026   600 600 5 s	508 6024 0 ));
+DATA(insert (	6026   600 600 10 s 509 6024 0 ));
+DATA(insert (	6026   600 600 6 s	510 6024 0 ));
+DATA(insert (	6026   603 600 7 s 433 6024 0 ));
+DATA(insert (	6026   600 603 8 s 511 6024 0 ));
+
+/*
+ *	SP-GiST text_ops
+ */
+DATA(insert (	6027   25 25 1 s 	2314 6024 0 ));
+DATA(insert (	6027   25 25 2 s 	2315 6024 0 ));
+DATA(insert (	6027   25 25 3 s	98	6024 0 ));
+DATA(insert (	6027   25 25 4 s 	2317 6024 0 ));
+DATA(insert (	6027   25 25 5 s 	2318 6024 0 ));
+DATA(insert (	6027   25 25 14 s	667 6024 0 ));
+DATA(insert (	6027   25 25 15 s	666 6024 0 ));
+DATA(insert (	6027   25 25 11 s	664 6024 0 ));
+DATA(insert (	6027   25 25 12 s	665 6024 0 ));
+
+/*
+ * SP-GiST kd_point_ops
+ */
+DATA(insert (	6028   600 600 11 s 506 6024 0 ));
+DATA(insert (	6028   600 600 1 s	507 6024 0 ));
+DATA(insert (	6028   600 600 5 s	508 6024 0 ));
+DATA(insert (	6028   600 600 10 s 509 6024 0 ));
+DATA(insert (	6028   600 600 6 s	510 6024 0 ));
+DATA(insert (	6028   603 600 7 s 433 6024 0 ));
+DATA(insert (	6028   600 603 8 s 511 6024 0 ));
+
 #endif   /* PG_AMOP_H */
diff --git a/src/include/catalog/pg_amproc.h b/src/include/catalog/pg_amproc.h
index e5d43f7..f61db42 100644
--- a/src/include/catalog/pg_amproc.h
+++ b/src/include/catalog/pg_amproc.h
@@ -346,4 +346,21 @@ DATA(insert (	3919   3831 3831 5 3879 ));
 DATA(insert (	3919   3831 3831 6 3880 ));
 DATA(insert (	3919   3831 3831 7 3881 ));
 
+/* spgist */
+DATA(insert (	6026   600 600 1 6014 ));
+DATA(insert (	6026   600 600 2 6015 ));
+DATA(insert (	6026   600 600 3 6016 ));
+DATA(insert (	6026   600 600 4 6018 ));
+DATA(insert (	6026   600 600 5 6017 ));
+DATA(insert (	6027   25 25 1 6019 ));
+DATA(insert (	6027   25 25 2 6020 ));
+DATA(insert (	6027   25 25 3 6021 ));
+DATA(insert (	6027   25 25 4 6023 ));
+DATA(insert (	6027   25 25 5 6022 ));
+DATA(insert (	6028   600 600 1 6029 ));
+DATA(insert (	6028   600 600 2 6030 ));
+DATA(insert (	6028   600 600 3 6031 ));
+DATA(insert (	6028   600 600 4 6018 ));
+DATA(insert (	6028   600 600 5 6032 ));
+
 #endif   /* PG_AMPROC_H */
diff --git a/src/include/catalog/pg_opclass.h b/src/include/catalog/pg_opclass.h
index eecd3b6..5f303dc 100644
--- a/src/include/catalog/pg_opclass.h
+++ b/src/include/catalog/pg_opclass.h
@@ -223,5 +223,8 @@ DATA(insert (	783		tsquery_ops			PGNSP PGUID 3702  3615 t 20 ));
 DATA(insert (	403		range_ops			PGNSP PGUID 3901  3831 t 0 ));
 DATA(insert (	405		range_ops			PGNSP PGUID 3903  3831 t 0 ));
 DATA(insert (	783		range_ops			PGNSP PGUID 3919  3831 t 0 ));
+DATA(insert (	6024	quad_point_ops		PGNSP PGUID 6026  600 t 0 ));
+DATA(insert (	6024	text_ops			PGNSP PGUID 6027  25 t 0 ));
+DATA(insert (	6024	kd_point_ops		PGNSP PGUID 6028  600 f 0 ));
 
 #endif   /* PG_OPCLASS_H */
diff --git a/src/include/catalog/pg_opfamily.h b/src/include/catalog/pg_opfamily.h
index 5ea949b..d8247c7 100644
--- a/src/include/catalog/pg_opfamily.h
+++ b/src/include/catalog/pg_opfamily.h
@@ -142,5 +142,8 @@ DATA(insert OID = 3702 (	783		tsquery_ops		PGNSP PGUID ));
 DATA(insert OID = 3901 (	403		range_ops		PGNSP PGUID ));
 DATA(insert OID = 3903 (	405		range_ops		PGNSP PGUID ));
 DATA(insert OID = 3919 (	783		range_ops		PGNSP PGUID ));
+DATA(insert OID = 6026 (	6024	quad_point_ops	PGNSP PGUID ));
+DATA(insert OID = 6027 (	6024	text_ops		PGNSP PGUID ));
+DATA(insert OID = 6028 (	6024	kd_point_ops	PGNSP PGUID ));
 
 #endif   /* PG_OPFAMILY_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 28e53b7..7c10653 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4460,6 +4460,70 @@ DESCR("int8range constructor");
 DATA(insert OID = 3946 (  int8range	PGNSP PGUID 12 1 0 0 0 f f f f f i 3 0 3926 "20 20 25" _null_ _null_ _null_ _null_ range_constructor3 _null_ _null_ _null_ ));
 DESCR("int8range constructor");
 
+/* spgist support functions */
+DATA(insert OID = 6000 (  spggettuple	   PGNSP PGUID 12 1 0 0 0 f f f t f v 2 0 16 "2281 2281" _null_ _null_ _null_ _null_	spggettuple _null_ _null_ _null_ ));
+DESCR("spgist(internal)");
+DATA(insert OID = 6001 (  spggetbitmap	   PGNSP PGUID 12 1 0 0 0 f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_	spggetbitmap _null_ _null_ _null_ ));
+DESCR("spgist(internal)");
+DATA(insert OID = 6002 (  spginsert		   PGNSP PGUID 12 1 0 0 0 f f f t f v 6 0 16 "2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_	spginsert _null_ _null_ _null_ ));
+DESCR("spgist(internal)");
+DATA(insert OID = 6003 (  spgbeginscan	   PGNSP PGUID 12 1 0 0 0 f f f t f v 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_	spgbeginscan _null_ _null_ _null_ ));
+DESCR("spgist(internal)");
+DATA(insert OID = 6004 (  spgrescan		   PGNSP PGUID 12 1 0 0 0 f f f t f v 5 0 2278 "2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ spgrescan _null_ _null_ _null_ ));
+DESCR("spgist(internal)");
+DATA(insert OID = 6005 (  spgendscan	   PGNSP PGUID 12 1 0 0 0 f f f t f v 1 0 2278 "2281" _null_ _null_ _null_ _null_ spgendscan _null_ _null_ _null_ ));
+DESCR("spgist(internal)");
+DATA(insert OID = 6006 (  spgmarkpos	   PGNSP PGUID 12 1 0 0 0 f f f t f v 1 0 2278 "2281" _null_ _null_ _null_ _null_ spgmarkpos _null_ _null_ _null_ ));
+DESCR("spgist(internal)");
+DATA(insert OID = 6007 (  spgrestrpos	   PGNSP PGUID 12 1 0 0 0 f f f t f v 1 0 2278 "2281" _null_ _null_ _null_ _null_ spgrestrpos _null_ _null_ _null_ ));
+DESCR("spgist(internal)");
+DATA(insert OID = 6008 (  spgbuild		   PGNSP PGUID 12 1 0 0 0 f f f t f v 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ spgbuild _null_ _null_ _null_ ));
+DESCR("spgist(internal)");
+DATA(insert OID = 6009 (  spgbuildempty    PGNSP PGUID 12 1 0 0 0 f f f t f v 1 0 2278 "2281" _null_ _null_ _null_ _null_ spgbuildempty _null_ _null_ _null_ ));
+DESCR("spgist(internal)");
+DATA(insert OID = 6010 (  spgbulkdelete    PGNSP PGUID 12 1 0 0 0 f f f t f v 4 0 2281 "2281 2281 2281 2281" _null_ _null_ _null_ _null_ spgbulkdelete _null_ _null_ _null_ ));
+DESCR("spgist(internal)");
+DATA(insert OID = 6011 (  spgvacuumcleanup   PGNSP PGUID 12 1 0 0 0 f f f t f v 2 0 2281 "2281 2281" _null_ _null_ _null_ _null_ spgvacuumcleanup _null_ _null_ _null_ ));
+DESCR("spgist(internal)");
+DATA(insert OID = 6012 (  spgcostestimate  PGNSP PGUID 12 1 0 0 0 f f f t f v 9 0 2278 "2281 2281 2281 2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ spgcostestimate _null_ _null_ _null_ ));
+DESCR("spgist(internal)");
+DATA(insert OID = 6013 (  spgoptions	   PGNSP PGUID 12 1 0 0 0 f f f t f s 2 0 17 "1009 16" _null_ _null_ _null_ _null_  spgoptions _null_ _null_ _null_ ));
+DESCR("spgist(internal)");
+
+/* spgist opclasses */
+DATA(insert OID = 6014 (  spg_quad_config	PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 2281 "2281" _null_ _null_ _null_ _null_  spg_quad_config _null_ _null_ _null_ ));
+DESCR("SP-GiST support for quad tree over point");
+DATA(insert OID = 6015 (  spg_quad_choose	PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_quad_choose _null_ _null_ _null_ ));
+DESCR("SP-GiST support for quad tree over point");
+DATA(insert OID = 6016 (  spg_quad_picksplit	PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_quad_picksplit _null_ _null_ _null_ ));
+DESCR("SP-GiST support for quad tree over point");
+DATA(insert OID = 6017 (  spg_quad_inner_consistent	PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_quad_inner_consistent _null_ _null_ _null_ ));
+DESCR("SP-GiST support for quad tree over point");
+DATA(insert OID = 6018 (  spg_quad_leaf_consistent	PGNSP PGUID 12 1 0 0 0 f f f t f i 6 0 16 "23 2281 23 2281 2281 2281" _null_ _null_ _null_ _null_  spg_quad_leaf_consistent _null_ _null_ _null_ ));
+DESCR("SP-GiST support for quad tree and k-d-tree over point");
+
+DATA(insert OID = 6019 (  spg_text_config	PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 2281 "2281" _null_ _null_ _null_ _null_  spg_text_config _null_ _null_ _null_ ));
+DESCR("SP-GiST support for suffix tree over text");
+DATA(insert OID = 6020 (  spg_text_choose	PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_text_choose _null_ _null_ _null_ ));
+DESCR("SP-GiST support for suffix tree over text");
+DATA(insert OID = 6021 (  spg_text_picksplit	PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_text_picksplit _null_ _null_ _null_ ));
+DESCR("SP-GiST support for suffix tree over text");
+DATA(insert OID = 6022 (  spg_text_inner_consistent	PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_text_inner_consistent _null_ _null_ _null_ ));
+DESCR("SP-GiST support for suffix tree over text");
+DATA(insert OID = 6023 (  spg_text_leaf_consistent	PGNSP PGUID 12 1 0 0 0 f f f t f i 6 0 16 "23 2281 23 2281 2281 2281" _null_ _null_ _null_ _null_  spg_text_leaf_consistent _null_ _null_ _null_ ));
+DESCR("SP-GiST support for suffix tree over text");
+
+DATA(insert OID = 6029 (  spg_kd_config	PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 2281 "2281" _null_ _null_ _null_ _null_  spg_kd_config _null_ _null_ _null_ ));
+DESCR("SP-GiST support for k-d-tree over point");
+DATA(insert OID = 6030 (  spg_kd_choose	PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_kd_choose _null_ _null_ _null_ ));
+DESCR("SP-GiST support for k-d-tree over point");
+DATA(insert OID = 6031 (  spg_kd_picksplit	PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_kd_picksplit _null_ _null_ _null_ ));
+DESCR("SP-GiST support for k-d-tree over point");
+DATA(insert OID = 6032 (  spg_kd_inner_consistent	PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_kd_inner_consistent _null_ _null_ _null_ ));
+DESCR("SP-GiST support for k-d-tree over point");
+
+DATA(insert OID = 6025 (  spgstat	   PGNSP PGUID 12 1 0 0 0 f f f t f v 1 0 25 "25" _null_ _null_ _null_ _null_  spgstat _null_ _null_ _null_ ));
+DESCR("stats of SP-GiST index");
 
 /*
  * Symbolic values for provolatile column: these indicate whether the result
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 47a1412..ccf8a3d 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -1072,6 +1072,26 @@ extern Datum ginarrayextract_2args(PG_FUNCTION_ARGS);
 extern Datum ginqueryarrayextract(PG_FUNCTION_ARGS);
 extern Datum ginarrayconsistent(PG_FUNCTION_ARGS);
 
+/* access/spgist/spgquadtreeproc.c */
+extern Datum spg_quad_config(PG_FUNCTION_ARGS);
+extern Datum spg_quad_choose(PG_FUNCTION_ARGS);
+extern Datum spg_quad_picksplit(PG_FUNCTION_ARGS);
+extern Datum spg_quad_leaf_consistent(PG_FUNCTION_ARGS);
+extern Datum spg_quad_inner_consistent(PG_FUNCTION_ARGS);
+
+/* access/spgist/spgkdtreeproc.c */
+extern Datum spg_kd_config(PG_FUNCTION_ARGS);
+extern Datum spg_kd_choose(PG_FUNCTION_ARGS);
+extern Datum spg_kd_picksplit(PG_FUNCTION_ARGS);
+extern Datum spg_kd_inner_consistent(PG_FUNCTION_ARGS);
+
+/* access/spgist/spgtextproc.c */
+extern Datum spg_text_config(PG_FUNCTION_ARGS);
+extern Datum spg_text_choose(PG_FUNCTION_ARGS);
+extern Datum spg_text_picksplit(PG_FUNCTION_ARGS);
+extern Datum spg_text_leaf_consistent(PG_FUNCTION_ARGS);
+extern Datum spg_text_inner_consistent(PG_FUNCTION_ARGS);
+
 /* access/transam/twophase.c */
 extern Datum pg_prepared_xact(PG_FUNCTION_ARGS);
 
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
index 32d14b6..f791cba 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -195,5 +195,6 @@ extern Datum btcostestimate(PG_FUNCTION_ARGS);
 extern Datum hashcostestimate(PG_FUNCTION_ARGS);
 extern Datum gistcostestimate(PG_FUNCTION_ARGS);
 extern Datum gincostestimate(PG_FUNCTION_ARGS);
+extern Datum spgcostestimate(PG_FUNCTION_ARGS);
 
 #endif   /* SELFUNCS_H */
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index bdd1f4e..9faf595 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -61,6 +61,21 @@ CREATE TEMP TABLE gcircle_tbl AS
     SELECT circle(home_base) AS f1 FROM slow_emp4000;
 CREATE INDEX ggpolygonind ON gpolygon_tbl USING gist (f1);
 CREATE INDEX ggcircleind ON gcircle_tbl USING gist (f1);
+--
+-- SP-GiST
+--
+SELECT f1 AS p INTO quad_point_tbl FROM point_tbl;
+INSERT INTO quad_point_tbl SELECT '(1.0,5.0)'::point 
+    FROM generate_series(1,1000);
+CREATE INDEX quadind ON quad_point_tbl USING spgist (p);
+SELECT p AS p INTO kd_point_tbl FROM quad_point_tbl;
+CREATE INDEX kdind ON kd_point_tbl USING spgist (p kd_point_ops);
+SELECT name AS t INTO suffix_text_tbl FROM road;
+INSERT INTO suffix_text_tbl SELECT '0123456789abcdef' 
+	FROM generate_series(1,1000);
+INSERT INTO suffix_text_tbl VALUES ('0123456789abcde');
+INSERT INTO suffix_text_tbl VALUES ('0123456789abcdefF');
+CREATE INDEX sfxind ON suffix_text_tbl USING spgist (t);
 -- get non-indexed results for comparison purposes
 SET enable_seqscan = ON;
 SET enable_indexscan = OFF;
@@ -207,22 +222,140 @@ SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0
  (10,10)
 (4 rows)
 
+SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(0,0,100,100)';
+ count 
+-------
+  1003
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE box '(0,0,100,100)' @> p;
+ count 
+-------
+  1003
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p << '(0.0, 0.0)';
+ count 
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p >> '(0.0, 0.0)';
+ count 
+-------
+  1002
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <^ '(0.0, 0.0)';
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p >^ '(0.0, 0.0)';
+ count 
+-------
+  1003
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p ~= '(-5, -12)';
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdef';
+ count 
+-------
+  1000
+(1 row)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcde';
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdefF';
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t <    'Aztec                         Ct  ';
+ count 
+-------
+  1705
+(1 row)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t ~<~  'Aztec                         Ct  ';
+ count 
+-------
+  1705
+(1 row)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t <=   'Aztec                         Ct  ';
+ count 
+-------
+  1706
+(1 row)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t ~<=~ 'Aztec                         Ct  ';
+ count 
+-------
+  1706
+(1 row)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t =    'Aztec                         Ct  ';
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t =    'Worth                         St  ';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t >=   'Worth                         St  ';
+ count 
+-------
+    50
+(1 row)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t ~>=~ 'Worth                         St  ';
+ count 
+-------
+    50
+(1 row)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t >    'Worth                         St  ';
+ count 
+-------
+    48
+(1 row)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t ~>~  'Worth                         St  ';
+ count 
+-------
+    48
+(1 row)
+
 SET enable_seqscan = OFF;
 SET enable_indexscan = ON;
-SET enable_bitmapscan = ON;
+SET enable_bitmapscan = OFF;
 EXPLAIN (COSTS OFF)
 SELECT * FROM fast_emp4000
     WHERE home_base @ '(200,200),(2000,1000)'::box
     ORDER BY (home_base[0])[0];
-                              QUERY PLAN                              
-----------------------------------------------------------------------
+                           QUERY PLAN                           
+----------------------------------------------------------------
  Sort
    Sort Key: ((home_base[0])[0])
-   ->  Bitmap Heap Scan on fast_emp4000
-         Recheck Cond: (home_base @ '(2000,1000),(200,200)'::box)
-         ->  Bitmap Index Scan on grect2ind
-               Index Cond: (home_base @ '(2000,1000),(200,200)'::box)
-(6 rows)
+   ->  Index Scan using grect2ind on fast_emp4000
+         Index Cond: (home_base @ '(2000,1000),(200,200)'::box)
+(4 rows)
 
 SELECT * FROM fast_emp4000
     WHERE home_base @ '(200,200),(2000,1000)'::box
@@ -235,14 +368,12 @@ SELECT * FROM fast_emp4000
 
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM fast_emp4000 WHERE home_base && '(1000,1000,0,0)'::box;
-                            QUERY PLAN                             
--------------------------------------------------------------------
+                         QUERY PLAN                          
+-------------------------------------------------------------
  Aggregate
-   ->  Bitmap Heap Scan on fast_emp4000
-         Recheck Cond: (home_base && '(1000,1000),(0,0)'::box)
-         ->  Bitmap Index Scan on grect2ind
-               Index Cond: (home_base && '(1000,1000),(0,0)'::box)
-(5 rows)
+   ->  Index Scan using grect2ind on fast_emp4000
+         Index Cond: (home_base && '(1000,1000),(0,0)'::box)
+(3 rows)
 
 SELECT count(*) FROM fast_emp4000 WHERE home_base && '(1000,1000,0,0)'::box;
  count 
@@ -252,14 +383,12 @@ SELECT count(*) FROM fast_emp4000 WHERE home_base && '(1000,1000,0,0)'::box;
 
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL;
-                  QUERY PLAN                   
------------------------------------------------
+                    QUERY PLAN                    
+--------------------------------------------------
  Aggregate
-   ->  Bitmap Heap Scan on fast_emp4000
-         Recheck Cond: (home_base IS NULL)
-         ->  Bitmap Index Scan on grect2ind
-               Index Cond: (home_base IS NULL)
-(5 rows)
+   ->  Index Scan using grect2ind on fast_emp4000
+         Index Cond: (home_base IS NULL)
+(3 rows)
 
 SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL;
  count 
@@ -308,14 +437,12 @@ SELECT * FROM circle_tbl WHERE f1 && circle(point(1,-2), 1)
 
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM gpolygon_tbl WHERE f1 && '(1000,1000,0,0)'::polygon;
-                            QUERY PLAN                            
-------------------------------------------------------------------
+                         QUERY PLAN                         
+------------------------------------------------------------
  Aggregate
-   ->  Bitmap Heap Scan on gpolygon_tbl
-         Recheck Cond: (f1 && '((1000,1000),(0,0))'::polygon)
-         ->  Bitmap Index Scan on ggpolygonind
-               Index Cond: (f1 && '((1000,1000),(0,0))'::polygon)
-(5 rows)
+   ->  Index Scan using ggpolygonind on gpolygon_tbl
+         Index Cond: (f1 && '((1000,1000),(0,0))'::polygon)
+(3 rows)
 
 SELECT count(*) FROM gpolygon_tbl WHERE f1 && '(1000,1000,0,0)'::polygon;
  count 
@@ -325,14 +452,12 @@ SELECT count(*) FROM gpolygon_tbl WHERE f1 && '(1000,1000,0,0)'::polygon;
 
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM gcircle_tbl WHERE f1 && '<(500,500),500>'::circle;
-                         QUERY PLAN                          
--------------------------------------------------------------
+                      QUERY PLAN                       
+-------------------------------------------------------
  Aggregate
-   ->  Bitmap Heap Scan on gcircle_tbl
-         Recheck Cond: (f1 && '<(500,500),500>'::circle)
-         ->  Bitmap Index Scan on ggcircleind
-               Index Cond: (f1 && '<(500,500),500>'::circle)
-(5 rows)
+   ->  Index Scan using ggcircleind on gcircle_tbl
+         Index Cond: (f1 && '<(500,500),500>'::circle)
+(3 rows)
 
 SELECT count(*) FROM gcircle_tbl WHERE f1 && '<(500,500),500>'::circle;
  count 
@@ -547,29 +672,723 @@ SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0
  (10,10)
 (4 rows)
 
-SET enable_seqscan = OFF;
-SET enable_indexscan = OFF;
-SET enable_bitmapscan = ON;
 EXPLAIN (COSTS OFF)
-SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
-                         QUERY PLAN                         
-------------------------------------------------------------
- Sort
-   Sort Key: ((f1 <-> '(0,1)'::point))
-   ->  Bitmap Heap Scan on point_tbl
-         Recheck Cond: (f1 <@ '(10,10),(-10,-10)'::box)
-         ->  Bitmap Index Scan on gpointind
-               Index Cond: (f1 <@ '(10,10),(-10,-10)'::box)
-(6 rows)
+SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(0,0,100,100)';
+                    QUERY PLAN                     
+---------------------------------------------------
+ Aggregate
+   ->  Index Scan using quadind on quad_point_tbl
+         Index Cond: (p <@ '(100,100),(0,0)'::box)
+(3 rows)
 
-SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
-   f1    
----------
- (0,0)
- (-3,4)
- (-10,0)
- (10,10)
-(4 rows)
+SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(0,0,100,100)';
+ count 
+-------
+  1003
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE box '(0,0,100,100)' @> p;
+                    QUERY PLAN                     
+---------------------------------------------------
+ Aggregate
+   ->  Index Scan using quadind on quad_point_tbl
+         Index Cond: ('(100,100),(0,0)'::box @> p)
+(3 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE box '(0,0,100,100)' @> p;
+ count 
+-------
+  1003
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p << '(0.0, 0.0)';
+                    QUERY PLAN                    
+--------------------------------------------------
+ Aggregate
+   ->  Index Scan using quadind on quad_point_tbl
+         Index Cond: (p << '(0,0)'::point)
+(3 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p << '(0.0, 0.0)';
+ count 
+-------
+     3
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p >> '(0.0, 0.0)';
+                    QUERY PLAN                    
+--------------------------------------------------
+ Aggregate
+   ->  Index Scan using quadind on quad_point_tbl
+         Index Cond: (p >> '(0,0)'::point)
+(3 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p >> '(0.0, 0.0)';
+ count 
+-------
+  1002
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p <^ '(0.0, 0.0)';
+                    QUERY PLAN                    
+--------------------------------------------------
+ Aggregate
+   ->  Index Scan using quadind on quad_point_tbl
+         Index Cond: (p <^ '(0,0)'::point)
+(3 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <^ '(0.0, 0.0)';
+ count 
+-------
+     1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p >^ '(0.0, 0.0)';
+                    QUERY PLAN                    
+--------------------------------------------------
+ Aggregate
+   ->  Index Scan using quadind on quad_point_tbl
+         Index Cond: (p >^ '(0,0)'::point)
+(3 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p >^ '(0.0, 0.0)';
+ count 
+-------
+  1003
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p ~= '(-5, -12)';
+                    QUERY PLAN                    
+--------------------------------------------------
+ Aggregate
+   ->  Index Scan using quadind on quad_point_tbl
+         Index Cond: (p ~= '(-5,-12)'::point)
+(3 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p ~= '(-5, -12)';
+ count 
+-------
+     1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(0,0,100,100)';
+                    QUERY PLAN                     
+---------------------------------------------------
+ Aggregate
+   ->  Index Scan using kdind on kd_point_tbl
+         Index Cond: (p <@ '(100,100),(0,0)'::box)
+(3 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(0,0,100,100)';
+ count 
+-------
+  1003
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE box '(0,0,100,100)' @> p;
+                    QUERY PLAN                     
+---------------------------------------------------
+ Aggregate
+   ->  Index Scan using kdind on kd_point_tbl
+         Index Cond: ('(100,100),(0,0)'::box @> p)
+(3 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE box '(0,0,100,100)' @> p;
+ count 
+-------
+  1003
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p << '(0.0, 0.0)';
+                  QUERY PLAN                  
+----------------------------------------------
+ Aggregate
+   ->  Index Scan using kdind on kd_point_tbl
+         Index Cond: (p << '(0,0)'::point)
+(3 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE p << '(0.0, 0.0)';
+ count 
+-------
+     3
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p >> '(0.0, 0.0)';
+                  QUERY PLAN                  
+----------------------------------------------
+ Aggregate
+   ->  Index Scan using kdind on kd_point_tbl
+         Index Cond: (p >> '(0,0)'::point)
+(3 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE p >> '(0.0, 0.0)';
+ count 
+-------
+  1002
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p <^ '(0.0, 0.0)';
+                  QUERY PLAN                  
+----------------------------------------------
+ Aggregate
+   ->  Index Scan using kdind on kd_point_tbl
+         Index Cond: (p <^ '(0,0)'::point)
+(3 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE p <^ '(0.0, 0.0)';
+ count 
+-------
+     1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p >^ '(0.0, 0.0)';
+                  QUERY PLAN                  
+----------------------------------------------
+ Aggregate
+   ->  Index Scan using kdind on kd_point_tbl
+         Index Cond: (p >^ '(0,0)'::point)
+(3 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE p >^ '(0.0, 0.0)';
+ count 
+-------
+  1003
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p ~= '(-5, -12)';
+                  QUERY PLAN                  
+----------------------------------------------
+ Aggregate
+   ->  Index Scan using kdind on kd_point_tbl
+         Index Cond: (p ~= '(-5,-12)'::point)
+(3 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE p ~= '(-5, -12)';
+ count 
+-------
+     1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdef';
+                     QUERY PLAN                     
+----------------------------------------------------
+ Aggregate
+   ->  Index Scan using sfxind on suffix_text_tbl
+         Index Cond: (t = '0123456789abcdef'::text)
+(3 rows)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdef';
+ count 
+-------
+  1000
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcde';
+                    QUERY PLAN                     
+---------------------------------------------------
+ Aggregate
+   ->  Index Scan using sfxind on suffix_text_tbl
+         Index Cond: (t = '0123456789abcde'::text)
+(3 rows)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcde';
+ count 
+-------
+     1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdefF';
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Aggregate
+   ->  Index Scan using sfxind on suffix_text_tbl
+         Index Cond: (t = '0123456789abcdefF'::text)
+(3 rows)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdefF';
+ count 
+-------
+     1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t <    'Aztec                         Ct  ';
+                              QUERY PLAN                              
+----------------------------------------------------------------------
+ Aggregate
+   ->  Index Scan using sfxind on suffix_text_tbl
+         Index Cond: (t < 'Aztec                         Ct  '::text)
+(3 rows)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t <    'Aztec                         Ct  ';
+ count 
+-------
+  1705
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t ~<~  'Aztec                         Ct  ';
+                               QUERY PLAN                               
+------------------------------------------------------------------------
+ Aggregate
+   ->  Index Scan using sfxind on suffix_text_tbl
+         Index Cond: (t ~<~ 'Aztec                         Ct  '::text)
+(3 rows)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t ~<~  'Aztec                         Ct  ';
+ count 
+-------
+  1705
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t <=   'Aztec                         Ct  ';
+                              QUERY PLAN                               
+-----------------------------------------------------------------------
+ Aggregate
+   ->  Index Scan using sfxind on suffix_text_tbl
+         Index Cond: (t <= 'Aztec                         Ct  '::text)
+(3 rows)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t <=   'Aztec                         Ct  ';
+ count 
+-------
+  1706
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t ~<=~ 'Aztec                         Ct  ';
+                               QUERY PLAN                                
+-------------------------------------------------------------------------
+ Aggregate
+   ->  Index Scan using sfxind on suffix_text_tbl
+         Index Cond: (t ~<=~ 'Aztec                         Ct  '::text)
+(3 rows)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t ~<=~ 'Aztec                         Ct  ';
+ count 
+-------
+  1706
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t =    'Aztec                         Ct  ';
+                              QUERY PLAN                              
+----------------------------------------------------------------------
+ Aggregate
+   ->  Index Scan using sfxind on suffix_text_tbl
+         Index Cond: (t = 'Aztec                         Ct  '::text)
+(3 rows)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t =    'Aztec                         Ct  ';
+ count 
+-------
+     1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t =    'Worth                         St  ';
+                              QUERY PLAN                              
+----------------------------------------------------------------------
+ Aggregate
+   ->  Index Scan using sfxind on suffix_text_tbl
+         Index Cond: (t = 'Worth                         St  '::text)
+(3 rows)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t =    'Worth                         St  ';
+ count 
+-------
+     2
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t >=   'Worth                         St  ';
+                              QUERY PLAN                               
+-----------------------------------------------------------------------
+ Aggregate
+   ->  Index Scan using sfxind on suffix_text_tbl
+         Index Cond: (t >= 'Worth                         St  '::text)
+(3 rows)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t >=   'Worth                         St  ';
+ count 
+-------
+    50
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t ~>=~ 'Worth                         St  ';
+                               QUERY PLAN                                
+-------------------------------------------------------------------------
+ Aggregate
+   ->  Index Scan using sfxind on suffix_text_tbl
+         Index Cond: (t ~>=~ 'Worth                         St  '::text)
+(3 rows)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t ~>=~ 'Worth                         St  ';
+ count 
+-------
+    50
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t >    'Worth                         St  ';
+                              QUERY PLAN                              
+----------------------------------------------------------------------
+ Aggregate
+   ->  Index Scan using sfxind on suffix_text_tbl
+         Index Cond: (t > 'Worth                         St  '::text)
+(3 rows)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t >    'Worth                         St  ';
+ count 
+-------
+    48
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t ~>~  'Worth                         St  ';
+                               QUERY PLAN                               
+------------------------------------------------------------------------
+ Aggregate
+   ->  Index Scan using sfxind on suffix_text_tbl
+         Index Cond: (t ~>~ 'Worth                         St  '::text)
+(3 rows)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t ~>~  'Worth                         St  ';
+ count 
+-------
+    48
+(1 row)
+
+SET enable_seqscan = OFF;
+SET enable_indexscan = OFF;
+SET enable_bitmapscan = ON;
+EXPLAIN (COSTS OFF)
+SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Sort
+   Sort Key: ((f1 <-> '(0,1)'::point))
+   ->  Bitmap Heap Scan on point_tbl
+         Recheck Cond: (f1 <@ '(10,10),(-10,-10)'::box)
+         ->  Bitmap Index Scan on gpointind
+               Index Cond: (f1 <@ '(10,10),(-10,-10)'::box)
+(6 rows)
+
+SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
+   f1    
+---------
+ (0,0)
+ (-3,4)
+ (-10,0)
+ (10,10)
+(4 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(0,0,100,100)';
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on quad_point_tbl
+         Recheck Cond: (p <@ '(100,100),(0,0)'::box)
+         ->  Bitmap Index Scan on quadind
+               Index Cond: (p <@ '(100,100),(0,0)'::box)
+(5 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(0,0,100,100)';
+ count 
+-------
+  1003
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE box '(0,0,100,100)' @> p;
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on quad_point_tbl
+         Recheck Cond: ('(100,100),(0,0)'::box @> p)
+         ->  Bitmap Index Scan on quadind
+               Index Cond: ('(100,100),(0,0)'::box @> p)
+(5 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE box '(0,0,100,100)' @> p;
+ count 
+-------
+  1003
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p << '(0.0, 0.0)';
+                   QUERY PLAN                    
+-------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on quad_point_tbl
+         Recheck Cond: (p << '(0,0)'::point)
+         ->  Bitmap Index Scan on quadind
+               Index Cond: (p << '(0,0)'::point)
+(5 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p << '(0.0, 0.0)';
+ count 
+-------
+     3
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p >> '(0.0, 0.0)';
+                   QUERY PLAN                    
+-------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on quad_point_tbl
+         Recheck Cond: (p >> '(0,0)'::point)
+         ->  Bitmap Index Scan on quadind
+               Index Cond: (p >> '(0,0)'::point)
+(5 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p >> '(0.0, 0.0)';
+ count 
+-------
+  1002
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p <^ '(0.0, 0.0)';
+                   QUERY PLAN                    
+-------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on quad_point_tbl
+         Recheck Cond: (p <^ '(0,0)'::point)
+         ->  Bitmap Index Scan on quadind
+               Index Cond: (p <^ '(0,0)'::point)
+(5 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <^ '(0.0, 0.0)';
+ count 
+-------
+     1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p >^ '(0.0, 0.0)';
+                   QUERY PLAN                    
+-------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on quad_point_tbl
+         Recheck Cond: (p >^ '(0,0)'::point)
+         ->  Bitmap Index Scan on quadind
+               Index Cond: (p >^ '(0,0)'::point)
+(5 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p >^ '(0.0, 0.0)';
+ count 
+-------
+  1003
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p ~= '(-5, -12)';
+                     QUERY PLAN                     
+----------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on quad_point_tbl
+         Recheck Cond: (p ~= '(-5,-12)'::point)
+         ->  Bitmap Index Scan on quadind
+               Index Cond: (p ~= '(-5,-12)'::point)
+(5 rows)
+
+SELECT count(*) FROM quad_point_tbl WHERE p ~= '(-5, -12)';
+ count 
+-------
+     1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(0,0,100,100)';
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on kd_point_tbl
+         Recheck Cond: (p <@ '(100,100),(0,0)'::box)
+         ->  Bitmap Index Scan on kdind
+               Index Cond: (p <@ '(100,100),(0,0)'::box)
+(5 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(0,0,100,100)';
+ count 
+-------
+  1003
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE box '(0,0,100,100)' @> p;
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on kd_point_tbl
+         Recheck Cond: ('(100,100),(0,0)'::box @> p)
+         ->  Bitmap Index Scan on kdind
+               Index Cond: ('(100,100),(0,0)'::box @> p)
+(5 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE box '(0,0,100,100)' @> p;
+ count 
+-------
+  1003
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p << '(0.0, 0.0)';
+                   QUERY PLAN                    
+-------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on kd_point_tbl
+         Recheck Cond: (p << '(0,0)'::point)
+         ->  Bitmap Index Scan on kdind
+               Index Cond: (p << '(0,0)'::point)
+(5 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE p << '(0.0, 0.0)';
+ count 
+-------
+     3
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p >> '(0.0, 0.0)';
+                   QUERY PLAN                    
+-------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on kd_point_tbl
+         Recheck Cond: (p >> '(0,0)'::point)
+         ->  Bitmap Index Scan on kdind
+               Index Cond: (p >> '(0,0)'::point)
+(5 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE p >> '(0.0, 0.0)';
+ count 
+-------
+  1002
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p <^ '(0.0, 0.0)';
+                   QUERY PLAN                    
+-------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on kd_point_tbl
+         Recheck Cond: (p <^ '(0,0)'::point)
+         ->  Bitmap Index Scan on kdind
+               Index Cond: (p <^ '(0,0)'::point)
+(5 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE p <^ '(0.0, 0.0)';
+ count 
+-------
+     1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p >^ '(0.0, 0.0)';
+                   QUERY PLAN                    
+-------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on kd_point_tbl
+         Recheck Cond: (p >^ '(0,0)'::point)
+         ->  Bitmap Index Scan on kdind
+               Index Cond: (p >^ '(0,0)'::point)
+(5 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE p >^ '(0.0, 0.0)';
+ count 
+-------
+  1003
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p ~= '(-5, -12)';
+                     QUERY PLAN                     
+----------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on kd_point_tbl
+         Recheck Cond: (p ~= '(-5,-12)'::point)
+         ->  Bitmap Index Scan on kdind
+               Index Cond: (p ~= '(-5,-12)'::point)
+(5 rows)
+
+SELECT count(*) FROM kd_point_tbl WHERE p ~= '(-5, -12)';
+ count 
+-------
+     1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdef';
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on suffix_text_tbl
+         Recheck Cond: (t = '0123456789abcdef'::text)
+         ->  Bitmap Index Scan on sfxind
+               Index Cond: (t = '0123456789abcdef'::text)
+(5 rows)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdef';
+ count 
+-------
+  1000
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcde';
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on suffix_text_tbl
+         Recheck Cond: (t = '0123456789abcde'::text)
+         ->  Bitmap Index Scan on sfxind
+               Index Cond: (t = '0123456789abcde'::text)
+(5 rows)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcde';
+ count 
+-------
+     1
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdefF';
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on suffix_text_tbl
+         Recheck Cond: (t = '0123456789abcdefF'::text)
+         ->  Bitmap Index Scan on sfxind
+               Index Cond: (t = '0123456789abcdefF'::text)
+(5 rows)
+
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdefF';
+ count 
+-------
+     1
+(1 row)
 
 RESET enable_seqscan;
 RESET enable_indexscan;
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index b915134..36cdbd5 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1053,7 +1053,23 @@ ORDER BY 1, 2, 3;
        2742 |            2 | @@@
        2742 |            3 | <@
        2742 |            4 | =
-(43 rows)
+       6024 |            1 | <<
+       6024 |            1 | ~<~
+       6024 |            2 | ~<=~
+       6024 |            3 | =
+       6024 |            4 | ~>=~
+       6024 |            5 | >>
+       6024 |            5 | ~>~
+       6024 |            6 | ~=
+       6024 |            7 | @>
+       6024 |            8 | <@
+       6024 |           10 | <^
+       6024 |           11 | <
+       6024 |           11 | >^
+       6024 |           12 | <=
+       6024 |           14 | >=
+       6024 |           15 | >
+(59 rows)
 
 -- Check that all opclass search operators have selectivity estimators.
 -- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index cb468e5..9cae9d8 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -63,6 +63,7 @@ SELECT relname, relhasindex
  int8_tbl                | f
  interval_tbl            | f
  iportaltest             | f
+ kd_point_tbl            | t
  log_table               | f
  lseg_tbl                | f
  main_table              | f
@@ -134,6 +135,7 @@ SELECT relname, relhasindex
  pg_user_mapping         | t
  point_tbl               | t
  polygon_tbl             | t
+ quad_point_tbl          | t
  ramp                    | f
  real_city               | f
  reltime_tbl             | f
@@ -149,6 +151,7 @@ SELECT relname, relhasindex
  sql_sizing_profiles     | f
  stud_emp                | f
  student                 | f
+ suffix_text_tbl         | t
  tenk1                   | t
  tenk2                   | t
  test_range_excl         | t
@@ -161,7 +164,7 @@ SELECT relname, relhasindex
  timetz_tbl              | f
  tinterval_tbl           | f
  varchar_tbl             | f
-(150 rows)
+(153 rows)
 
 --
 -- another sanity check: every system catalog that has OIDs should have
diff --git a/src/test/regress/output/misc.source b/src/test/regress/output/misc.source
index 45bc926..b57c554 100644
--- a/src/test/regress/output/misc.source
+++ b/src/test/regress/output/misc.source
@@ -636,6 +636,7 @@ SELECT user_relns() AS user_relns
  int8_tbl
  interval_tbl
  iportaltest
+ kd_point_tbl
  log_table
  lseg_tbl
  main_table
@@ -657,6 +658,7 @@ SELECT user_relns() AS user_relns
  person
  point_tbl
  polygon_tbl
+ quad_point_tbl
  ramp
  random_tbl
  real_city
@@ -668,6 +670,7 @@ SELECT user_relns() AS user_relns
  stud_emp
  student
  subselect_tbl
+ suffix_text_tbl
  tenk1
  tenk2
  test_range_excl
@@ -682,7 +685,7 @@ SELECT user_relns() AS user_relns
  toyemp
  varchar_tbl
  xacttest
-(104 rows)
+(107 rows)
 
 SELECT name(equipment(hobby_construct(text 'skywalking', text 'mer')));
  name 
diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql
index 85cf23c..1cc0223 100644
--- a/src/test/regress/sql/create_index.sql
+++ b/src/test/regress/sql/create_index.sql
@@ -92,6 +92,30 @@ CREATE INDEX ggpolygonind ON gpolygon_tbl USING gist (f1);
 
 CREATE INDEX ggcircleind ON gcircle_tbl USING gist (f1);
 
+--
+-- SP-GiST
+--
+
+SELECT f1 AS p INTO quad_point_tbl FROM point_tbl;
+
+INSERT INTO quad_point_tbl SELECT '(1.0,5.0)'::point 
+    FROM generate_series(1,1000);
+
+CREATE INDEX quadind ON quad_point_tbl USING spgist (p);
+
+SELECT p AS p INTO kd_point_tbl FROM quad_point_tbl;
+
+CREATE INDEX kdind ON kd_point_tbl USING spgist (p kd_point_ops);
+
+SELECT name AS t INTO suffix_text_tbl FROM road;
+
+INSERT INTO suffix_text_tbl SELECT '0123456789abcdef' 
+	FROM generate_series(1,1000);
+INSERT INTO suffix_text_tbl VALUES ('0123456789abcde');
+INSERT INTO suffix_text_tbl VALUES ('0123456789abcdefF');
+
+CREATE INDEX sfxind ON suffix_text_tbl USING spgist (t);
+
 -- get non-indexed results for comparison purposes
 
 SET enable_seqscan = ON;
@@ -142,9 +166,49 @@ SELECT * FROM point_tbl WHERE f1 IS NOT NULL ORDER BY f1 <-> '0,1';
 
 SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
 
+SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(0,0,100,100)';
+
+SELECT count(*) FROM quad_point_tbl WHERE box '(0,0,100,100)' @> p;
+
+SELECT count(*) FROM quad_point_tbl WHERE p << '(0.0, 0.0)';
+
+SELECT count(*) FROM quad_point_tbl WHERE p >> '(0.0, 0.0)';
+
+SELECT count(*) FROM quad_point_tbl WHERE p <^ '(0.0, 0.0)';
+
+SELECT count(*) FROM quad_point_tbl WHERE p >^ '(0.0, 0.0)';
+
+SELECT count(*) FROM quad_point_tbl WHERE p ~= '(-5, -12)';
+
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdef';
+
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcde';
+
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdefF';
+
+SELECT count(*) FROM suffix_text_tbl WHERE t <    'Aztec                         Ct  ';
+
+SELECT count(*) FROM suffix_text_tbl WHERE t ~<~  'Aztec                         Ct  ';
+
+SELECT count(*) FROM suffix_text_tbl WHERE t <=   'Aztec                         Ct  ';
+
+SELECT count(*) FROM suffix_text_tbl WHERE t ~<=~ 'Aztec                         Ct  ';
+
+SELECT count(*) FROM suffix_text_tbl WHERE t =    'Aztec                         Ct  ';
+
+SELECT count(*) FROM suffix_text_tbl WHERE t =    'Worth                         St  ';
+
+SELECT count(*) FROM suffix_text_tbl WHERE t >=   'Worth                         St  ';
+
+SELECT count(*) FROM suffix_text_tbl WHERE t ~>=~ 'Worth                         St  ';
+
+SELECT count(*) FROM suffix_text_tbl WHERE t >    'Worth                         St  ';
+
+SELECT count(*) FROM suffix_text_tbl WHERE t ~>~  'Worth                         St  ';
+
 SET enable_seqscan = OFF;
 SET enable_indexscan = ON;
-SET enable_bitmapscan = ON;
+SET enable_bitmapscan = OFF;
 
 EXPLAIN (COSTS OFF)
 SELECT * FROM fast_emp4000
@@ -234,6 +298,114 @@ EXPLAIN (COSTS OFF)
 SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
 SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
 
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(0,0,100,100)';
+SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(0,0,100,100)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE box '(0,0,100,100)' @> p;
+SELECT count(*) FROM quad_point_tbl WHERE box '(0,0,100,100)' @> p;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p << '(0.0, 0.0)';
+SELECT count(*) FROM quad_point_tbl WHERE p << '(0.0, 0.0)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p >> '(0.0, 0.0)';
+SELECT count(*) FROM quad_point_tbl WHERE p >> '(0.0, 0.0)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p <^ '(0.0, 0.0)';
+SELECT count(*) FROM quad_point_tbl WHERE p <^ '(0.0, 0.0)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p >^ '(0.0, 0.0)';
+SELECT count(*) FROM quad_point_tbl WHERE p >^ '(0.0, 0.0)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p ~= '(-5, -12)';
+SELECT count(*) FROM quad_point_tbl WHERE p ~= '(-5, -12)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(0,0,100,100)';
+SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(0,0,100,100)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE box '(0,0,100,100)' @> p;
+SELECT count(*) FROM kd_point_tbl WHERE box '(0,0,100,100)' @> p;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p << '(0.0, 0.0)';
+SELECT count(*) FROM kd_point_tbl WHERE p << '(0.0, 0.0)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p >> '(0.0, 0.0)';
+SELECT count(*) FROM kd_point_tbl WHERE p >> '(0.0, 0.0)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p <^ '(0.0, 0.0)';
+SELECT count(*) FROM kd_point_tbl WHERE p <^ '(0.0, 0.0)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p >^ '(0.0, 0.0)';
+SELECT count(*) FROM kd_point_tbl WHERE p >^ '(0.0, 0.0)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p ~= '(-5, -12)';
+SELECT count(*) FROM kd_point_tbl WHERE p ~= '(-5, -12)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdef';
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdef';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcde';
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcde';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdefF';
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdefF';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t <    'Aztec                         Ct  ';
+SELECT count(*) FROM suffix_text_tbl WHERE t <    'Aztec                         Ct  ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t ~<~  'Aztec                         Ct  ';
+SELECT count(*) FROM suffix_text_tbl WHERE t ~<~  'Aztec                         Ct  ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t <=   'Aztec                         Ct  ';
+SELECT count(*) FROM suffix_text_tbl WHERE t <=   'Aztec                         Ct  ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t ~<=~ 'Aztec                         Ct  ';
+SELECT count(*) FROM suffix_text_tbl WHERE t ~<=~ 'Aztec                         Ct  ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t =    'Aztec                         Ct  ';
+SELECT count(*) FROM suffix_text_tbl WHERE t =    'Aztec                         Ct  ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t =    'Worth                         St  ';
+SELECT count(*) FROM suffix_text_tbl WHERE t =    'Worth                         St  ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t >=   'Worth                         St  ';
+SELECT count(*) FROM suffix_text_tbl WHERE t >=   'Worth                         St  ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t ~>=~ 'Worth                         St  ';
+SELECT count(*) FROM suffix_text_tbl WHERE t ~>=~ 'Worth                         St  ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t >    'Worth                         St  ';
+SELECT count(*) FROM suffix_text_tbl WHERE t >    'Worth                         St  ';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t ~>~  'Worth                         St  ';
+SELECT count(*) FROM suffix_text_tbl WHERE t ~>~  'Worth                         St  ';
+
 SET enable_seqscan = OFF;
 SET enable_indexscan = OFF;
 SET enable_bitmapscan = ON;
@@ -242,6 +414,74 @@ EXPLAIN (COSTS OFF)
 SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
 SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
 
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(0,0,100,100)';
+SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(0,0,100,100)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE box '(0,0,100,100)' @> p;
+SELECT count(*) FROM quad_point_tbl WHERE box '(0,0,100,100)' @> p;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p << '(0.0, 0.0)';
+SELECT count(*) FROM quad_point_tbl WHERE p << '(0.0, 0.0)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p >> '(0.0, 0.0)';
+SELECT count(*) FROM quad_point_tbl WHERE p >> '(0.0, 0.0)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p <^ '(0.0, 0.0)';
+SELECT count(*) FROM quad_point_tbl WHERE p <^ '(0.0, 0.0)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p >^ '(0.0, 0.0)';
+SELECT count(*) FROM quad_point_tbl WHERE p >^ '(0.0, 0.0)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM quad_point_tbl WHERE p ~= '(-5, -12)';
+SELECT count(*) FROM quad_point_tbl WHERE p ~= '(-5, -12)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(0,0,100,100)';
+SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(0,0,100,100)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE box '(0,0,100,100)' @> p;
+SELECT count(*) FROM kd_point_tbl WHERE box '(0,0,100,100)' @> p;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p << '(0.0, 0.0)';
+SELECT count(*) FROM kd_point_tbl WHERE p << '(0.0, 0.0)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p >> '(0.0, 0.0)';
+SELECT count(*) FROM kd_point_tbl WHERE p >> '(0.0, 0.0)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p <^ '(0.0, 0.0)';
+SELECT count(*) FROM kd_point_tbl WHERE p <^ '(0.0, 0.0)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p >^ '(0.0, 0.0)';
+SELECT count(*) FROM kd_point_tbl WHERE p >^ '(0.0, 0.0)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM kd_point_tbl WHERE p ~= '(-5, -12)';
+SELECT count(*) FROM kd_point_tbl WHERE p ~= '(-5, -12)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdef';
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdef';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcde';
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcde';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdefF';
+SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdefF';
+
 RESET enable_seqscan;
 RESET enable_indexscan;
 RESET enable_bitmapscan;
