OSDN Git Service

pgindent run.
[pg-rex/syncrep.git] / src / backend / access / gist / gist.c
index 640c189..c238ea2 100644 (file)
@@ -4,13 +4,14 @@
  *       interface routines for the postgres GiST index access method.
  *
  *
+ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/access/gist/gist.c,v 1.62 2000/07/14 22:17:28 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/access/gist/gist.c,v 1.96 2002/09/04 20:31:09 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
-
 #include "postgres.h"
 
 #include "access/genam.h"
 #include "access/gistscan.h"
 #include "access/heapam.h"
 #include "catalog/index.h"
-#include "catalog/pg_index.h"
-#include "executor/executor.h"
 #include "miscadmin.h"
-#include "utils/syscache.h"
+
+
+#undef GIST_PAGEADDITEM
+
+#define ATTSIZE( datum, TupDesc, i, isnull ) \
+       ( \
+               ( isnull ) ? 0 : \
+                       att_addlength(0, (TupDesc)->attrs[(i)-1]->attlen, (datum)) \
+       )
+
+/* result's status */
+#define INSERTED       0x01
+#define SPLITED                0x02
+
+/* group flags ( in gistSplit ) */
+#define LEFT_ADDED     0x01
+#define RIGHT_ADDED 0x02
+#define BOTH_ADDED     ( LEFT_ADDED | RIGHT_ADDED )
+
+/*
+ * This defines only for shorter code, used in gistgetadjusted
+ * and gistadjsubkey only
+ */
+#define FILLITEM(evp, isnullkey, okey, okeyb, rkey, rkeyb)      do { \
+               if ( isnullkey ) {                                                                                \
+                               gistentryinit((evp), rkey, r, (Page) NULL ,               \
+                                               (OffsetNumber) 0, rkeyb, FALSE);                  \
+               } else {                                                                                                  \
+                               gistentryinit((evp), okey, r, (Page) NULL,                \
+                                               (OffsetNumber) 0, okeyb, FALSE);                  \
+               }                                                                                                                 \
+} while(0)
+
+#define FILLEV(isnull1, key1, key1b, isnull2, key2, key2b) do { \
+       FILLITEM(*ev0p, isnull1, key1, key1b, key2, key2b);             \
+       FILLITEM(*ev1p, isnull2, key2, key2b, key1, key1b);             \
+} while(0);
+
+/* Working state for gistbuild and its callback */
+typedef struct
+{
+       GISTSTATE       giststate;
+       int                     numindexattrs;
+       double          indtuples;
+} GISTBuildState;
 
 
 /* non-export function prototypes */
-static InsertIndexResult gistdoinsert(Relation r, IndexTuple itup,
+static void gistbuildCallback(Relation index,
+                                 HeapTuple htup,
+                                 Datum *attdata,
+                                 char *nulls,
+                                 bool tupleIsAlive,
+                                 void *state);
+static void gistdoinsert(Relation r,
+                        IndexTuple itup,
+                        InsertIndexResult *res,
                         GISTSTATE *GISTstate);
-static InsertIndexResult gistentryinsert(Relation r, GISTSTACK *stk,
-                               IndexTuple tup,
+static int gistlayerinsert(Relation r, BlockNumber blkno,
+                               IndexTuple **itup,
+                               int *len,
+                               InsertIndexResult *res,
                                GISTSTATE *giststate);
-static void gistentryinserttwo(Relation r, GISTSTACK *stk, IndexTuple ltup,
-                                  IndexTuple rtup, GISTSTATE *giststate);
-static void gistAdjustKeys(Relation r, GISTSTACK *stk, BlockNumber blk,
-                          char *datum, int att_size, GISTSTATE *giststate);
-static void gistintinsert(Relation r, GISTSTACK *stk, IndexTuple ltup,
-                         IndexTuple rtup, GISTSTATE *giststate);
-static InsertIndexResult gistSplit(Relation r, Buffer buffer,
-                 GISTSTACK *stack, IndexTuple itup,
-                 GISTSTATE *giststate);
-static void gistnewroot(GISTSTATE *giststate, Relation r, IndexTuple lt,
-                       IndexTuple rt);
+static OffsetNumber gistwritebuffer(Relation r,
+                               Page page,
+                               IndexTuple *itup,
+                               int len,
+                               OffsetNumber off);
+static int gistnospace(Page page,
+                       IndexTuple *itvec, int len);
+static IndexTuple *gistreadbuffer(Buffer buffer, int *len);
+static IndexTuple *gistjoinvector(
+                          IndexTuple *itvec, int *len,
+                          IndexTuple *additvec, int addlen);
+static IndexTuple gistunion(Relation r, IndexTuple *itvec,
+                 int len, GISTSTATE *giststate);
+
+static IndexTuple gistgetadjusted(Relation r,
+                               IndexTuple oldtup,
+                               IndexTuple addtup,
+                               GISTSTATE *giststate);
+static int gistfindgroup(GISTSTATE *giststate,
+                         GISTENTRY *valvec, GIST_SPLITVEC *spl);
+static void gistadjsubkey(Relation r,
+                         IndexTuple *itup, int *len,
+                         GIST_SPLITVEC *v,
+                         GISTSTATE *giststate);
+static IndexTuple gistFormTuple(GISTSTATE *giststate,
+                       Relation r, Datum attdata[], int datumsize[], bool isnull[]);
+static IndexTuple *gistSplit(Relation r,
+                 Buffer buffer,
+                 IndexTuple *itup,
+                 int *len,
+                 GISTSTATE *giststate,
+                 InsertIndexResult *res);
+static void gistnewroot(Relation r,
+                       IndexTuple *itup, int len);
 static void GISTInitBuffer(Buffer b, uint32 f);
-static BlockNumber gistChooseSubtree(Relation r, IndexTuple itup, int level,
-                                 GISTSTATE *giststate,
-                                 GISTSTACK **retstack, Buffer *leafbuf);
-static OffsetNumber gistchoose(Relation r, Page p, IndexTuple it,
+static OffsetNumber gistchoose(Relation r, Page p,
+                  IndexTuple it,
                   GISTSTATE *giststate);
-static int     gistnospace(Page p, IndexTuple it);
-static IndexTuple gist_tuple_replacekey(Relation r, GISTENTRY entry, IndexTuple t);
-static void gistcentryinit(GISTSTATE *giststate, GISTENTRY *e, char *pr,
-                          Relation r, Page pg, OffsetNumber o, int b, bool l);
+static void gistdelete(Relation r, ItemPointer tid);
 
-#ifdef GISTDEBUG
-static char *int_range_out(INTRANGE *r);
+#ifdef GIST_PAGEADDITEM
+static IndexTuple gist_tuple_replacekey(Relation r,
+                                         GISTENTRY entry, IndexTuple t);
+#endif
+static void gistcentryinit(GISTSTATE *giststate, int nkey,
+                          GISTENTRY *e, Datum k,
+                          Relation r, Page pg,
+                          OffsetNumber o, int b, bool l, bool isNull);
+static void gistDeCompressAtt(GISTSTATE *giststate, Relation r,
+                                 IndexTuple tuple, Page p, OffsetNumber o,
+                                 GISTENTRY attdata[], bool decompvec[], bool isnull[]);
+static void gistFreeAtt(Relation r, GISTENTRY attdata[], bool decompvec[]);
+static void gistpenalty(GISTSTATE *giststate, int attno,
+                       GISTENTRY *key1, bool isNull1,
+                       GISTENTRY *key2, bool isNull2,
+                       float *penalty);
+
+#undef GISTDEBUG
 
+#ifdef GISTDEBUG
+static void gist_dumptree(Relation r, int level, BlockNumber blk, OffsetNumber coff);
 #endif
 
 /*
-** routine to build an index.  Basically calls insert over and over
-*/
+ * routine to build an index.  Basically calls insert over and over
+ */
 Datum
 gistbuild(PG_FUNCTION_ARGS)
 {
-       Relation                heap = (Relation) PG_GETARG_POINTER(0);
-       Relation                index = (Relation) PG_GETARG_POINTER(1);
-       IndexInfo          *indexInfo = (IndexInfo *) PG_GETARG_POINTER(2);
-       Node               *oldPred = (Node *) PG_GETARG_POINTER(3);
-#ifdef NOT_USED
-       IndexStrategy   istrat = (IndexStrategy) PG_GETARG_POINTER(4);
-#endif
-       HeapScanDesc hscan;
-       HeapTuple       htup;
-       IndexTuple      itup;
-       TupleDesc       htupdesc,
-                               itupdesc;
-       Datum           attdata[INDEX_MAX_KEYS];
-       char            nulls[INDEX_MAX_KEYS];
-       int                     nhtups,
-                               nitups;
-       Node       *pred = indexInfo->ii_Predicate;
-#ifndef OMIT_PARTIAL_INDEX
-       TupleTable      tupleTable;
-       TupleTableSlot *slot;
-#endif
-       ExprContext *econtext;
-       InsertIndexResult res = NULL;
-       GISTSTATE       giststate;
-       GISTENTRY       tmpcentry;
-       Buffer          buffer = InvalidBuffer;
-       bool       *compvec;
-       int                     i;
+       Relation        heap = (Relation) PG_GETARG_POINTER(0);
+       Relation        index = (Relation) PG_GETARG_POINTER(1);
+       IndexInfo  *indexInfo = (IndexInfo *) PG_GETARG_POINTER(2);
+       double          reltuples;
+       GISTBuildState buildstate;
+       Buffer          buffer;
 
        /* no locking is needed */
 
-       initGISTstate(&giststate, index);
+       initGISTstate(&buildstate.giststate, index);
 
        /*
         * We expect to be called exactly once for any index relation. If
         * that's not the case, big trouble's what we have.
         */
-       if (oldPred == NULL && RelationGetNumberOfBlocks(index) != 0)
-               elog(ERROR, "%s already contains data", RelationGetRelationName(index));
-
-       /* initialize the root page (if this is a new index) */
-       if (oldPred == NULL)
-       {
-               buffer = ReadBuffer(index, P_NEW);
-               GISTInitBuffer(buffer, F_LEAF);
-               WriteBuffer(buffer);
-       }
-
-       /* get tuple descriptors for heap and index relations */
-       htupdesc = RelationGetDescr(heap);
-       itupdesc = RelationGetDescr(index);
+       if (RelationGetNumberOfBlocks(index) != 0)
+               elog(ERROR, "%s already contains data",
+                        RelationGetRelationName(index));
 
-       /*
-        * If this is a predicate (partial) index, we will need to evaluate
-        * the predicate using ExecQual, which requires the current tuple to
-        * be in a slot of a TupleTable.  In addition, ExecQual must have an
-        * ExprContext referring to that slot.  Here, we initialize dummy
-        * TupleTable and ExprContext objects for this purpose. --Nels, Feb 92
-        *
-        * We construct the ExprContext anyway since we need a per-tuple
-        * temporary memory context for function evaluation -- tgl July 00
-        */
-#ifndef OMIT_PARTIAL_INDEX
-       if (pred != NULL || oldPred != NULL)
-       {
-               tupleTable = ExecCreateTupleTable(1);
-               slot = ExecAllocTableSlot(tupleTable);
-               ExecSetSlotDescriptor(slot, htupdesc);
-       }
-       else
-       {
-               tupleTable = NULL;
-               slot = NULL;
-       }
-       econtext = MakeExprContext(slot, TransactionCommandContext);
-#else
-       econtext = MakeExprContext(NULL, TransactionCommandContext);
-#endif  /* OMIT_PARTIAL_INDEX */
+       /* initialize the root page */
+       buffer = ReadBuffer(index, P_NEW);
+       GISTInitBuffer(buffer, F_LEAF);
+       WriteBuffer(buffer);
 
        /* build the index */
-       nhtups = nitups = 0;
-
-       compvec = (bool *) palloc(sizeof(bool) * indexInfo->ii_NumIndexAttrs);
-
-       /* start a heap scan */
-       hscan = heap_beginscan(heap, 0, SnapshotNow, 0, (ScanKey) NULL);
-
-       while (HeapTupleIsValid(htup = heap_getnext(hscan, 0)))
-       {
-               MemoryContextReset(econtext->ecxt_per_tuple_memory);
-
-               nhtups++;
+       buildstate.numindexattrs = indexInfo->ii_NumIndexAttrs;
+       buildstate.indtuples = 0;
 
-#ifndef OMIT_PARTIAL_INDEX
-               /*
-                * If oldPred != NULL, this is an EXTEND INDEX command, so skip
-                * this tuple if it was already in the existing partial index
-                */
-               if (oldPred != NULL)
-               {
-                       slot->val = htup;
-                       if (ExecQual((List *) oldPred, econtext, false))
-                       {
-                               nitups++;
-                               continue;
-                       }
-               }
-
-               /*
-                * Skip this tuple if it doesn't satisfy the partial-index
-                * predicate
-                */
-               if (pred != NULL)
-               {
-                       slot->val = htup;
-                       if (!ExecQual((List *) pred, econtext, false))
-                               continue;
-               }
-#endif  /* OMIT_PARTIAL_INDEX */
-
-               nitups++;
-
-               /*
-                * For the current heap tuple, extract all the attributes we use
-                * in this index, and note which are null.
-                */
-               FormIndexDatum(indexInfo,
-                                          htup,
-                                          htupdesc,
-                                          econtext->ecxt_per_tuple_memory,
-                                          attdata,
-                                          nulls);
-
-               /* immediately compress keys to normalize */
-               for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
-               {
-                       gistcentryinit(&giststate, &tmpcentry, (char *) attdata[i],
-                                                  (Relation) NULL, (Page) NULL, (OffsetNumber) 0,
-                                                  -1 /* size is currently bogus */ , TRUE);
-                       if (attdata[i] != (Datum) tmpcentry.pred &&
-                               !(giststate.keytypbyval))
-                               compvec[i] = TRUE;
-                       else
-                               compvec[i] = FALSE;
-                       attdata[i] = (Datum) tmpcentry.pred;
-               }
-
-               /* form an index tuple and point it at the heap tuple */
-               itup = index_formtuple(itupdesc, attdata, nulls);
-               itup->t_tid = htup->t_self;
-
-               /*
-                * Since we already have the index relation locked, we call
-                * gistdoinsert directly.  Normal access method calls dispatch
-                * through gistinsert, which locks the relation for write.      This
-                * is the right thing to do if you're inserting single tups, but
-                * not when you're initializing the whole index at once.
-                */
-
-               res = gistdoinsert(index, itup, &giststate);
-
-               for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
-                       if (compvec[i])
-                               pfree(DatumGetPointer(attdata[i]));
-
-               pfree(itup);
-               pfree(res);
-       }
+       /* do the heap scan */
+       reltuples = IndexBuildHeapScan(heap, index, indexInfo,
+                                                               gistbuildCallback, (void *) &buildstate);
 
        /* okay, all heap tuples are indexed */
-       heap_endscan(hscan);
-
-       pfree(compvec);
-
-#ifndef OMIT_PARTIAL_INDEX
-       if (pred != NULL || oldPred != NULL)
-       {
-               ExecDropTupleTable(tupleTable, true);
-       }
-#endif  /* OMIT_PARTIAL_INDEX */
-       FreeExprContext(econtext);
 
        /*
         * Since we just counted the tuples in the heap, we update its stats
@@ -260,21 +201,84 @@ gistbuild(PG_FUNCTION_ARGS)
        {
                Oid                     hrelid = RelationGetRelid(heap);
                Oid                     irelid = RelationGetRelid(index);
-               bool            inplace = IsReindexProcessing();
 
                heap_close(heap, NoLock);
                index_close(index);
-               UpdateStats(hrelid, nhtups, inplace);
-               UpdateStats(irelid, nitups, inplace);
-               if (oldPred != NULL && !inplace)
+               UpdateStats(hrelid, reltuples);
+               UpdateStats(irelid, buildstate.indtuples);
+       }
+
+       freeGISTstate(&buildstate.giststate);
+#ifdef GISTDEBUG
+       gist_dumptree(index, 0, GISTP_ROOT, 0);
+#endif
+
+       PG_RETURN_VOID();
+}
+
+/*
+ * Per-tuple callback from IndexBuildHeapScan
+ */
+static void
+gistbuildCallback(Relation index,
+                                 HeapTuple htup,
+                                 Datum *attdata,
+                                 char *nulls,
+                                 bool tupleIsAlive,
+                                 void *state)
+{
+       GISTBuildState *buildstate = (GISTBuildState *) state;
+       IndexTuple      itup;
+       bool            compvec[INDEX_MAX_KEYS];
+       GISTENTRY       tmpcentry;
+       int                     i;
+
+       /* GiST cannot index tuples with leading NULLs */
+       if (nulls[0] == 'n')
+               return;
+
+       /* immediately compress keys to normalize */
+       for (i = 0; i < buildstate->numindexattrs; i++)
+       {
+               if (nulls[i] == 'n')
                {
-                       if (nitups == nhtups)
-                               pred = NULL;
-                       UpdateIndexPredicate(irelid, oldPred, pred);
+                       attdata[i] = (Datum) 0;
+                       compvec[i] = FALSE;
+               }
+               else
+               {
+                       gistcentryinit(&buildstate->giststate, i, &tmpcentry, attdata[i],
+                                                  (Relation) NULL, (Page) NULL, (OffsetNumber) 0,
+                                                -1 /* size is currently bogus */ , TRUE, FALSE);
+                       if (attdata[i] != tmpcentry.key &&
+                               !(isAttByVal(&buildstate->giststate, i)))
+                               compvec[i] = TRUE;
+                       else
+                               compvec[i] = FALSE;
+                       attdata[i] = tmpcentry.key;
                }
        }
 
-       PG_RETURN_VOID();
+       /* form an index tuple and point it at the heap tuple */
+       itup = index_formtuple(buildstate->giststate.tupdesc, attdata, nulls);
+       itup->t_tid = htup->t_self;
+
+       /*
+        * Since we already have the index relation locked, we call
+        * gistdoinsert directly.  Normal access method calls dispatch through
+        * gistinsert, which locks the relation for write.      This is the right
+        * thing to do if you're inserting single tups, but not when you're
+        * initializing the whole index at once.
+        */
+       gistdoinsert(index, itup, NULL, &buildstate->giststate);
+
+       buildstate->indtuples += 1;
+
+       for (i = 0; i < buildstate->numindexattrs; i++)
+               if (compvec[i])
+                       pfree(DatumGetPointer(attdata[i]));
+
+       pfree(itup);
 }
 
 /*
@@ -286,60 +290,79 @@ gistbuild(PG_FUNCTION_ARGS)
 Datum
 gistinsert(PG_FUNCTION_ARGS)
 {
-       Relation                r = (Relation) PG_GETARG_POINTER(0);
-       Datum              *datum = (Datum *) PG_GETARG_POINTER(1);
-       char               *nulls = (char *) PG_GETARG_POINTER(2);
-       ItemPointer             ht_ctid = (ItemPointer) PG_GETARG_POINTER(3);
+       Relation        r = (Relation) PG_GETARG_POINTER(0);
+       Datum      *datum = (Datum *) PG_GETARG_POINTER(1);
+       char       *nulls = (char *) PG_GETARG_POINTER(2);
+       ItemPointer ht_ctid = (ItemPointer) PG_GETARG_POINTER(3);
+
 #ifdef NOT_USED
-       Relation                heapRel = (Relation) PG_GETARG_POINTER(4);
+       Relation        heapRel = (Relation) PG_GETARG_POINTER(4);
+       bool            checkUnique = PG_GETARG_BOOL(5);
 #endif
        InsertIndexResult res;
        IndexTuple      itup;
        GISTSTATE       giststate;
        GISTENTRY       tmpentry;
        int                     i;
-       bool       *compvec;
+       bool            compvec[INDEX_MAX_KEYS];
+
+       /*
+        * Since GIST is not marked "amconcurrent" in pg_am, caller should
+        * have acquired exclusive lock on index relation.      We need no locking
+        * here.
+        */
+
+       /* GiST cannot index tuples with leading NULLs */
+       if (nulls[0] == 'n')
+       {
+               res = NULL;
+               PG_RETURN_POINTER(res);
+       }
 
        initGISTstate(&giststate, r);
 
        /* immediately compress keys to normalize */
-       compvec = (bool *) palloc(sizeof(bool) * r->rd_att->natts);
        for (i = 0; i < r->rd_att->natts; i++)
        {
-               gistcentryinit(&giststate, &tmpentry, (char *) datum[i],
-                                          (Relation) NULL, (Page) NULL, (OffsetNumber) 0,
-                                          -1 /* size is currently bogus */ , TRUE);
-               if (datum[i] != (Datum) tmpentry.pred && !(giststate.keytypbyval))
-                       compvec[i] = TRUE;
-               else
+               if (nulls[i] == 'n')
+               {
+                       datum[i] = (Datum) 0;
                        compvec[i] = FALSE;
-               datum[i] = (Datum) tmpentry.pred;
+               }
+               else
+               {
+                       gistcentryinit(&giststate, i, &tmpentry, datum[i],
+                                                  (Relation) NULL, (Page) NULL, (OffsetNumber) 0,
+                                                -1 /* size is currently bogus */ , TRUE, FALSE);
+                       if (datum[i] != tmpentry.key && !(isAttByVal(&giststate, i)))
+                               compvec[i] = TRUE;
+                       else
+                               compvec[i] = FALSE;
+                       datum[i] = tmpentry.key;
+               }
        }
-       itup = index_formtuple(RelationGetDescr(r), datum, nulls);
+       itup = index_formtuple(giststate.tupdesc, datum, nulls);
        itup->t_tid = *ht_ctid;
 
-       /*
-        * Notes in ExecUtils:ExecOpenIndices()
-        *
-        * RelationSetLockForWrite(r);
-        */
+       res = (InsertIndexResult) palloc(sizeof(InsertIndexResultData));
+       gistdoinsert(r, itup, &res, &giststate);
 
-       res = gistdoinsert(r, itup, &giststate);
        for (i = 0; i < r->rd_att->natts; i++)
                if (compvec[i] == TRUE)
-                       pfree((char *) datum[i]);
+                       pfree(DatumGetPointer(datum[i]));
        pfree(itup);
-       pfree(compvec);
+       freeGISTstate(&giststate);
 
        PG_RETURN_POINTER(res);
 }
 
+#ifdef GIST_PAGEADDITEM
 /*
-** Take a compressed entry, and install it on a page.  Since we now know
-** where the entry will live, we decompress it and recompress it using
-** that knowledge (some compression routines may want to fish around
-** on the page, for example, or do something special for leaf nodes.)
-*/
+ * Take a compressed entry, and install it on a page.  Since we now know
+ * where the entry will live, we decompress it and recompress it using
+ * that knowledge (some compression routines may want to fish around
+ * on the page, for example, or do something special for leaf nodes.)
+ */
 static OffsetNumber
 gistPageAddItem(GISTSTATE *giststate,
                                Relation r,
@@ -353,310 +376,877 @@ gistPageAddItem(GISTSTATE *giststate,
 {
        GISTENTRY       tmpcentry;
        IndexTuple      itup = (IndexTuple) item;
+       OffsetNumber retval;
+       Datum           datum;
+       bool            IsNull;
 
        /*
         * recompress the item given that we now know the exact page and
         * offset for insertion
         */
-       gistdentryinit(giststate, dentry,
-                                  (((char *) itup) + sizeof(IndexTupleData)),
-                         (Relation) 0, (Page) 0, (OffsetNumber) InvalidOffsetNumber,
-                                  IndexTupleSize(itup) - sizeof(IndexTupleData), FALSE);
-       gistcentryinit(giststate, &tmpcentry, dentry->pred, r, page,
+       datum = index_getattr(itup, 1, r->rd_att, &IsNull);
+       gistdentryinit(giststate, 0, dentry, datum,
+                                  (Relation) 0, (Page) 0,
+                                  (OffsetNumber) InvalidOffsetNumber,
+                                  ATTSIZE(datum, r, 1, IsNull),
+                                  FALSE, IsNull);
+       gistcentryinit(giststate, 0, &tmpcentry, dentry->key, r, page,
                                   offsetNumber, dentry->bytes, FALSE);
-       *newtup = gist_tuple_replacekey(r, *dentry, itup);
+       *newtup = gist_tuple_replacekey(r, tmpcentry, itup);
+       retval = PageAddItem(page, (Item) *newtup, IndexTupleSize(*newtup),
+                                                offsetNumber, flags);
+       if (retval == InvalidOffsetNumber)
+               elog(ERROR, "gist: failed to add index item to %s",
+                        RelationGetRelationName(r));
        /* be tidy */
-       if (tmpcentry.pred != dentry->pred
-               && tmpcentry.pred != (((char *) itup) + sizeof(IndexTupleData)))
-               pfree(tmpcentry.pred);
-
-       return (PageAddItem(page, (Item) *newtup, IndexTupleSize(*newtup),
-                                               offsetNumber, flags));
+       if (DatumGetPointer(tmpcentry.key) != NULL &&
+               tmpcentry.key != dentry->key &&
+               tmpcentry.key != datum)
+               pfree(DatumGetPointer(tmpcentry.key));
+       return (retval);
 }
+#endif
 
-
-static InsertIndexResult
+static void
 gistdoinsert(Relation r,
-                        IndexTuple itup,       /* itup contains compressed entry */
+                        IndexTuple itup,
+                        InsertIndexResult *res,
                         GISTSTATE *giststate)
 {
-       GISTENTRY       tmpdentry;
-       InsertIndexResult res;
-       OffsetNumber l;
-       GISTSTACK  *stack;
+       IndexTuple *instup;
+       int                     i,
+                               ret,
+                               len = 1;
+
+       instup = (IndexTuple *) palloc(sizeof(IndexTuple));
+       instup[0] = (IndexTuple) palloc(IndexTupleSize(itup));
+       memcpy(instup[0], itup, IndexTupleSize(itup));
+
+       ret = gistlayerinsert(r, GISTP_ROOT, &instup, &len, res, giststate);
+       if (ret & SPLITED)
+               gistnewroot(r, instup, len);
+
+       for (i = 0; i < len; i++)
+               pfree(instup[i]);
+       pfree(instup);
+}
+
+static int
+gistlayerinsert(Relation r, BlockNumber blkno,
+                               IndexTuple **itup,              /* in - out, has compressed entry */
+                               int *len,               /* in - out */
+                               InsertIndexResult *res, /* out */
+                               GISTSTATE *giststate)
+{
        Buffer          buffer;
-       BlockNumber blk;
        Page            page;
-       OffsetNumber off;
-       IndexTuple      newtup;
+       OffsetNumber child;
+       int                     ret;
+       GISTPageOpaque opaque;
 
-       /* 3rd arg is ignored for now */
-       blk = gistChooseSubtree(r, itup, 0, giststate, &stack, &buffer);
+       buffer = ReadBuffer(r, blkno);
        page = (Page) BufferGetPage(buffer);
+       opaque = (GISTPageOpaque) PageGetSpecialPointer(page);
 
-       if (gistnospace(page, itup))
+       if (!(opaque->flags & F_LEAF))
        {
-               /* need to do a split */
-               res = gistSplit(r, buffer, stack, itup, giststate);
-               gistfreestack(stack);
-               WriteBuffer(buffer);    /* don't forget to release buffer! */
-               return res;
+               /* internal page, so we must walk on tree */
+               /* len IS equal 1 */
+               ItemId          iid;
+               BlockNumber nblkno;
+               ItemPointerData oldtid;
+               IndexTuple      oldtup;
+
+               child = gistchoose(r, page, *(*itup), giststate);
+               iid = PageGetItemId(page, child);
+               oldtup = (IndexTuple) PageGetItem(page, iid);
+               nblkno = ItemPointerGetBlockNumber(&(oldtup->t_tid));
+
+               /*
+                * After this call: 1. if child page was splited, then itup
+                * contains keys for each page 2. if  child page wasn't splited,
+                * then itup contains additional for adjustement of current key
+                */
+               ret = gistlayerinsert(r, nblkno, itup, len, res, giststate);
+
+               /* nothing inserted in child */
+               if (!(ret & INSERTED))
+               {
+                       ReleaseBuffer(buffer);
+                       return 0x00;
+               }
+
+               /* child does not splited */
+               if (!(ret & SPLITED))
+               {
+                       IndexTuple      newtup = gistgetadjusted(r, oldtup, (*itup)[0], giststate);
+
+                       if (!newtup)
+                       {
+                               /* not need to update key */
+                               ReleaseBuffer(buffer);
+                               return 0x00;
+                       }
+
+                       pfree((*itup)[0]);      /* !!! */
+                       (*itup)[0] = newtup;
+               }
+
+               /* key is modified, so old version must be deleted */
+               ItemPointerSet(&oldtid, blkno, child);
+               gistdelete(r, &oldtid);
+
+               /*
+                * if child was splitted, new key for child will be inserted in
+                * the end list of child, so we must say to any scans that page is
+                * changed beginning from 'child' offset
+                */
+               if (ret & SPLITED)
+                       gistadjscans(r, GISTOP_SPLIT, blkno, child);
        }
 
-       if (PageIsEmpty(page))
-               off = FirstOffsetNumber;
+       ret = INSERTED;
+
+       if (gistnospace(page, (*itup), *len))
+       {
+               /* no space for insertion */
+               IndexTuple *itvec,
+                                  *newitup;
+               int                     tlen,
+                                       oldlen;
+
+               ret |= SPLITED;
+               itvec = gistreadbuffer(buffer, &tlen);
+               itvec = gistjoinvector(itvec, &tlen, (*itup), *len);
+               oldlen = *len;
+               newitup = gistSplit(r, buffer, itvec, &tlen, giststate,
+                                                       (opaque->flags & F_LEAF) ? res : NULL);         /* res only for
+                                                                                                                                                * inserting in leaf */
+               ReleaseBuffer(buffer);
+               do
+                       pfree((*itup)[oldlen - 1]);
+               while ((--oldlen) > 0);
+               pfree((*itup));
+               pfree(itvec);
+               *itup = newitup;
+               *len = tlen;                    /* now tlen >= 2 */
+       }
        else
-               off = OffsetNumberNext(PageGetMaxOffsetNumber(page));
+       {
+               /* enogth space */
+               OffsetNumber off,
+                                       l;
+
+               off = (PageIsEmpty(page)) ?
+                       FirstOffsetNumber
+                       :
+                       OffsetNumberNext(PageGetMaxOffsetNumber(page));
+               l = gistwritebuffer(r, page, (*itup), *len, off);
+               WriteBuffer(buffer);
 
-       /* add the item and write the buffer */
-       l = gistPageAddItem(giststate, r, page, (Item) itup, IndexTupleSize(itup),
-                                               off, LP_USED, &tmpdentry, &newtup);
-       WriteBuffer(buffer);
+               /*
+                * set res if insert into leaf page, in this case, len = 1 always
+                */
+               if (res && (opaque->flags & F_LEAF))
+                       ItemPointerSet(&((*res)->pointerData), blkno, l);
 
-       /* now expand the page boundary in the parent to include the new child */
-       gistAdjustKeys(r, stack, blk, tmpdentry.pred, tmpdentry.bytes, giststate);
-       gistfreestack(stack);
+               if (*len > 1)
+               {                                               /* previos insert ret & SPLITED != 0 */
+                       int                     i;
 
-       /* be tidy */
-       if (itup != newtup)
-               pfree(newtup);
-       if (tmpdentry.pred != (((char *) itup) + sizeof(IndexTupleData)))
-               pfree(tmpdentry.pred);
+                       /*
+                        * child was splited, so we must form union for insertion in
+                        * parent
+                        */
+                       IndexTuple      newtup = gistunion(r, (*itup), *len, giststate);
 
-       /* build and return an InsertIndexResult for this insertion */
-       res = (InsertIndexResult) palloc(sizeof(InsertIndexResultData));
-       ItemPointerSet(&(res->pointerData), blk, l);
+                       ItemPointerSet(&(newtup->t_tid), blkno, 1);
 
-       return res;
+                       for (i = 0; i < *len; i++)
+                               pfree((*itup)[i]);
+                       (*itup)[0] = newtup;
+                       *len = 1;
+               }
+       }
+
+       return ret;
 }
 
+/*
+ * Write itup vector to page, has no control of free space
+ */
+static OffsetNumber
+gistwritebuffer(Relation r, Page page, IndexTuple *itup,
+                               int len, OffsetNumber off)
+{
+       OffsetNumber l = InvalidOffsetNumber;
+       int                     i;
 
-static BlockNumber
-gistChooseSubtree(Relation r, IndexTuple itup, /* itup has compressed
-                                                                                                * entry */
-                                 int level,
-                                 GISTSTATE *giststate,
-                                 GISTSTACK **retstack /* out */ ,
-                                 Buffer *leafbuf /* out */ )
+#ifdef GIST_PAGEADDITEM
+       GISTENTRY       tmpdentry;
+       IndexTuple      newtup;
+       bool            IsNull;
+#endif
+       for (i = 0; i < len; i++)
+       {
+#ifdef GIST_PAGEADDITEM
+               l = gistPageAddItem(giststate, r, page,
+                                                       (Item) itup[i], IndexTupleSize(itup[i]),
+                                                       off, LP_USED, &tmpdentry, &newtup);
+               off = OffsetNumberNext(off);
+               if (DatumGetPointer(tmpdentry.key) != NULL &&
+                 tmpdentry.key != index_getattr(itup[i], 1, r->rd_att, &IsNull))
+                       pfree(DatumGetPointer(tmpdentry.key));
+               if (itup[i] != newtup)
+                       pfree(newtup);
+#else
+               l = PageAddItem(page, (Item) itup[i], IndexTupleSize(itup[i]),
+                                               off, LP_USED);
+               if (l == InvalidOffsetNumber)
+                       elog(ERROR, "gist: failed to add index item to %s",
+                                RelationGetRelationName(r));
+#endif
+       }
+       return l;
+}
+
+/*
+ * Check space for itup vector on page
+ */
+static int
+gistnospace(Page page, IndexTuple *itvec, int len)
 {
-       Buffer          buffer;
-       BlockNumber blk;
-       GISTSTACK  *stack;
-       Page            page;
-       GISTPageOpaque opaque;
-       IndexTuple      which;
+       unsigned int size = 0;
+       int                     i;
+
+       for (i = 0; i < len; i++)
+               size += IndexTupleSize(itvec[i]) + sizeof(ItemIdData);
+
+       return (PageGetFreeSpace(page) < size);
+}
 
-       blk = GISTP_ROOT;
-       buffer = InvalidBuffer;
-       stack = (GISTSTACK *) NULL;
+/*
+ * Read buffer into itup vector
+ */
+static IndexTuple *
+gistreadbuffer(Buffer buffer, int *len /* out */ )
+{
+       OffsetNumber i,
+                               maxoff;
+       IndexTuple *itvec;
+       Page            p = (Page) BufferGetPage(buffer);
 
-       do
+       maxoff = PageGetMaxOffsetNumber(p);
+       *len = maxoff;
+       itvec = palloc(sizeof(IndexTuple) * maxoff);
+       for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
+               itvec[i - 1] = (IndexTuple) PageGetItem(p, PageGetItemId(p, i));
+
+       return itvec;
+}
+
+/*
+ * join two vectors into one
+ */
+static IndexTuple *
+gistjoinvector(IndexTuple *itvec, int *len, IndexTuple *additvec, int addlen)
+{
+       itvec = (IndexTuple *) repalloc((void *) itvec, sizeof(IndexTuple) * ((*len) + addlen));
+       memmove(&itvec[*len], additvec, sizeof(IndexTuple) * addlen);
+       *len += addlen;
+       return itvec;
+}
+
+/*
+ * return union of itup vector
+ */
+static IndexTuple
+gistunion(Relation r, IndexTuple *itvec, int len, GISTSTATE *giststate)
+{
+       Datum           attr[INDEX_MAX_KEYS];
+       bool            whatfree[INDEX_MAX_KEYS];
+       char            isnull[INDEX_MAX_KEYS];
+       char       *storage;
+       bytea      *evec;
+       Datum           datum;
+       int                     datumsize,
+                               i,
+                               j;
+       GISTENTRY       centry[INDEX_MAX_KEYS];
+       bool       *needfree;
+       IndexTuple      newtup;
+       bool            IsNull;
+       int                     reallen;
+
+       needfree = (bool *) palloc(((len == 1) ? 2 : len) * sizeof(bool));
+       /* workaround for 64-bit: ensure GISTENTRY array is maxaligned */
+       storage = (char *) palloc(((len == 1) ? 2 : len) * sizeof(GISTENTRY) + MAXALIGN(VARHDRSZ));
+       evec = (bytea *) (storage + MAXALIGN(VARHDRSZ) - VARHDRSZ);
+
+       for (j = 0; j < r->rd_att->natts; j++)
        {
-               /* let go of current buffer before getting next */
-               if (buffer != InvalidBuffer)
-                       ReleaseBuffer(buffer);
+               reallen = 0;
+               for (i = 0; i < len; i++)
+               {
+                       datum = index_getattr(itvec[i], j + 1, giststate->tupdesc, &IsNull);
+                       if (IsNull)
+                               continue;
 
-               /* get next buffer */
-               buffer = ReadBuffer(r, blk);
-               page = (Page) BufferGetPage(buffer);
+                       gistdentryinit(giststate, j,
+                                                  &((GISTENTRY *) VARDATA(evec))[reallen],
+                                                  datum,
+                                          (Relation) NULL, (Page) NULL, (OffsetNumber) NULL,
+                                                  ATTSIZE(datum, giststate->tupdesc, j + 1, IsNull), FALSE, IsNull);
+                       if ((!isAttByVal(giststate, j)) &&
+                               ((GISTENTRY *) VARDATA(evec))[reallen].key != datum)
+                               needfree[reallen] = TRUE;
+                       else
+                               needfree[reallen] = FALSE;
+                       reallen++;
+               }
 
-               opaque = (GISTPageOpaque) PageGetSpecialPointer(page);
-               if (!(opaque->flags & F_LEAF))
+               if (reallen == 0)
                {
-                       GISTSTACK  *n;
-                       ItemId          iid;
-
-                       n = (GISTSTACK *) palloc(sizeof(GISTSTACK));
-                       n->gs_parent = stack;
-                       n->gs_blk = blk;
-                       n->gs_child = gistchoose(r, page, itup, giststate);
-                       stack = n;
-
-                       iid = PageGetItemId(page, n->gs_child);
-                       which = (IndexTuple) PageGetItem(page, iid);
-                       blk = ItemPointerGetBlockNumber(&(which->t_tid));
+                       attr[j] = (Datum) 0;
+                       isnull[j] = 'n';
+                       whatfree[j] = FALSE;
                }
-       } while (!(opaque->flags & F_LEAF));
+               else
+               {
+                       if (reallen == 1)
+                       {
+                               VARATT_SIZEP(evec) = 2 * sizeof(GISTENTRY) + VARHDRSZ;
+                               gistentryinit(((GISTENTRY *) VARDATA(evec))[1],
+                                       ((GISTENTRY *) VARDATA(evec))[0].key, r, (Page) NULL,
+                                                         (OffsetNumber) 0, ((GISTENTRY *) VARDATA(evec))[0].bytes, FALSE);
+
+                       }
+                       else
+                               VARATT_SIZEP(evec) = reallen * sizeof(GISTENTRY) + VARHDRSZ;
+                       datum = FunctionCall2(&giststate->unionFn[j],
+                                                                 PointerGetDatum(evec),
+                                                                 PointerGetDatum(&datumsize));
+
+                       for (i = 0; i < reallen; i++)
+                               if (needfree[i])
+                                       pfree(DatumGetPointer(((GISTENTRY *) VARDATA(evec))[i].key));
+
+                       gistcentryinit(giststate, j, &centry[j], datum,
+                                          (Relation) NULL, (Page) NULL, (OffsetNumber) NULL,
+                                                  datumsize, FALSE, FALSE);
+                       isnull[j] = ' ';
+                       attr[j] = centry[j].key;
+                       if (!isAttByVal(giststate, j))
+                       {
+                               whatfree[j] = TRUE;
+                               if (centry[j].key != datum)
+                                       pfree(DatumGetPointer(datum));
+                       }
+                       else
+                               whatfree[j] = FALSE;
+               }
+       }
+
+       pfree(storage);                         /* pfree(evec); */
+       pfree(needfree);
 
-       *retstack = stack;
-       *leafbuf = buffer;
+       newtup = (IndexTuple) index_formtuple(giststate->tupdesc, attr, isnull);
+       for (j = 0; j < r->rd_att->natts; j++)
+               if (whatfree[j])
+                       pfree(DatumGetPointer(attr[j]));
 
-       return blk;
+       return newtup;
 }
 
 
-static void
-gistAdjustKeys(Relation r,
-                          GISTSTACK *stk,
-                          BlockNumber blk,
-                          char *datum,         /* datum is uncompressed */
-                          int att_size,
-                          GISTSTATE *giststate)
+/*
+ * Forms union of oldtup and addtup, if union == oldtup then return NULL
+ */
+static IndexTuple
+gistgetadjusted(Relation r, IndexTuple oldtup, IndexTuple addtup, GISTSTATE *giststate)
 {
-       char       *oldud;
-       Page            p;
-       Buffer          b;
-       bool            result;
        bytea      *evec;
-       GISTENTRY       centry,
+       Datum           datum;
+       int                     datumsize;
+       bool            result,
+                               neednew = false;
+       char            isnull[INDEX_MAX_KEYS],
+                               whatfree[INDEX_MAX_KEYS];
+       Datum           attr[INDEX_MAX_KEYS];
+       GISTENTRY       centry[INDEX_MAX_KEYS],
+                               oldatt[INDEX_MAX_KEYS],
+                               addatt[INDEX_MAX_KEYS],
                           *ev0p,
                           *ev1p;
-       int                     size,
-                               datumsize;
-       IndexTuple      tid;
+       bool            olddec[INDEX_MAX_KEYS],
+                               adddec[INDEX_MAX_KEYS];
+       bool            oldisnull[INDEX_MAX_KEYS],
+                               addisnull[INDEX_MAX_KEYS];
+       IndexTuple      newtup = NULL;
+       char       *storage;
+       int                     j;
+
+       /* workaround for 64-bit: ensure GISTENTRY array is maxaligned */
+       storage = (char *) palloc(2 * sizeof(GISTENTRY) + MAXALIGN(VARHDRSZ));
+       evec = (bytea *) (storage + MAXALIGN(VARHDRSZ) - VARHDRSZ);
+       VARATT_SIZEP(evec) = 2 * sizeof(GISTENTRY) + VARHDRSZ;
+       ev0p = &((GISTENTRY *) VARDATA(evec))[0];
+       ev1p = &((GISTENTRY *) VARDATA(evec))[1];
 
-       if (stk == (GISTSTACK *) NULL)
-               return;
+       gistDeCompressAtt(giststate, r, oldtup, (Page) NULL,
+                                         (OffsetNumber) 0, oldatt, olddec, oldisnull);
 
-       b = ReadBuffer(r, stk->gs_blk);
-       p = BufferGetPage(b);
+       gistDeCompressAtt(giststate, r, addtup, (Page) NULL,
+                                         (OffsetNumber) 0, addatt, adddec, addisnull);
 
-       oldud = (char *) PageGetItem(p, PageGetItemId(p, stk->gs_child));
-       tid = (IndexTuple) oldud;
-       size = IndexTupleSize((IndexTuple) oldud) - sizeof(IndexTupleData);
-       oldud += sizeof(IndexTupleData);
 
-       evec = (bytea *) palloc(2 * sizeof(GISTENTRY) + VARHDRSZ);
-       VARATT_SIZEP(evec) = 2 * sizeof(GISTENTRY) + VARHDRSZ;
+       for (j = 0; j < r->rd_att->natts; j++)
+       {
+               if (oldisnull[j] && addisnull[j])
+               {
+                       isnull[j] = 'n';
+                       attr[j] = (Datum) 0;
+                       whatfree[j] = FALSE;
+               }
+               else
+               {
+                       FILLEV(
+                                  oldisnull[j], oldatt[j].key, oldatt[j].bytes,
+                                  addisnull[j], addatt[j].key, addatt[j].bytes
+                               );
 
-       /* insert decompressed oldud into entry vector */
-       gistdentryinit(giststate, &((GISTENTRY *) VARDATA(evec))[0],
-                                  oldud, r, p, stk->gs_child,
-                                  size, FALSE);
-       ev0p = &((GISTENTRY *) VARDATA(evec))[0];
+                       datum = FunctionCall2(&giststate->unionFn[j],
+                                                                 PointerGetDatum(evec),
+                                                                 PointerGetDatum(&datumsize));
 
-       /* insert datum entry into entry vector */
-       gistentryinit(((GISTENTRY *) VARDATA(evec))[1], datum,
-               (Relation) NULL, (Page) NULL, (OffsetNumber) 0, att_size, FALSE);
-       ev1p = &((GISTENTRY *) VARDATA(evec))[1];
+                       if (oldisnull[j] || addisnull[j])
+                       {
+                               if (oldisnull[j])
+                                       neednew = true;
+                       }
+                       else
+                       {
+                               FunctionCall3(&giststate->equalFn[j],
+                                                         ev0p->key,
+                                                         datum,
+                                                         PointerGetDatum(&result));
+
+                               if (!result)
+                                       neednew = true;
+                       }
+
+                       if (olddec[j])
+                               pfree(DatumGetPointer(oldatt[j].key));
+                       if (adddec[j])
+                               pfree(DatumGetPointer(addatt[j].key));
+
+                       gistcentryinit(giststate, j, &centry[j], datum,
+                                          (Relation) NULL, (Page) NULL, (OffsetNumber) NULL,
+                                                  datumsize, FALSE, FALSE);
+
+                       isnull[j] = ' ';
+                       attr[j] = centry[j].key;
+                       if ((!isAttByVal(giststate, j)))
+                       {
+                               whatfree[j] = TRUE;
+                               if (centry[j].key != datum)
+                                       pfree(DatumGetPointer(datum));
+                       }
+                       else
+                               whatfree[j] = FALSE;
+               }
+       }
+       pfree(storage);                         /* pfree(evec); */
+
+       if (neednew)
+       {
+               /* need to update key */
+               newtup = (IndexTuple) index_formtuple(giststate->tupdesc, attr, isnull);
+               newtup->t_tid = oldtup->t_tid;
+       }
+
+       for (j = 0; j < r->rd_att->natts; j++)
+               if (whatfree[j])
+                       pfree(DatumGetPointer(attr[j]));
+
+       return newtup;
+}
+
+static void
+gistunionsubkey(Relation r, GISTSTATE *giststate, IndexTuple *itvec, GIST_SPLITVEC *spl)
+{
+       int                     i,
+                               j,
+                               lr;
+       Datum      *attr;
+       bool       *needfree,
+                               IsNull;
+       int                     len,
+                          *attrsize;
+       OffsetNumber *entries;
+       bytea      *evec;
+       char       *storage;
+       Datum           datum;
+       int                     datumsize;
+       int                     reallen;
+       bool       *isnull;
+
+       for (lr = 0; lr <= 1; lr++)
+       {
+               if (lr)
+               {
+                       attrsize = spl->spl_lattrsize;
+                       attr = spl->spl_lattr;
+                       len = spl->spl_nleft;
+                       entries = spl->spl_left;
+                       isnull = spl->spl_lisnull;
+               }
+               else
+               {
+                       attrsize = spl->spl_rattrsize;
+                       attr = spl->spl_rattr;
+                       len = spl->spl_nright;
+                       entries = spl->spl_right;
+                       isnull = spl->spl_risnull;
+               }
 
-       /* form union of decompressed entries */
-       datum = (char *)
-               DatumGetPointer(FunctionCall2(&giststate->unionFn,
+               needfree = (bool *) palloc(((len == 1) ? 2 : len) * sizeof(bool));
+               /* workaround for 64-bit: ensure GISTENTRY array is maxaligned */
+               storage = (char *) palloc(((len == 1) ? 2 : len) * sizeof(GISTENTRY) + MAXALIGN(VARHDRSZ));
+               evec = (bytea *) (storage + MAXALIGN(VARHDRSZ) - VARHDRSZ);
+
+               for (j = 1; j < r->rd_att->natts; j++)
+               {
+                       reallen = 0;
+                       for (i = 0; i < len; i++)
+                       {
+                               if (spl->spl_idgrp[entries[i]])
+                                       continue;
+                               datum = index_getattr(itvec[entries[i] - 1], j + 1,
+                                                                         giststate->tupdesc, &IsNull);
+                               if (IsNull)
+                                       continue;
+                               gistdentryinit(giststate, j,
+                                                          &((GISTENTRY *) VARDATA(evec))[reallen],
+                                                          datum,
+                                                          (Relation) NULL, (Page) NULL,
+                                                          (OffsetNumber) NULL,
+                                                          ATTSIZE(datum, giststate->tupdesc, j + 1, IsNull), FALSE, IsNull);
+                               if ((!isAttByVal(giststate, j)) &&
+                                       ((GISTENTRY *) VARDATA(evec))[reallen].key != datum)
+                                       needfree[reallen] = TRUE;
+                               else
+                                       needfree[reallen] = FALSE;
+                               reallen++;
+
+                       }
+                       if (reallen == 0)
+                       {
+                               datum = (Datum) 0;
+                               datumsize = 0;
+                               isnull[j] = true;
+                       }
+                       else
+                       {
+                               /*
+                                * ((GISTENTRY *) VARDATA(evec))[0].bytes may be not
+                                * defined, so form union with itself
+                                */
+                               if (reallen == 1)
+                               {
+                                       VARATT_SIZEP(evec) = 2 * sizeof(GISTENTRY) + VARHDRSZ;
+                                       memcpy((void *) &((GISTENTRY *) VARDATA(evec))[1],
+                                                  (void *) &((GISTENTRY *) VARDATA(evec))[0],
+                                                  sizeof(GISTENTRY));
+                               }
+                               else
+                                       VARATT_SIZEP(evec) = reallen * sizeof(GISTENTRY) + VARHDRSZ;
+                               datum = FunctionCall2(&giststate->unionFn[j],
                                                                          PointerGetDatum(evec),
-                                                                         PointerGetDatum(&datumsize)));
-
-       /* did union leave decompressed version of oldud unchanged? */
-       FunctionCall3(&giststate->equalFn,
-                                 PointerGetDatum(ev0p->pred),
-                                 PointerGetDatum(datum),
-                                 PointerGetDatum(&result));
-       if (!result)
+                                                                         PointerGetDatum(&datumsize));
+                               isnull[j] = false;
+                       }
+
+                       for (i = 0; i < reallen; i++)
+                               if (needfree[i])
+                                       pfree(DatumGetPointer(((GISTENTRY *) VARDATA(evec))[i].key));
+
+                       attr[j] = datum;
+                       attrsize[j] = datumsize;
+               }
+               pfree(storage);                 /* pfree(evec); */
+               pfree(needfree);
+       }
+}
+
+/*
+ * find group in vector with equial value
+ */
+static int
+gistfindgroup(GISTSTATE *giststate, GISTENTRY *valvec, GIST_SPLITVEC *spl)
+{
+       int                     i,
+                               j,
+                               len;
+       int                     curid = 1;
+       bool            result;
+
+       /*
+        * first key is always not null (see gistinsert), so we may not check
+        * for nulls
+        */
+
+       for (i = 0; i < spl->spl_nleft; i++)
        {
-               TupleDesc       td = RelationGetDescr(r);
+               if (spl->spl_idgrp[spl->spl_left[i]])
+                       continue;
+               len = 0;
+               /* find all equal value in right part */
+               for (j = 0; j < spl->spl_nright; j++)
+               {
+                       if (spl->spl_idgrp[spl->spl_right[j]])
+                               continue;
+                       FunctionCall3(&giststate->equalFn[0],
+                                                 valvec[spl->spl_left[i]].key,
+                                                 valvec[spl->spl_right[j]].key,
+                                                 PointerGetDatum(&result));
+                       if (result)
+                       {
+                               spl->spl_idgrp[spl->spl_right[j]] = curid;
+                               len++;
+                       }
+               }
+               /* find all other equal value in left part */
+               if (len)
+               {
+                       /* add current val to list of equial values */
+                       spl->spl_idgrp[spl->spl_left[i]] = curid;
+                       /* searching .. */
+                       for (j = i + 1; j < spl->spl_nleft; j++)
+                       {
+                               if (spl->spl_idgrp[spl->spl_left[j]])
+                                       continue;
+                               FunctionCall3(&giststate->equalFn[0],
+                                                         valvec[spl->spl_left[i]].key,
+                                                         valvec[spl->spl_left[j]].key,
+                                                         PointerGetDatum(&result));
+                               if (result)
+                               {
+                                       spl->spl_idgrp[spl->spl_left[j]] = curid;
+                                       len++;
+                               }
+                       }
+                       spl->spl_ngrp[curid] = len + 1;
+                       curid++;
+               }
+       }
+
+       return curid;
+}
+
+/*
+ * Insert equivalent tuples to left or right page
+ * with minimize penalty
+ */
+static void
+gistadjsubkey(Relation r,
+                         IndexTuple *itup, /* contains compressed entry */
+                         int *len,
+                         GIST_SPLITVEC *v,
+                         GISTSTATE *giststate
+)
+{
+       int                     curlen;
+       OffsetNumber *curwpos;
+       bool            decfree[INDEX_MAX_KEYS];
+       GISTENTRY       entry,
+                               identry[INDEX_MAX_KEYS],
+                          *ev0p,
+                          *ev1p;
+       float           lpenalty,
+                               rpenalty;
+       bytea      *evec;
+       char       *storage;
+       int                     datumsize;
+       bool            isnull[INDEX_MAX_KEYS];
+       int                     i,
+                               j;
+       Datum           datum;
+
+       /* clear vectors */
+       curlen = v->spl_nleft;
+       curwpos = v->spl_left;
+       for (i = 0; i < v->spl_nleft; i++)
+               if (v->spl_idgrp[v->spl_left[i]] == 0)
+               {
+                       *curwpos = v->spl_left[i];
+                       curwpos++;
+               }
+               else
+                       curlen--;
+       v->spl_nleft = curlen;
+
+       curlen = v->spl_nright;
+       curwpos = v->spl_right;
+       for (i = 0; i < v->spl_nright; i++)
+               if (v->spl_idgrp[v->spl_right[i]] == 0)
+               {
+                       *curwpos = v->spl_right[i];
+                       curwpos++;
+               }
+               else
+                       curlen--;
+       v->spl_nright = curlen;
+
+       /* workaround for 64-bit: ensure GISTENTRY array is maxaligned */
+       storage = (char *) palloc(2 * sizeof(GISTENTRY) + MAXALIGN(VARHDRSZ));
+       evec = (bytea *) (storage + MAXALIGN(VARHDRSZ) - VARHDRSZ);
+       VARATT_SIZEP(evec) = 2 * sizeof(GISTENTRY) + VARHDRSZ;
+       ev0p = &((GISTENTRY *) VARDATA(evec))[0];
+       ev1p = &((GISTENTRY *) VARDATA(evec))[1];
+
+       /* add equivalent tuple */
+       for (i = 0; i < *len; i++)
+       {
+               if (v->spl_idgrp[i + 1] == 0)   /* already inserted */
+                       continue;
+               gistDeCompressAtt(giststate, r, itup[i], (Page) NULL, (OffsetNumber) 0,
+                                                 identry, decfree, isnull);
+
+               v->spl_ngrp[v->spl_idgrp[i + 1]]--;
+               if (v->spl_ngrp[v->spl_idgrp[i + 1]] == 0 &&
+               (v->spl_grpflag[v->spl_idgrp[i + 1]] & BOTH_ADDED) != BOTH_ADDED)
+               {
 
-               /* compress datum for storage on page */
-               gistcentryinit(giststate, &centry, datum, ev0p->rel, ev0p->page,
-                                          ev0p->offset, datumsize, FALSE);
-               if (td->attrs[0]->attlen >= 0)
+                       /* force last in group */
+                       rpenalty = 1.0;
+                       lpenalty = (v->spl_grpflag[v->spl_idgrp[i + 1]] & LEFT_ADDED) ? 2.0 : 0.0;
+               }
+               else
                {
-                       memmove(oldud, centry.pred, att_size);
-                       gistAdjustKeys(r, stk->gs_parent, stk->gs_blk, datum, att_size,
-                                                  giststate);
+                       /* where? */
+                       for (j = 1; j < r->rd_att->natts; j++)
+                       {
+                               gistentryinit(entry, v->spl_lattr[j], r, (Page) NULL,
+                                                  (OffsetNumber) 0, v->spl_lattrsize[j], FALSE);
+                               gistpenalty(giststate, j, &entry, v->spl_lisnull[j],
+                                                       &identry[j], isnull[j], &lpenalty);
+
+                               gistentryinit(entry, v->spl_rattr[j], r, (Page) NULL,
+                                                  (OffsetNumber) 0, v->spl_rattrsize[j], FALSE);
+                               gistpenalty(giststate, j, &entry, v->spl_risnull[j],
+                                                       &identry[j], isnull[j], &rpenalty);
+
+                               if (lpenalty != rpenalty)
+                                       break;
+                       }
                }
-               else if (VARSIZE(centry.pred) == VARSIZE(oldud))
+               /* add */
+               if (lpenalty < rpenalty)
                {
-                       memmove(oldud, centry.pred, VARSIZE(centry.pred));
-                       gistAdjustKeys(r, stk->gs_parent, stk->gs_blk, datum, att_size,
-                                                  giststate);
+                       v->spl_grpflag[v->spl_idgrp[i + 1]] |= LEFT_ADDED;
+                       v->spl_left[v->spl_nleft] = i + 1;
+                       v->spl_nleft++;
+                       for (j = 1; j < r->rd_att->natts; j++)
+                       {
+                               if (isnull[j] && v->spl_lisnull[j])
+                               {
+                                       v->spl_lattr[j] = (Datum) 0;
+                                       v->spl_lattrsize[j] = 0;
+                               }
+                               else
+                               {
+                                       FILLEV(
+                                                  v->spl_lisnull[j], v->spl_lattr[j], v->spl_lattrsize[j],
+                                                  isnull[j], identry[j].key, identry[j].bytes
+                                               );
+
+                                       datum = FunctionCall2(&giststate->unionFn[j],
+                                                                                 PointerGetDatum(evec),
+                                                                                 PointerGetDatum(&datumsize));
+
+                                       if ((!isAttByVal(giststate, j)) && !v->spl_lisnull[j])
+                                               pfree(DatumGetPointer(v->spl_lattr[j]));
+                                       v->spl_lattr[j] = datum;
+                                       v->spl_lattrsize[j] = datumsize;
+                                       v->spl_lisnull[j] = false;
+                               }
+                       }
                }
                else
                {
+                       v->spl_grpflag[v->spl_idgrp[i + 1]] |= RIGHT_ADDED;
+                       v->spl_right[v->spl_nright] = i + 1;
+                       v->spl_nright++;
+                       for (j = 1; j < r->rd_att->natts; j++)
+                       {
+                               if (isnull[j] && v->spl_risnull[j])
+                               {
+                                       v->spl_rattr[j] = (Datum) 0;
+                                       v->spl_rattrsize[j] = 0;
+                               }
+                               else
+                               {
+                                       FILLEV(
+                                                  v->spl_risnull[j], v->spl_rattr[j], v->spl_rattrsize[j],
+                                                  isnull[j], identry[j].key, identry[j].bytes
+                                               );
+
+                                       datum = FunctionCall2(&giststate->unionFn[j],
+                                                                                 PointerGetDatum(evec),
+                                                                                 PointerGetDatum(&datumsize));
+
+                                       if ((!isAttByVal(giststate, j)) && !v->spl_risnull[j])
+                                               pfree(DatumGetPointer(v->spl_rattr[j]));
+
+                                       v->spl_rattr[j] = datum;
+                                       v->spl_rattrsize[j] = datumsize;
+                                       v->spl_risnull[j] = false;
+                               }
+                       }
 
-                       /*
-                        * * new datum is not the same size as the old. * We have to
-                        * delete the old entry and insert the new * one.  Note that
-                        * this may cause a split here!
-                        */
-                       IndexTuple      newtup;
-                       ItemPointerData oldtid;
-                       char       *isnull;
-                       TupleDesc       tupDesc;
-                       InsertIndexResult res;
-
-                       /* delete old tuple */
-                       ItemPointerSet(&oldtid, stk->gs_blk, stk->gs_child);
-                       DirectFunctionCall2(gistdelete,
-                                                               PointerGetDatum(r),
-                                                               PointerGetDatum(&oldtid));
-
-                       /* generate and insert new tuple */
-                       tupDesc = r->rd_att;
-                       isnull = (char *) palloc(r->rd_rel->relnatts);
-                       MemSet(isnull, ' ', r->rd_rel->relnatts);
-                       newtup = (IndexTuple) index_formtuple(tupDesc,
-                                                                                (Datum *) &centry.pred, isnull);
-                       pfree(isnull);
-                       /* set pointer in new tuple to point to current child */
-                       ItemPointerSet(&oldtid, blk, 1);
-                       newtup->t_tid = oldtid;
-
-                       /* inserting the new entry also adjust keys above */
-                       res = gistentryinsert(r, stk, newtup, giststate);
-
-                       /* in stack, set info to point to new tuple */
-                       stk->gs_blk = ItemPointerGetBlockNumber(&(res->pointerData));
-                       stk->gs_child = ItemPointerGetOffsetNumber(&(res->pointerData));
-
-                       pfree(res);
                }
-               WriteBuffer(b);
-
-               if (centry.pred != datum)
-                       pfree(datum);
+               gistFreeAtt(r, identry, decfree);
        }
-       else
-               ReleaseBuffer(b);
-       pfree(evec);
+       pfree(storage);                         /* pfree(evec); */
 }
 
 /*
  *     gistSplit -- split a page in the tree.
- *
  */
-static InsertIndexResult
+static IndexTuple *
 gistSplit(Relation r,
                  Buffer buffer,
-                 GISTSTACK *stack,
-                 IndexTuple itup,              /* contains compressed entry */
-                 GISTSTATE *giststate)
+                 IndexTuple *itup,             /* contains compressed entry */
+                 int *len,
+                 GISTSTATE *giststate,
+                 InsertIndexResult *res)
 {
        Page            p;
        Buffer          leftbuf,
                                rightbuf;
        Page            left,
                                right;
-       ItemId          itemid;
-       IndexTuple      item;
-       IndexTuple      ltup,
-                               rtup,
-                               newtup;
-       OffsetNumber maxoff;
-       OffsetNumber i;
-       OffsetNumber leftoff,
-                               rightoff;
+       IndexTuple *lvectup,
+                          *rvectup,
+                          *newtup;
        BlockNumber lbknum,
                                rbknum;
-       BlockNumber bufblock;
        GISTPageOpaque opaque;
-       int                     blank;
-       InsertIndexResult res;
-       char       *isnull;
        GIST_SPLITVEC v;
-       TupleDesc       tupDesc;
        bytea      *entryvec;
+       char       *storage;
        bool       *decompvec;
-       IndexTuple      item_1;
-       GISTENTRY       tmpdentry,
-                               tmpentry;
+       int                     i,
+                               j,
+                               nlen;
+       int                     MaxGrpId = 1;
+       Datum           datum;
+       bool            IsNull;
 
-       isnull = (char *) palloc(r->rd_rel->relnatts);
-       for (blank = 0; blank < r->rd_rel->relnatts; blank++)
-               isnull[blank] = ' ';
        p = (Page) BufferGetPage(buffer);
        opaque = (GISTPageOpaque) PageGetSpecialPointer(p);
 
-
        /*
         * The root of the tree is the first block in the relation.  If we're
         * about to split the root, we need to do some hocus-pocus to enforce
@@ -684,313 +1274,173 @@ gistSplit(Relation r,
        right = (Page) BufferGetPage(rightbuf);
 
        /* generate the item array */
-       maxoff = PageGetMaxOffsetNumber(p);
-       entryvec = (bytea *) palloc(VARHDRSZ + (maxoff + 2) * sizeof(GISTENTRY));
-       decompvec = (bool *) palloc(VARHDRSZ + (maxoff + 2) * sizeof(bool));
-       for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
+       /* workaround for 64-bit: ensure GISTENTRY array is maxaligned */
+       storage = palloc(MAXALIGN(VARHDRSZ) + (*len + 1) * sizeof(GISTENTRY));
+       entryvec = (bytea *) (storage + MAXALIGN(VARHDRSZ) - VARHDRSZ);
+       decompvec = (bool *) palloc((*len + 1) * sizeof(bool));
+       VARATT_SIZEP(entryvec) = (*len + 1) * sizeof(GISTENTRY) + VARHDRSZ;
+       for (i = 1; i <= *len; i++)
        {
-               item_1 = (IndexTuple) PageGetItem(p, PageGetItemId(p, i));
-               gistdentryinit(giststate, &((GISTENTRY *) VARDATA(entryvec))[i],
-                                          (((char *) item_1) + sizeof(IndexTupleData)),
-                                          r, p, i,
-                                IndexTupleSize(item_1) - sizeof(IndexTupleData), FALSE);
-               if ((char *) (((GISTENTRY *) VARDATA(entryvec))[i].pred)
-                       == (((char *) item_1) + sizeof(IndexTupleData)))
-                       decompvec[i] = FALSE;
-               else
+               datum = index_getattr(itup[i - 1], 1, giststate->tupdesc, &IsNull);
+               gistdentryinit(giststate, 0, &((GISTENTRY *) VARDATA(entryvec))[i],
+                                          datum, r, p, i,
+                  ATTSIZE(datum, giststate->tupdesc, 1, IsNull), FALSE, IsNull);
+               if ((!isAttByVal(giststate, 0)) && ((GISTENTRY *) VARDATA(entryvec))[i].key != datum)
                        decompvec[i] = TRUE;
+               else
+                       decompvec[i] = FALSE;
        }
 
-       /* add the new datum as the last entry */
-       gistdentryinit(giststate, &(((GISTENTRY *) VARDATA(entryvec))[maxoff + 1]),
-                                  (((char *) itup) + sizeof(IndexTupleData)),
-                                  (Relation) NULL, (Page) NULL,
-                                  (OffsetNumber) 0, tmpentry.bytes, FALSE);
-       if ((char *) (((GISTENTRY *) VARDATA(entryvec))[maxoff + 1]).pred !=
-               (((char *) itup) + sizeof(IndexTupleData)))
-               decompvec[maxoff + 1] = TRUE;
-       else
-               decompvec[maxoff + 1] = FALSE;
-
-       VARATT_SIZEP(entryvec) = (maxoff + 2) * sizeof(GISTENTRY) + VARHDRSZ;
-
-       /* now let the user-defined picksplit function set up the split vector */
-       FunctionCall2(&giststate->picksplitFn,
+       /*
+        * now let the user-defined picksplit function set up the split
+        * vector; in entryvec have no null value!!
+        */
+       FunctionCall2(&giststate->picksplitFn[0],
                                  PointerGetDatum(entryvec),
                                  PointerGetDatum(&v));
 
-       /* compress ldatum and rdatum */
-       gistcentryinit(giststate, &tmpentry, v.spl_ldatum, (Relation) NULL,
-                                  (Page) NULL, (OffsetNumber) 0,
-                                  ((GISTENTRY *) VARDATA(entryvec))[i].bytes, FALSE);
-       if (v.spl_ldatum != tmpentry.pred)
-               pfree(v.spl_ldatum);
-       v.spl_ldatum = tmpentry.pred;
-
-       gistcentryinit(giststate, &tmpentry, v.spl_rdatum, (Relation) NULL,
-                                  (Page) NULL, (OffsetNumber) 0,
-                                  ((GISTENTRY *) VARDATA(entryvec))[i].bytes, FALSE);
-       if (v.spl_rdatum != tmpentry.pred)
-               pfree(v.spl_rdatum);
-       v.spl_rdatum = tmpentry.pred;
-
-       /* clean up the entry vector: its preds need to be deleted, too */
-       for (i = FirstOffsetNumber; i <= maxoff + 1; i = OffsetNumberNext(i))
-               if (decompvec[i])
-                       pfree(((GISTENTRY *) VARDATA(entryvec))[i].pred);
-       pfree(entryvec);
-       pfree(decompvec);
-
-       leftoff = rightoff = FirstOffsetNumber;
-       maxoff = PageGetMaxOffsetNumber(p);
-       for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
-       {
-               itemid = PageGetItemId(p, i);
-               item = (IndexTuple) PageGetItem(p, itemid);
-
-               if (i == *(v.spl_left))
-               {
-                       gistPageAddItem(giststate, r, left, (Item) item,
-                                                       IndexTupleSize(item),
-                                                       leftoff, LP_USED, &tmpdentry, &newtup);
-                       leftoff = OffsetNumberNext(leftoff);
-                       v.spl_left++;           /* advance in left split vector */
-                       /* be tidy */
-                       if (tmpdentry.pred != (((char *) item) + sizeof(IndexTupleData)))
-                               pfree(tmpdentry.pred);
-                       if ((IndexTuple) item != newtup)
-                               pfree(newtup);
-               }
-               else
-               {
-                       gistPageAddItem(giststate, r, right, (Item) item,
-                                                       IndexTupleSize(item),
-                                                       rightoff, LP_USED, &tmpdentry, &newtup);
-                       rightoff = OffsetNumberNext(rightoff);
-                       v.spl_right++;          /* advance in right split vector */
-                       /* be tidy */
-                       if (tmpdentry.pred != (((char *) item) + sizeof(IndexTupleData)))
-                               pfree(tmpdentry.pred);
-                       if (item != newtup)
-                               pfree(newtup);
-               }
-       }
-
-       /* build an InsertIndexResult for this insertion */
-       res = (InsertIndexResult) palloc(sizeof(InsertIndexResultData));
-
-       /* now insert the new index tuple */
-       if (*(v.spl_left) != FirstOffsetNumber)
-       {
-               gistPageAddItem(giststate, r, left, (Item) itup,
-                                               IndexTupleSize(itup),
-                                               leftoff, LP_USED, &tmpdentry, &newtup);
-               leftoff = OffsetNumberNext(leftoff);
-               ItemPointerSet(&(res->pointerData), lbknum, leftoff);
-               /* be tidy */
-               if (tmpdentry.pred != (((char *) itup) + sizeof(IndexTupleData)))
-                       pfree(tmpdentry.pred);
-               if (itup != newtup)
-                       pfree(newtup);
-       }
-       else
-       {
-               gistPageAddItem(giststate, r, right, (Item) itup,
-                                               IndexTupleSize(itup),
-                                               rightoff, LP_USED, &tmpdentry, &newtup);
-               rightoff = OffsetNumberNext(rightoff);
-               ItemPointerSet(&(res->pointerData), rbknum, rightoff);
-               /* be tidy */
-               if (tmpdentry.pred != (((char *) itup) + sizeof(IndexTupleData)))
-                       pfree(tmpdentry.pred);
-               if (itup != newtup)
-                       pfree(newtup);
-       }
+       /* compatibility with old code */
+       if (v.spl_left[v.spl_nleft - 1] == InvalidOffsetNumber)
+               v.spl_left[v.spl_nleft - 1] = (OffsetNumber) *len;
+       if (v.spl_right[v.spl_nright - 1] == InvalidOffsetNumber)
+               v.spl_right[v.spl_nright - 1] = (OffsetNumber) *len;
 
-       if ((bufblock = BufferGetBlockNumber(buffer)) != GISTP_ROOT)
-               PageRestoreTempPage(left, p);
-       WriteBuffer(leftbuf);
-       WriteBuffer(rightbuf);
+       v.spl_lattr[0] = v.spl_ldatum;
+       v.spl_rattr[0] = v.spl_rdatum;
+       v.spl_lisnull[0] = false;
+       v.spl_risnull[0] = false;
 
        /*
-        * Okay, the page is split.  We have three things left to do:
-        *
-        * 1)  Adjust any active scans on this index to cope with changes we
-        * introduced in its structure by splitting this page.
-        *
-        * 2)  "Tighten" the bounding box of the pointer to the left page in the
-        * parent node in the tree, if any.  Since we moved a bunch of stuff
-        * off the left page, we expect it to get smaller.      This happens in
-        * the internal insertion routine.
-        *
-        * 3)  Insert a pointer to the right page in the parent.  This may cause
-        * the parent to split.  If it does, we need to repeat steps one and
-        * two for each split node in the tree.
+        * if index is multikey, then we must to try get smaller bounding box
+        * for subkey(s)
         */
+       if (r->rd_att->natts > 1)
+       {
+               v.spl_idgrp = (int *) palloc(sizeof(int) * (*len + 1));
+               MemSet((void *) v.spl_idgrp, 0, sizeof(int) * (*len + 1));
+               v.spl_grpflag = (char *) palloc(sizeof(char) * (*len + 1));
+               MemSet((void *) v.spl_grpflag, 0, sizeof(char) * (*len + 1));
+               v.spl_ngrp = (int *) palloc(sizeof(int) * (*len + 1));
+
+               MaxGrpId = gistfindgroup(giststate, (GISTENTRY *) VARDATA(entryvec), &v);
 
-       /* adjust active scans */
-       gistadjscans(r, GISTOP_SPLIT, bufblock, FirstOffsetNumber);
+               /* form union of sub keys for each page (l,p) */
+               gistunionsubkey(r, giststate, itup, &v);
 
-       tupDesc = r->rd_att;
+               /*
+                * if possible, we insert equivalent tuples with control by
+                * penalty for a subkey(s)
+                */
+               if (MaxGrpId > 1)
+                       gistadjsubkey(r, itup, len, &v, giststate);
 
-       ltup = (IndexTuple) index_formtuple(tupDesc,
-                                                                         (Datum *) &(v.spl_ldatum), isnull);
-       rtup = (IndexTuple) index_formtuple(tupDesc,
-                                                                         (Datum *) &(v.spl_rdatum), isnull);
-       pfree(isnull);
+               pfree(v.spl_idgrp);
+               pfree(v.spl_grpflag);
+               pfree(v.spl_ngrp);
+       }
 
-       /* set pointers to new child pages in the internal index tuples */
-       ItemPointerSet(&(ltup->t_tid), lbknum, 1);
-       ItemPointerSet(&(rtup->t_tid), rbknum, 1);
+       /* clean up the entry vector: its keys need to be deleted, too */
+       for (i = 1; i <= *len; i++)
+               if (decompvec[i])
+                       pfree(DatumGetPointer(((GISTENTRY *) VARDATA(entryvec))[i].key));
+       pfree(storage);                         /* pfree(entryvec); */
+       pfree(decompvec);
 
-       gistintinsert(r, stack, ltup, rtup, giststate);
+       /* form left and right vector */
+       lvectup = (IndexTuple *) palloc(sizeof(IndexTuple) * v.spl_nleft);
+       rvectup = (IndexTuple *) palloc(sizeof(IndexTuple) * v.spl_nright);
 
-       pfree(ltup);
-       pfree(rtup);
+       for (i = 0; i < v.spl_nleft; i++)
+               lvectup[i] = itup[v.spl_left[i] - 1];
 
-       return res;
-}
+       for (i = 0; i < v.spl_nright; i++)
+               rvectup[i] = itup[v.spl_right[i] - 1];
 
-/*
-** After a split, we need to overwrite the old entry's key in the parent,
-** and install install an entry for the new key into the parent.
-*/
-static void
-gistintinsert(Relation r,
-                         GISTSTACK *stk,
-                         IndexTuple ltup,      /* new version of entry for old page */
-                         IndexTuple rtup,      /* entry for new page */
-                         GISTSTATE *giststate)
-{
-       ItemPointerData ltid;
 
-       if (stk == (GISTSTACK *) NULL)
+       /* write on disk (may be need another split) */
+       if (gistnospace(right, rvectup, v.spl_nright))
        {
-               gistnewroot(giststate, r, ltup, rtup);
-               return;
+               nlen = v.spl_nright;
+               newtup = gistSplit(r, rightbuf, rvectup, &nlen, giststate,
+                         (res && rvectup[nlen - 1] == itup[*len - 1]) ? res : NULL);
+               ReleaseBuffer(rightbuf);
+               for (j = 1; j < r->rd_att->natts; j++)
+                       if ((!isAttByVal(giststate, j)) && !v.spl_risnull[j])
+                               pfree(DatumGetPointer(v.spl_rattr[j]));
        }
+       else
+       {
+               OffsetNumber l;
 
-       /* remove old left pointer, insert the 2 new entries */
-       ItemPointerSet(&ltid, stk->gs_blk, stk->gs_child);
-       DirectFunctionCall2(gistdelete,
-                                               PointerGetDatum(r),
-                                               PointerGetDatum(&ltid));
-       gistentryinserttwo(r, stk, ltup, rtup, giststate);
-}
+               l = gistwritebuffer(r, right, rvectup, v.spl_nright, FirstOffsetNumber);
+               WriteBuffer(rightbuf);
 
+               if (res)
+                       ItemPointerSet(&((*res)->pointerData), rbknum, l);
 
-/*
-** Insert two entries onto one page, handling a split for either one!
-*/
-static void
-gistentryinserttwo(Relation r, GISTSTACK *stk, IndexTuple ltup,
-                                  IndexTuple rtup, GISTSTATE *giststate)
-{
-       Buffer          b;
-       Page            p;
-       InsertIndexResult res;
-       GISTENTRY       tmpentry;
-       IndexTuple      newtup;
+               nlen = 1;
+               newtup = (IndexTuple *) palloc(sizeof(IndexTuple) * 1);
+               newtup[0] = gistFormTuple(giststate, r, v.spl_rattr, v.spl_rattrsize, v.spl_risnull);
+               ItemPointerSet(&(newtup[0]->t_tid), rbknum, 1);
+       }
 
-       b = ReadBuffer(r, stk->gs_blk);
-       p = BufferGetPage(b);
 
-       if (gistnospace(p, ltup))
+       if (gistnospace(left, lvectup, v.spl_nleft))
        {
-               res = gistSplit(r, b, stk->gs_parent, ltup, giststate);
-               WriteBuffer(b);                 /* don't forget to release buffer!  -
-                                                                * 01/31/94 */
-               pfree(res);
-               gistdoinsert(r, rtup, giststate);
+               int                     llen = v.spl_nleft;
+               IndexTuple *lntup;
+
+               lntup = gistSplit(r, leftbuf, lvectup, &llen, giststate,
+                         (res && lvectup[llen - 1] == itup[*len - 1]) ? res : NULL);
+               ReleaseBuffer(leftbuf);
+
+               for (j = 1; j < r->rd_att->natts; j++)
+                       if ((!isAttByVal(giststate, j)) && !v.spl_lisnull[j])
+                               pfree(DatumGetPointer(v.spl_lattr[j]));
+
+               newtup = gistjoinvector(newtup, &nlen, lntup, llen);
+               pfree(lntup);
        }
        else
        {
-               gistPageAddItem(giststate, r, p, (Item) ltup,
-                                               IndexTupleSize(ltup), InvalidOffsetNumber,
-                                               LP_USED, &tmpentry, &newtup);
-               WriteBuffer(b);
-               gistAdjustKeys(r, stk->gs_parent, stk->gs_blk, tmpentry.pred,
-                                          tmpentry.bytes, giststate);
-               /* be tidy */
-               if (tmpentry.pred != (((char *) ltup) + sizeof(IndexTupleData)))
-                       pfree(tmpentry.pred);
-               if (ltup != newtup)
-                       pfree(newtup);
-               gistentryinsert(r, stk, rtup, giststate);
-       }
-}
+               OffsetNumber l;
 
+               l = gistwritebuffer(r, left, lvectup, v.spl_nleft, FirstOffsetNumber);
+               if (BufferGetBlockNumber(buffer) != GISTP_ROOT)
+                       PageRestoreTempPage(left, p);
 
-/*
-** Insert an entry onto a page
-*/
-static InsertIndexResult
-gistentryinsert(Relation r, GISTSTACK *stk, IndexTuple tup,
-                               GISTSTATE *giststate)
-{
-       Buffer          b;
-       Page            p;
-       InsertIndexResult res;
-       OffsetNumber off;
-       GISTENTRY       tmpentry;
-       IndexTuple      newtup;
+               WriteBuffer(leftbuf);
 
-       b = ReadBuffer(r, stk->gs_blk);
-       p = BufferGetPage(b);
+               if (res)
+                       ItemPointerSet(&((*res)->pointerData), lbknum, l);
 
-       if (gistnospace(p, tup))
-       {
-               res = gistSplit(r, b, stk->gs_parent, tup, giststate);
-               WriteBuffer(b);                 /* don't forget to release buffer!  -
-                                                                * 01/31/94 */
-               return res;
+               nlen += 1;
+               newtup = (IndexTuple *) repalloc((void *) newtup, sizeof(IndexTuple) * nlen);
+               newtup[nlen - 1] = gistFormTuple(giststate, r, v.spl_lattr, v.spl_lattrsize, v.spl_lisnull);
+               ItemPointerSet(&(newtup[nlen - 1]->t_tid), lbknum, 1);
        }
-       else
-       {
-               res = (InsertIndexResult) palloc(sizeof(InsertIndexResultData));
-               off = gistPageAddItem(giststate, r, p, (Item) tup, IndexTupleSize(tup),
-                                          InvalidOffsetNumber, LP_USED, &tmpentry, &newtup);
-               WriteBuffer(b);
-               ItemPointerSet(&(res->pointerData), stk->gs_blk, off);
-               gistAdjustKeys(r, stk->gs_parent, stk->gs_blk, tmpentry.pred,
-                                          tmpentry.bytes, giststate);
-               /* be tidy */
-               if (tmpentry.pred != (((char *) tup) + sizeof(IndexTupleData)))
-                       pfree(tmpentry.pred);
-               if (tup != newtup)
-                       pfree(newtup);
-               return res;
-       }
-}
 
+       /* !!! pfree */
+       pfree(rvectup);
+       pfree(lvectup);
+       pfree(v.spl_left);
+       pfree(v.spl_right);
+
+       *len = nlen;
+       return newtup;
+}
 
 static void
-gistnewroot(GISTSTATE *giststate, Relation r, IndexTuple lt, IndexTuple rt)
+gistnewroot(Relation r, IndexTuple *itup, int len)
 {
        Buffer          b;
        Page            p;
-       GISTENTRY       tmpentry;
-       IndexTuple      newtup;
 
        b = ReadBuffer(r, GISTP_ROOT);
        GISTInitBuffer(b, 0);
        p = BufferGetPage(b);
-       gistPageAddItem(giststate, r, p, (Item) lt, IndexTupleSize(lt),
-                                       FirstOffsetNumber,
-                                       LP_USED, &tmpentry, &newtup);
-       /* be tidy */
-       if (tmpentry.pred != (((char *) lt) + sizeof(IndexTupleData)))
-               pfree(tmpentry.pred);
-       if (lt != newtup)
-               pfree(newtup);
-       gistPageAddItem(giststate, r, p, (Item) rt, IndexTupleSize(rt),
-                                       OffsetNumberNext(FirstOffsetNumber), LP_USED,
-                                       &tmpentry, &newtup);
-       /* be tidy */
-       if (tmpentry.pred != (((char *) rt) + sizeof(IndexTupleData)))
-               pfree(tmpentry.pred);
-       if (rt != newtup)
-               pfree(newtup);
+
+       gistwritebuffer(r, p, itup, len, FirstOffsetNumber);
        WriteBuffer(b);
 }
 
@@ -1004,7 +1454,7 @@ GISTInitBuffer(Buffer b, uint32 f)
        pageSize = BufferGetPageSize(b);
 
        page = BufferGetPage(b);
-       MemSet(page, 0, (int) pageSize);
+
        PageInit(page, pageSize, sizeof(GISTPageOpaqueData));
 
        opaque = (GISTPageOpaque) PageGetSpecialPointer(page);
@@ -1021,57 +1471,62 @@ gistchoose(Relation r, Page p, IndexTuple it,   /* it has compressed entry */
 {
        OffsetNumber maxoff;
        OffsetNumber i;
-       char       *id;
-       char       *datum;
+       Datum           datum;
        float           usize;
        OffsetNumber which;
-       float           which_grow;
+       float           sum_grow,
+                               which_grow[INDEX_MAX_KEYS];
        GISTENTRY       entry,
-                               identry;
-       int                     size,
-                               idsize;
+                               identry[INDEX_MAX_KEYS];
+       bool            IsNull,
+                               decompvec[INDEX_MAX_KEYS],
+                               isnull[INDEX_MAX_KEYS];
+       int                     j;
 
-       idsize = IndexTupleSize(it) - sizeof(IndexTupleData);
-       id = ((char *) it) + sizeof(IndexTupleData);
        maxoff = PageGetMaxOffsetNumber(p);
-       which_grow = -1.0;
+       *which_grow = -1.0;
        which = -1;
+       sum_grow = 1;
+       gistDeCompressAtt(giststate, r,
+                                         it, (Page) NULL, (OffsetNumber) 0,
+                                         identry, decompvec, isnull);
 
-       gistdentryinit(giststate, &identry, id, (Relation) NULL, (Page) NULL,
-                                  (OffsetNumber) 0, idsize, FALSE);
-
-       for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
+       for (i = FirstOffsetNumber; i <= maxoff && sum_grow; i = OffsetNumberNext(i))
        {
-               datum = (char *) PageGetItem(p, PageGetItemId(p, i));
-               size = IndexTupleSize(datum) - sizeof(IndexTupleData);
-               datum += sizeof(IndexTupleData);
-               gistdentryinit(giststate, &entry, datum, r, p, i, size, FALSE);
-               FunctionCall3(&giststate->penaltyFn,
-                                         PointerGetDatum(&entry),
-                                         PointerGetDatum(&identry),
-                                         PointerGetDatum(&usize));
-               if (which_grow < 0 || usize < which_grow)
+               IndexTuple      itup = (IndexTuple) PageGetItem(p, PageGetItemId(p, i));
+
+               sum_grow = 0;
+               for (j = 0; j < r->rd_att->natts; j++)
                {
-                       which = i;
-                       which_grow = usize;
-                       if (which_grow == 0)
+                       datum = index_getattr(itup, j + 1, giststate->tupdesc, &IsNull);
+                       gistdentryinit(giststate, j, &entry, datum, r, p, i, ATTSIZE(datum, giststate->tupdesc, j + 1, IsNull), FALSE, IsNull);
+                       gistpenalty(giststate, j, &entry, IsNull, &identry[j], isnull[j], &usize);
+
+                       if ((!isAttByVal(giststate, j)) && entry.key != datum)
+                               pfree(DatumGetPointer(entry.key));
+
+                       if (which_grow[j] < 0 || usize < which_grow[j])
+                       {
+                               which = i;
+                               which_grow[j] = usize;
+                               if (j < r->rd_att->natts - 1 && i == FirstOffsetNumber)
+                                       which_grow[j + 1] = -1;
+                               sum_grow += which_grow[j];
+                       }
+                       else if (which_grow[j] == usize)
+                               sum_grow += usize;
+                       else
+                       {
+                               sum_grow = 1;
                                break;
+                       }
                }
-               if (entry.pred != datum)
-                       pfree(entry.pred);
        }
-       if (identry.pred != id)
-               pfree(identry.pred);
 
+       gistFreeAtt(r, identry, decompvec);
        return which;
 }
 
-static int
-gistnospace(Page p, IndexTuple it)
-{
-       return PageGetFreeSpace(p) < IndexTupleSize(it);
-}
-
 void
 gistfreestack(GISTSTACK *s)
 {
@@ -1087,29 +1542,31 @@ gistfreestack(GISTSTACK *s)
 
 
 /*
-** remove an entry from a page
-*/
-Datum
-gistdelete(PG_FUNCTION_ARGS)
+ * Retail deletion of a single tuple.
+ *
+ * NB: this is no longer called externally, but is still needed by
+ * gistlayerinsert().  That dependency will have to be fixed if GIST
+ * is ever going to allow concurrent insertions.
+ */
+static void
+gistdelete(Relation r, ItemPointer tid)
 {
-       Relation                r = (Relation) PG_GETARG_POINTER(0);
-       ItemPointer             tid = (ItemPointer) PG_GETARG_POINTER(1);
        BlockNumber blkno;
        OffsetNumber offnum;
        Buffer          buf;
        Page            page;
 
        /*
-        * Notes in ExecUtils:ExecOpenIndices() Also note that only vacuum
-        * deletes index tuples now...
-        *
-        * RelationSetLockForWrite(r);
+        * Since GIST is not marked "amconcurrent" in pg_am, caller should
+        * have acquired exclusive lock on index relation.      We need no locking
+        * here.
         */
 
        blkno = ItemPointerGetBlockNumber(tid);
        offnum = ItemPointerGetOffsetNumber(tid);
 
        /* adjust any scans that will be affected by this deletion */
+       /* NB: this works only for scans in *this* backend! */
        gistadjscans(r, GISTOP_DEL, blkno, offnum);
 
        /* delete the index tuple */
@@ -1119,84 +1576,160 @@ gistdelete(PG_FUNCTION_ARGS)
        PageIndexTupleDelete(page, offnum);
 
        WriteBuffer(buf);
+}
 
-       PG_RETURN_VOID();
+/*
+ * Bulk deletion of all index entries pointing to a set of heap tuples.
+ * The set of target tuples is specified via a callback routine that tells
+ * whether any given heap tuple (identified by ItemPointer) is being deleted.
+ *
+ * Result: a palloc'd struct containing statistical info for VACUUM displays.
+ */
+Datum
+gistbulkdelete(PG_FUNCTION_ARGS)
+{
+       Relation        rel = (Relation) PG_GETARG_POINTER(0);
+       IndexBulkDeleteCallback callback = (IndexBulkDeleteCallback) PG_GETARG_POINTER(1);
+       void       *callback_state = (void *) PG_GETARG_POINTER(2);
+       IndexBulkDeleteResult *result;
+       BlockNumber num_pages;
+       double          tuples_removed;
+       double          num_index_tuples;
+       IndexScanDesc iscan;
+
+       tuples_removed = 0;
+       num_index_tuples = 0;
+
+       /*
+        * Since GIST is not marked "amconcurrent" in pg_am, caller should
+        * have acquired exclusive lock on index relation.      We need no locking
+        * here.
+        */
+
+       /*
+        * XXX generic implementation --- should be improved!
+        */
+
+       /* walk through the entire index */
+       iscan = index_beginscan(NULL, rel, SnapshotAny, 0, (ScanKey) NULL);
+       /* including killed tuples */
+       iscan->ignore_killed_tuples = false;
+
+       while (index_getnext_indexitem(iscan, ForwardScanDirection))
+       {
+               if (callback(&iscan->xs_ctup.t_self, callback_state))
+               {
+                       ItemPointerData indextup = iscan->currentItemData;
+                       BlockNumber blkno;
+                       OffsetNumber offnum;
+                       Buffer          buf;
+                       Page            page;
+
+                       blkno = ItemPointerGetBlockNumber(&indextup);
+                       offnum = ItemPointerGetOffsetNumber(&indextup);
+
+                       /* adjust any scans that will be affected by this deletion */
+                       gistadjscans(rel, GISTOP_DEL, blkno, offnum);
+
+                       /* delete the index tuple */
+                       buf = ReadBuffer(rel, blkno);
+                       page = BufferGetPage(buf);
+
+                       PageIndexTupleDelete(page, offnum);
+
+                       WriteBuffer(buf);
+
+                       tuples_removed += 1;
+               }
+               else
+                       num_index_tuples += 1;
+       }
+
+       index_endscan(iscan);
+
+       /* return statistics */
+       num_pages = RelationGetNumberOfBlocks(rel);
+
+       result = (IndexBulkDeleteResult *) palloc(sizeof(IndexBulkDeleteResult));
+       result->num_pages = num_pages;
+       result->tuples_removed = tuples_removed;
+       result->num_index_tuples = num_index_tuples;
+
+       PG_RETURN_POINTER(result);
 }
 
+
 void
 initGISTstate(GISTSTATE *giststate, Relation index)
 {
-       RegProcedure consistent_proc,
-                               union_proc,
-                               compress_proc,
-                               decompress_proc;
-       RegProcedure penalty_proc,
-                               picksplit_proc,
-                               equal_proc;
-       HeapTuple       htup;
-       Form_pg_index itupform;
-
-       consistent_proc = index_getprocid(index, 1, GIST_CONSISTENT_PROC);
-       union_proc = index_getprocid(index, 1, GIST_UNION_PROC);
-       compress_proc = index_getprocid(index, 1, GIST_COMPRESS_PROC);
-       decompress_proc = index_getprocid(index, 1, GIST_DECOMPRESS_PROC);
-       penalty_proc = index_getprocid(index, 1, GIST_PENALTY_PROC);
-       picksplit_proc = index_getprocid(index, 1, GIST_PICKSPLIT_PROC);
-       equal_proc = index_getprocid(index, 1, GIST_EQUAL_PROC);
-       fmgr_info(consistent_proc, &giststate->consistentFn);
-       fmgr_info(union_proc, &giststate->unionFn);
-       fmgr_info(compress_proc, &giststate->compressFn);
-       fmgr_info(decompress_proc, &giststate->decompressFn);
-       fmgr_info(penalty_proc, &giststate->penaltyFn);
-       fmgr_info(picksplit_proc, &giststate->picksplitFn);
-       fmgr_info(equal_proc, &giststate->equalFn);
-
-       /* see if key type is different from type of attribute being indexed */
-       htup = SearchSysCacheTuple(INDEXRELID,
-                                                          ObjectIdGetDatum(RelationGetRelid(index)),
-                                                          0, 0, 0);
-       itupform = (Form_pg_index) GETSTRUCT(htup);
-       if (!HeapTupleIsValid(htup))
-               elog(ERROR, "initGISTstate: index %u not found",
-                        RelationGetRelid(index));
-       giststate->haskeytype = itupform->indhaskeytype;
-       if (giststate->haskeytype)
+       int                     i;
+
+       if (index->rd_att->natts > INDEX_MAX_KEYS)
+               elog(ERROR, "initGISTstate: numberOfAttributes %d > %d",
+                        index->rd_att->natts, INDEX_MAX_KEYS);
+
+       giststate->tupdesc = index->rd_att;
+
+       for (i = 0; i < index->rd_att->natts; i++)
        {
-               /* key type is different -- is it byval? */
-               htup = SearchSysCacheTuple(ATTNUM,
-                                                                  ObjectIdGetDatum(itupform->indexrelid),
-                                                                  UInt16GetDatum(FirstOffsetNumber),
-                                                                  0, 0);
-               if (!HeapTupleIsValid(htup))
-               {
-                       elog(ERROR, "initGISTstate: no attribute tuple %u %d",
-                                itupform->indexrelid, FirstOffsetNumber);
-                       return;
-               }
-               giststate->keytypbyval = (((Form_pg_attribute) htup)->attbyval);
+               fmgr_info_copy(&(giststate->consistentFn[i]),
+                                  index_getprocinfo(index, i + 1, GIST_CONSISTENT_PROC),
+                                          CurrentMemoryContext);
+               fmgr_info_copy(&(giststate->unionFn[i]),
+                                          index_getprocinfo(index, i + 1, GIST_UNION_PROC),
+                                          CurrentMemoryContext);
+               fmgr_info_copy(&(giststate->compressFn[i]),
+                                        index_getprocinfo(index, i + 1, GIST_COMPRESS_PROC),
+                                          CurrentMemoryContext);
+               fmgr_info_copy(&(giststate->decompressFn[i]),
+                                  index_getprocinfo(index, i + 1, GIST_DECOMPRESS_PROC),
+                                          CurrentMemoryContext);
+               fmgr_info_copy(&(giststate->penaltyFn[i]),
+                                          index_getprocinfo(index, i + 1, GIST_PENALTY_PROC),
+                                          CurrentMemoryContext);
+               fmgr_info_copy(&(giststate->picksplitFn[i]),
+                                       index_getprocinfo(index, i + 1, GIST_PICKSPLIT_PROC),
+                                          CurrentMemoryContext);
+               fmgr_info_copy(&(giststate->equalFn[i]),
+                                          index_getprocinfo(index, i + 1, GIST_EQUAL_PROC),
+                                          CurrentMemoryContext);
        }
-       else
-               giststate->keytypbyval = FALSE;
-       return;
 }
 
+void
+freeGISTstate(GISTSTATE *giststate)
+{
+       /* no work */
+}
 
+#ifdef GIST_PAGEADDITEM
 /*
 ** Given an IndexTuple to be inserted on a page, this routine replaces
 ** the key with another key, which may involve generating a new IndexTuple
-** if the sizes don't match
+** if the sizes don't match or if the null status changes.
+**
+** XXX this only works for a single-column index tuple!
 */
 static IndexTuple
 gist_tuple_replacekey(Relation r, GISTENTRY entry, IndexTuple t)
 {
-       char       *datum = (((char *) t) + sizeof(IndexTupleData));
+       bool            IsNull;
+       Datum           datum = index_getattr(t, 1, r->rd_att, &IsNull);
 
-       /* if new entry fits in index tuple, copy it in */
-       if ((Size) entry.bytes < IndexTupleSize(t) - sizeof(IndexTupleData))
+       /*
+        * If new entry fits in index tuple, copy it in.  To avoid worrying
+        * about null-value bitmask, pass it off to the general
+        * index_formtuple routine if either the previous or new value is
+        * NULL.
+        */
+       if (!IsNull && DatumGetPointer(entry.key) != NULL &&
+               (Size) entry.bytes <= ATTSIZE(datum, r, 1, IsNull))
        {
-               memcpy(datum, entry.pred, entry.bytes);
+               memcpy(DatumGetPointer(datum),
+                          DatumGetPointer(entry.key),
+                          entry.bytes);
                /* clear out old size */
-               t->t_info &= 0xe000;
+               t->t_info &= ~INDEX_SIZE_MASK;
                /* or in new size */
                t->t_info |= MAXALIGN(entry.bytes + sizeof(IndexTupleData));
 
@@ -1207,140 +1740,228 @@ gist_tuple_replacekey(Relation r, GISTENTRY entry, IndexTuple t)
                /* generate a new index tuple for the compressed entry */
                TupleDesc       tupDesc = r->rd_att;
                IndexTuple      newtup;
-               char       *isnull;
-               int                     blank;
+               char            isnull;
 
-               isnull = (char *) palloc(r->rd_rel->relnatts);
-               for (blank = 0; blank < r->rd_rel->relnatts; blank++)
-                       isnull[blank] = ' ';
+               isnull = DatumGetPointer(entry.key) != NULL ? ' ' : 'n';
                newtup = (IndexTuple) index_formtuple(tupDesc,
-                                                                                         (Datum *) &(entry.pred),
-                                                                                         isnull);
+                                                                                         &(entry.key),
+                                                                                         &isnull);
                newtup->t_tid = t->t_tid;
-               pfree(isnull);
                return newtup;
        }
 }
-
+#endif
 
 /*
-** initialize a GiST entry with a decompressed version of pred
+** initialize a GiST entry with a decompressed version of key
 */
 void
-gistdentryinit(GISTSTATE *giststate, GISTENTRY *e, char *pr, Relation r,
-                          Page pg, OffsetNumber o, int b, bool l)
+gistdentryinit(GISTSTATE *giststate, int nkey, GISTENTRY *e,
+                          Datum k, Relation r, Page pg, OffsetNumber o,
+                          int b, bool l, bool isNull)
 {
-       GISTENTRY  *dep;
-
-       gistentryinit(*e, pr, r, pg, o, b, l);
-       if (giststate->haskeytype)
+       if (b && !isNull)
        {
+               GISTENTRY  *dep;
+
+               gistentryinit(*e, k, r, pg, o, b, l);
                dep = (GISTENTRY *)
-                       DatumGetPointer(FunctionCall1(&giststate->decompressFn,
+                       DatumGetPointer(FunctionCall1(&giststate->decompressFn[nkey],
                                                                                  PointerGetDatum(e)));
-               gistentryinit(*e, dep->pred, dep->rel, dep->page, dep->offset, dep->bytes,
-                                         dep->leafkey);
+               /* decompressFn may just return the given pointer */
                if (dep != e)
+               {
+                       gistentryinit(*e, dep->key, dep->rel, dep->page, dep->offset,
+                                                 dep->bytes, dep->leafkey);
                        pfree(dep);
+               }
        }
+       else
+               gistentryinit(*e, (Datum) 0, r, pg, o, 0, l);
 }
 
 
 /*
-** initialize a GiST entry with a compressed version of pred
+** initialize a GiST entry with a compressed version of key
 */
 static void
-gistcentryinit(GISTSTATE *giststate, GISTENTRY *e, char *pr, Relation r,
-                          Page pg, OffsetNumber o, int b, bool l)
+gistcentryinit(GISTSTATE *giststate, int nkey,
+                          GISTENTRY *e, Datum k, Relation r,
+                          Page pg, OffsetNumber o, int b, bool l, bool isNull)
 {
-       GISTENTRY  *cep;
-
-       gistentryinit(*e, pr, r, pg, o, b, l);
-       if (giststate->haskeytype)
+       if (!isNull)
        {
+               GISTENTRY  *cep;
+
+               gistentryinit(*e, k, r, pg, o, b, l);
                cep = (GISTENTRY *)
-                       DatumGetPointer(FunctionCall1(&giststate->compressFn,
+                       DatumGetPointer(FunctionCall1(&giststate->compressFn[nkey],
                                                                                  PointerGetDatum(e)));
-               gistentryinit(*e, cep->pred, cep->rel, cep->page, cep->offset, cep->bytes,
-                                         cep->leafkey);
+               /* compressFn may just return the given pointer */
                if (cep != e)
+               {
+                       gistentryinit(*e, cep->key, cep->rel, cep->page, cep->offset,
+                                                 cep->bytes, cep->leafkey);
                        pfree(cep);
+               }
        }
+       else
+               gistentryinit(*e, (Datum) 0, r, pg, o, 0, l);
 }
 
+static IndexTuple
+gistFormTuple(GISTSTATE *giststate, Relation r,
+                         Datum attdata[], int datumsize[], bool isnull[])
+{
+       IndexTuple      tup;
+       char            isnullchar[INDEX_MAX_KEYS];
+       bool            whatfree[INDEX_MAX_KEYS];
+       GISTENTRY       centry[INDEX_MAX_KEYS];
+       Datum           compatt[INDEX_MAX_KEYS];
+       int                     j;
+
+       for (j = 0; j < r->rd_att->natts; j++)
+       {
+               if (isnull[j])
+               {
+                       isnullchar[j] = 'n';
+                       compatt[j] = (Datum) 0;
+                       whatfree[j] = FALSE;
+               }
+               else
+               {
+                       gistcentryinit(giststate, j, &centry[j], attdata[j],
+                                          (Relation) NULL, (Page) NULL, (OffsetNumber) NULL,
+                                                  datumsize[j], FALSE, FALSE);
+                       isnullchar[j] = ' ';
+                       compatt[j] = centry[j].key;
+                       if (!isAttByVal(giststate, j))
+                       {
+                               whatfree[j] = TRUE;
+                               if (centry[j].key != attdata[j])
+                                       pfree(DatumGetPointer(attdata[j]));
+                       }
+                       else
+                               whatfree[j] = FALSE;
+               }
+       }
 
+       tup = (IndexTuple) index_formtuple(giststate->tupdesc, compatt, isnullchar);
+       for (j = 0; j < r->rd_att->natts; j++)
+               if (whatfree[j])
+                       pfree(DatumGetPointer(compatt[j]));
 
-#ifdef GISTDEBUG
+       return tup;
+}
 
-/*
-** sloppy debugging support routine, requires recompilation with appropriate
-** "out" method for the index keys.  Could be fixed to find that info
-** in the catalogs...
-*/
-void
-_gistdump(Relation r)
+static void
+gistDeCompressAtt(GISTSTATE *giststate, Relation r, IndexTuple tuple, Page p,
+       OffsetNumber o, GISTENTRY attdata[], bool decompvec[], bool isnull[])
 {
-       Buffer          buf;
-       Page            page;
-       OffsetNumber offnum,
-                               maxoff;
-       BlockNumber blkno;
-       BlockNumber nblocks;
-       GISTPageOpaque po;
-       IndexTuple      itup;
-       BlockNumber itblkno;
-       OffsetNumber itoffno;
-       char       *datum;
-       char       *itkey;
+       int                     i;
+       Datum           datum;
 
-       nblocks = RelationGetNumberOfBlocks(r);
-       for (blkno = 0; blkno < nblocks; blkno++)
+       for (i = 0; i < r->rd_att->natts; i++)
        {
-               buf = ReadBuffer(r, blkno);
-               page = BufferGetPage(buf);
-               po = (GISTPageOpaque) PageGetSpecialPointer(page);
-               maxoff = PageGetMaxOffsetNumber(page);
-               printf("Page %d maxoff %d <%s>\n", blkno, maxoff,
-                          (po->flags & F_LEAF ? "LEAF" : "INTERNAL"));
-
-               if (PageIsEmpty(page))
+               datum = index_getattr(tuple, i + 1, giststate->tupdesc, &isnull[i]);
+               gistdentryinit(giststate, i, &attdata[i],
+                                          datum, r, p, o,
+                                          ATTSIZE(datum, giststate->tupdesc, i + 1, isnull[i]), FALSE, isnull[i]);
+               if (isAttByVal(giststate, i))
+                       decompvec[i] = FALSE;
+               else
                {
-                       ReleaseBuffer(buf);
-                       continue;
+                       if (attdata[i].key == datum || isnull[i])
+                               decompvec[i] = FALSE;
+                       else
+                               decompvec[i] = TRUE;
                }
+       }
+}
 
-               for (offnum = FirstOffsetNumber;
-                        offnum <= maxoff;
-                        offnum = OffsetNumberNext(offnum))
-               {
-                       itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum));
-                       itblkno = ItemPointerGetBlockNumber(&(itup->t_tid));
-                       itoffno = ItemPointerGetOffsetNumber(&(itup->t_tid));
-                       datum = ((char *) itup);
-                       datum += sizeof(IndexTupleData);
-                       /* get out function for type of key, and out it! */
-                       itkey = (char *) int_range_out((INTRANGE *) datum);
-                       /* itkey = " unable to print"; */
-                       printf("\t[%d] size %d heap <%d,%d> key:%s\n",
-                                  offnum, IndexTupleSize(itup), itblkno, itoffno, itkey);
-                       pfree(itkey);
-               }
+static void
+gistFreeAtt(Relation r, GISTENTRY attdata[], bool decompvec[])
+{
+       int                     i;
 
-               ReleaseBuffer(buf);
-       }
+       for (i = 0; i < r->rd_att->natts; i++)
+               if (decompvec[i])
+                       pfree(DatumGetPointer(attdata[i].key));
+}
+
+static void
+gistpenalty(GISTSTATE *giststate, int attno,
+                       GISTENTRY *key1, bool isNull1,
+                       GISTENTRY *key2, bool isNull2, float *penalty)
+{
+       if (giststate->penaltyFn[attno].fn_strict && (isNull1 || isNull2))
+               *penalty = 0.0;
+       else
+               FunctionCall3(&giststate->penaltyFn[attno],
+                                         PointerGetDatum(key1),
+                                         PointerGetDatum(key2),
+                                         PointerGetDatum(penalty));
 }
 
-static char *
-int_range_out(INTRANGE *r)
+#ifdef GISTDEBUG
+static void
+gist_dumptree(Relation r, int level, BlockNumber blk, OffsetNumber coff)
 {
-       char       *result;
+       Buffer          buffer;
+       Page            page;
+       GISTPageOpaque opaque;
+       IndexTuple      which;
+       ItemId          iid;
+       OffsetNumber i,
+                               maxoff;
+       BlockNumber cblk;
+       char       *pred;
+
+       pred = (char *) palloc(sizeof(char) * level + 1);
+       MemSet(pred, '\t', level);
+       pred[level] = '\0';
+
+       buffer = ReadBuffer(r, blk);
+       page = (Page) BufferGetPage(buffer);
+       opaque = (GISTPageOpaque) PageGetSpecialPointer(page);
+
+       maxoff = PageGetMaxOffsetNumber(page);
+
+       elog(DEBUG3, "%sPage: %d %s blk: %d maxoff: %d free: %d", pred,
+                coff, (opaque->flags & F_LEAF) ? "LEAF" : "INTE", (int) blk,
+                (int) maxoff, PageGetFreeSpace(page));
 
-       if (r == NULL)
-               return NULL;
-       result = (char *) palloc(80);
-       snprintf(result, 80, "[%d,%d): %d", r->lower, r->upper, r->flag);
+       for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
+       {
+               iid = PageGetItemId(page, i);
+               which = (IndexTuple) PageGetItem(page, iid);
+               cblk = ItemPointerGetBlockNumber(&(which->t_tid));
+#ifdef PRINTTUPLE
+               elog(DEBUG3, "%s  Tuple. blk: %d size: %d", pred, (int) cblk,
+                        IndexTupleSize(which));
+#endif
+
+               if (!(opaque->flags & F_LEAF))
+                       gist_dumptree(r, level + 1, cblk, i);
+       }
+       ReleaseBuffer(buffer);
+       pfree(pred);
+}
+#endif   /* defined GISTDEBUG */
+
+void
+gist_redo(XLogRecPtr lsn, XLogRecord *record)
+{
+       elog(PANIC, "gist_redo: unimplemented");
+}
 
-       return result;
+void
+gist_undo(XLogRecPtr lsn, XLogRecord *record)
+{
+       elog(PANIC, "gist_undo: unimplemented");
 }
 
-#endif  /* defined GISTDEBUG */
+void
+gist_desc(char *buf, uint8 xl_info, char *rec)
+{
+}