OSDN Git Service

pgindent run.
[pg-rex/syncrep.git] / src / backend / access / gist / gist.c
index 3326cde..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.74 2001/05/14 21:53:16 momjian 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"
 
-#include "access/xlogutils.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 void gistbuildCallback(Relation index,
+                                 HeapTuple htup,
+                                 Datum *attdata,
+                                 char *nulls,
+                                 bool tupleIsAlive,
+                                 void *state);
 static void gistdoinsert(Relation r,
                         IndexTuple itup,
                         InsertIndexResult *res,
@@ -43,44 +87,63 @@ static OffsetNumber gistwritebuffer(Relation r,
                                Page page,
                                IndexTuple *itup,
                                int len,
-                               OffsetNumber off,
-                               GISTSTATE *giststate);
+                               OffsetNumber off);
 static int gistnospace(Page page,
                        IndexTuple *itvec, int len);
-static IndexTuple *gistreadbuffer(Relation r,
-                          Buffer buffer, 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(GISTSTATE *giststate, Relation r,
+static void gistnewroot(Relation r,
                        IndexTuple *itup, int len);
 static void GISTInitBuffer(Buffer b, uint32 f);
 static OffsetNumber gistchoose(Relation r, Page p,
                   IndexTuple it,
                   GISTSTATE *giststate);
+static void gistdelete(Relation r, ItemPointer tid);
+
+#ifdef GIST_PAGEADDITEM
 static IndexTuple gist_tuple_replacekey(Relation r,
                                          GISTENTRY entry, IndexTuple t);
-static void gistcentryinit(GISTSTATE *giststate,
-                          GISTENTRY *e, char *pr,
+#endif
+static void gistcentryinit(GISTSTATE *giststate, int nkey,
+                          GISTENTRY *e, Datum k,
                           Relation r, Page pg,
-                          OffsetNumber o, int b, bool l);
+                          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
 
 /*
@@ -92,185 +155,36 @@ 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];
-       double          nhtups,
-                               nitups;
-       Node       *pred = indexInfo->ii_Predicate;
-
-#ifndef OMIT_PARTIAL_INDEX
-       TupleTable      tupleTable;
-       TupleTableSlot *slot;
-
-#endif
-       ExprContext *econtext;
-       GISTSTATE       giststate;
-       GISTENTRY       tmpcentry;
-       Buffer          buffer = InvalidBuffer;
-       bool       *compvec;
-       int                     i;
+       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));
+       if (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 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, false);
-       }
-       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.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 += 1.0;
+       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 += 1.0;
-                               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 += 1.0;
-
-               /*
-                * 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.
-                */
-
-               gistdoinsert(index, itup, NULL, &giststate);
-
-               for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
-                       if (compvec[i])
-                               pfree(DatumGetPointer(attdata[i]));
-
-               pfree(itup);
-       }
+       /* 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
@@ -290,16 +204,11 @@ gistbuild(PG_FUNCTION_ARGS)
 
                heap_close(heap, NoLock);
                index_close(index);
-               UpdateStats(hrelid, nhtups);
-               UpdateStats(irelid, nitups);
-               if (oldPred != NULL)
-               {
-                       if (nitups == nhtups)
-                               pred = NULL;
-                       UpdateIndexPredicate(irelid, oldPred, pred);
-               }
+               UpdateStats(hrelid, reltuples);
+               UpdateStats(irelid, buildstate.indtuples);
        }
 
+       freeGISTstate(&buildstate.giststate);
 #ifdef GISTDEBUG
        gist_dumptree(index, 0, GISTP_ROOT, 0);
 #endif
@@ -308,6 +217,71 @@ gistbuild(PG_FUNCTION_ARGS)
 }
 
 /*
+ * 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')
+               {
+                       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;
+               }
+       }
+
+       /* 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);
+}
+
+/*
  *     gistinsert -- wrapper for GiST tuple insertion.
  *
  *       This is the public interface routine for tuple insertion in GiSTs.
@@ -323,56 +297,72 @@ gistinsert(PG_FUNCTION_ARGS)
 
 #ifdef NOT_USED
        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);
+
        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,
@@ -387,16 +377,20 @@ 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, tmpcentry, itup);
        retval = PageAddItem(page, (Item) *newtup, IndexTupleSize(*newtup),
@@ -405,11 +399,13 @@ gistPageAddItem(GISTSTATE *giststate,
                elog(ERROR, "gist: failed to add index item to %s",
                         RelationGetRelationName(r));
        /* be tidy */
-       if (tmpcentry.pred && tmpcentry.pred != dentry->pred
-               && tmpcentry.pred != (((char *) itup) + sizeof(IndexTupleData)))
-               pfree(tmpcentry.pred);
+       if (DatumGetPointer(tmpcentry.key) != NULL &&
+               tmpcentry.key != dentry->key &&
+               tmpcentry.key != datum)
+               pfree(DatumGetPointer(tmpcentry.key));
        return (retval);
 }
+#endif
 
 static void
 gistdoinsert(Relation r,
@@ -428,7 +424,7 @@ gistdoinsert(Relation r,
 
        ret = gistlayerinsert(r, GISTP_ROOT, &instup, &len, res, giststate);
        if (ret & SPLITED)
-               gistnewroot(giststate, r, instup, len);
+               gistnewroot(r, instup, len);
 
        for (i = 0; i < len; i++)
                pfree(instup[i]);
@@ -455,7 +451,7 @@ gistlayerinsert(Relation r, BlockNumber blkno,
        if (!(opaque->flags & F_LEAF))
        {
                /* internal page, so we must walk on tree */
-               /* len IS equial 1 */
+               /* len IS equal 1 */
                ItemId          iid;
                BlockNumber nblkno;
                ItemPointerData oldtid;
@@ -498,9 +494,15 @@ gistlayerinsert(Relation r, BlockNumber blkno,
 
                /* key is modified, so old version must be deleted */
                ItemPointerSet(&oldtid, blkno, child);
-               DirectFunctionCall2(gistdelete,
-                                                       PointerGetDatum(r),
-                                                       PointerGetDatum(&oldtid));
+               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);
        }
 
        ret = INSERTED;
@@ -508,18 +510,25 @@ gistlayerinsert(Relation r, BlockNumber blkno,
        if (gistnospace(page, (*itup), *len))
        {
                /* no space for insertion */
-               IndexTuple *itvec;
-               int                     tlen;
+               IndexTuple *itvec,
+                                  *newitup;
+               int                     tlen,
+                                       oldlen;
 
                ret |= SPLITED;
-               itvec = gistreadbuffer(r, buffer, &tlen);
+               itvec = gistreadbuffer(buffer, &tlen);
                itvec = gistjoinvector(itvec, &tlen, (*itup), *len);
-               pfree((*itup));
-               (*itup) = gistSplit(r, buffer, itvec, &tlen, giststate,
+               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
@@ -532,7 +541,7 @@ gistlayerinsert(Relation r, BlockNumber blkno,
                        FirstOffsetNumber
                        :
                        OffsetNumberNext(PageGetMaxOffsetNumber(page));
-               l = gistwritebuffer(r, page, (*itup), *len, off, giststate);
+               l = gistwritebuffer(r, page, (*itup), *len, off);
                WriteBuffer(buffer);
 
                /*
@@ -551,6 +560,8 @@ gistlayerinsert(Relation r, BlockNumber blkno,
                         */
                        IndexTuple      newtup = gistunion(r, (*itup), *len, giststate);
 
+                       ItemPointerSet(&(newtup->t_tid), blkno, 1);
+
                        for (i = 0; i < *len; i++)
                                pfree((*itup)[i]);
                        (*itup)[0] = newtup;
@@ -566,23 +577,35 @@ gistlayerinsert(Relation r, BlockNumber blkno,
  */
 static OffsetNumber
 gistwritebuffer(Relation r, Page page, IndexTuple *itup,
-                               int len, OffsetNumber off, GISTSTATE *giststate)
+                               int len, OffsetNumber off)
 {
        OffsetNumber l = InvalidOffsetNumber;
        int                     i;
+
+#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 (tmpdentry.pred != (((char *) itup[i]) + sizeof(IndexTupleData)) && tmpdentry.pred)
-                       pfree(tmpdentry.pred);
+               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;
 }
@@ -593,11 +616,11 @@ gistwritebuffer(Relation r, Page page, IndexTuple *itup,
 static int
 gistnospace(Page page, IndexTuple *itvec, int len)
 {
-       int                     size = 0;
+       unsigned int size = 0;
        int                     i;
 
        for (i = 0; i < len; i++)
-               size += IndexTupleSize(itvec[i]) + 4;   /* ??? */
+               size += IndexTupleSize(itvec[i]) + sizeof(ItemIdData);
 
        return (PageGetFreeSpace(page) < size);
 }
@@ -606,18 +629,18 @@ gistnospace(Page page, IndexTuple *itvec, int len)
  * Read buffer into itup vector
  */
 static IndexTuple *
-gistreadbuffer(Relation r, Buffer buffer, int *len /* out */ )
+gistreadbuffer(Buffer buffer, int *len /* out */ )
 {
        OffsetNumber i,
                                maxoff;
        IndexTuple *itvec;
        Page            p = (Page) BufferGetPage(buffer);
 
-       *len = 0;
        maxoff = PageGetMaxOffsetNumber(p);
+       *len = maxoff;
        itvec = palloc(sizeof(IndexTuple) * maxoff);
        for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
-               itvec[(*len)++] = (IndexTuple) PageGetItem(p, PageGetItemId(p, i));
+               itvec[i - 1] = (IndexTuple) PageGetItem(p, PageGetItemId(p, i));
 
        return itvec;
 }
@@ -640,48 +663,102 @@ gistjoinvector(IndexTuple *itvec, int *len, IndexTuple *additvec, int addlen)
 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;
-       char       *datum;
+       Datum           datum;
        int                     datumsize,
-                               i;
-       GISTENTRY       centry;
-       char            isnull;
+                               i,
+                               j;
+       GISTENTRY       centry[INDEX_MAX_KEYS];
+       bool       *needfree;
        IndexTuple      newtup;
+       bool            IsNull;
+       int                     reallen;
 
-       evec = (bytea *) palloc(len * sizeof(GISTENTRY) + VARHDRSZ);
-       VARATT_SIZEP(evec) = len * sizeof(GISTENTRY) + VARHDRSZ;
+       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 (i = 0; i < len; i++)
-               gistdentryinit(giststate, &((GISTENTRY *) VARDATA(evec))[i],
-                                          (char *) itvec[i] + sizeof(IndexTupleData),
+       for (j = 0; j < r->rd_att->natts; j++)
+       {
+               reallen = 0;
+               for (i = 0; i < len; i++)
+               {
+                       datum = index_getattr(itvec[i], j + 1, giststate->tupdesc, &IsNull);
+                       if (IsNull)
+                               continue;
+
+                       gistdentryinit(giststate, j,
+                                                  &((GISTENTRY *) VARDATA(evec))[reallen],
+                                                  datum,
                                           (Relation) NULL, (Page) NULL, (OffsetNumber) NULL,
-                                          IndexTupleSize((IndexTuple) itvec[i]) - sizeof(IndexTupleData), FALSE);
+                                                  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++;
+               }
 
-       datum = (char *)
-               DatumGetPointer(FunctionCall2(&giststate->unionFn,
-                                                                         PointerGetDatum(evec),
-                                                                         PointerGetDatum(&datumsize)));
+               if (reallen == 0)
+               {
+                       attr[j] = (Datum) 0;
+                       isnull[j] = 'n';
+                       whatfree[j] = FALSE;
+               }
+               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);
 
-       for (i = 0; i < len; i++)
-               if (((GISTENTRY *) VARDATA(evec))[i].pred &&
-                       ((GISTENTRY *) VARDATA(evec))[i].pred !=
-                       ((char *) (itvec[i]) + sizeof(IndexTupleData)))
-                       pfree(((GISTENTRY *) VARDATA(evec))[i].pred);
+                       }
+                       else
+                               VARATT_SIZEP(evec) = reallen * sizeof(GISTENTRY) + VARHDRSZ;
+                       datum = FunctionCall2(&giststate->unionFn[j],
+                                                                 PointerGetDatum(evec),
+                                                                 PointerGetDatum(&datumsize));
 
-       pfree(evec);
+                       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;
+               }
+       }
 
-       gistcentryinit(giststate, &centry, datum,
-                                  (Relation) NULL, (Page) NULL, (OffsetNumber) NULL,
-                                  datumsize, FALSE);
+       pfree(storage);                         /* pfree(evec); */
+       pfree(needfree);
 
-       isnull = (centry.pred) ? ' ' : 'n';
-       newtup = (IndexTuple) index_formtuple(r->rd_att, (Datum *) &centry.pred, &isnull);
-       if (centry.pred != datum)
-               pfree(datum);
+       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 newtup;
 }
 
+
 /*
  * Forms union of oldtup and addtup, if union == oldtup then return NULL
  */
@@ -689,71 +766,449 @@ static IndexTuple
 gistgetadjusted(Relation r, IndexTuple oldtup, IndexTuple addtup, GISTSTATE *giststate)
 {
        bytea      *evec;
-       char       *datum;
+       Datum           datum;
        int                     datumsize;
-       bool            result;
-       char            isnull;
-       GISTENTRY       centry,
+       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;
+       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;
 
-       evec = (bytea *) palloc(2 * sizeof(GISTENTRY) + VARHDRSZ);
+       /* 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;
-
-       gistdentryinit(giststate, &((GISTENTRY *) VARDATA(evec))[0],
-                          (char *) oldtup + sizeof(IndexTupleData), (Relation) NULL,
-                                  (Page) NULL, (OffsetNumber) 0,
-       IndexTupleSize((IndexTuple) oldtup) - sizeof(IndexTupleData), FALSE);
        ev0p = &((GISTENTRY *) VARDATA(evec))[0];
-
-       gistdentryinit(giststate, &((GISTENTRY *) VARDATA(evec))[1],
-                          (char *) addtup + sizeof(IndexTupleData), (Relation) NULL,
-                                  (Page) NULL, (OffsetNumber) 0,
-       IndexTupleSize((IndexTuple) addtup) - sizeof(IndexTupleData), FALSE);
        ev1p = &((GISTENTRY *) VARDATA(evec))[1];
 
-       datum = (char *)
-               DatumGetPointer(FunctionCall2(&giststate->unionFn,
-                                                                         PointerGetDatum(evec),
-                                                                         PointerGetDatum(&datumsize)));
+       gistDeCompressAtt(giststate, r, oldtup, (Page) NULL,
+                                         (OffsetNumber) 0, oldatt, olddec, oldisnull);
 
-       if (!(ev0p->pred && ev1p->pred))
-               result = (ev0p->pred == NULL && ev1p->pred == NULL);
-       else
+       gistDeCompressAtt(giststate, r, addtup, (Page) NULL,
+                                         (OffsetNumber) 0, addatt, adddec, addisnull);
+
+
+       for (j = 0; j < r->rd_att->natts; j++)
        {
-               FunctionCall3(&giststate->equalFn,
-                                         PointerGetDatum(ev0p->pred),
-                                         PointerGetDatum(datum),
-                                         PointerGetDatum(&result));
+               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
+                               );
+
+                       datum = FunctionCall2(&giststate->unionFn[j],
+                                                                 PointerGetDatum(evec),
+                                                                 PointerGetDatum(&datumsize));
+
+                       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 (result)
+       if (neednew)
        {
-               /* not need to update key */
-               pfree(datum);
+               /* need to update key */
+               newtup = (IndexTuple) index_formtuple(giststate->tupdesc, attr, isnull);
+               newtup->t_tid = oldtup->t_tid;
        }
-       else
+
+       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++)
        {
-               gistcentryinit(giststate, &centry, datum, ev0p->rel, ev0p->page,
-                                          ev0p->offset, datumsize, FALSE);
+               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;
+               }
 
-               isnull = (centry.pred) ? ' ' : 'n';
-               newtup = (IndexTuple) index_formtuple(r->rd_att, (Datum *) &centry.pred, &isnull);
-               newtup->t_tid = oldtup->t_tid;
-               if (centry.pred != datum)
-                       pfree(datum);
+               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));
+                               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);
        }
+}
 
-       if (ev0p->pred &&
-               ev0p->pred != (char *) oldtup + sizeof(IndexTupleData))
-               pfree(ev0p->pred);
-       if (ev1p->pred &&
-               ev1p->pred != (char *) addtup + sizeof(IndexTupleData))
-               pfree(ev1p->pred);
-       pfree(evec);
+/*
+ * 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;
 
-       return newtup;
+       /*
+        * first key is always not null (see gistinsert), so we may not check
+        * for nulls
+        */
+
+       for (i = 0; i < spl->spl_nleft; i++)
+       {
+               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)
+               {
+
+                       /* force last in group */
+                       rpenalty = 1.0;
+                       lpenalty = (v->spl_grpflag[v->spl_idgrp[i + 1]] & LEFT_ADDED) ? 2.0 : 0.0;
+               }
+               else
+               {
+                       /* 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;
+                       }
+               }
+               /* add */
+               if (lpenalty < rpenalty)
+               {
+                       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;
+                               }
+                       }
+
+               }
+               gistFreeAtt(r, identry, decfree);
+       }
+       pfree(storage);                         /* pfree(evec); */
 }
 
 /*
@@ -772,28 +1227,26 @@ gistSplit(Relation r,
                                rightbuf;
        Page            left,
                                right;
-       OffsetNumber *spl_left,
-                          *spl_right;
        IndexTuple *lvectup,
                           *rvectup,
                           *newtup;
-       int                     leftoff,
-                               rightoff;
        BlockNumber lbknum,
                                rbknum;
        GISTPageOpaque opaque;
-       char            isnull;
        GIST_SPLITVEC v;
        bytea      *entryvec;
+       char       *storage;
        bool       *decompvec;
-       GISTENTRY       tmpentry;
        int                     i,
+                               j,
                                nlen;
+       int                     MaxGrpId = 1;
+       Datum           datum;
+       bool            IsNull;
 
        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
@@ -821,54 +1274,88 @@ gistSplit(Relation r,
        right = (Page) BufferGetPage(rightbuf);
 
        /* generate the item array */
-       entryvec = (bytea *) palloc(VARHDRSZ + (*len + 1) * sizeof(GISTENTRY));
-       decompvec = (bool *) palloc(VARHDRSZ + (*len + 1) * sizeof(bool));
+       /* 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++)
        {
-               gistdentryinit(giststate, &((GISTENTRY *) VARDATA(entryvec))[i],
-                                          (((char *) itup[i - 1]) + sizeof(IndexTupleData)),
-                                          r, p, i,
-                       IndexTupleSize(itup[i - 1]) - sizeof(IndexTupleData), FALSE);
-               if ((char *) (((GISTENTRY *) VARDATA(entryvec))[i].pred)
-                       == (((char *) itup[i - 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;
        }
 
-       /* 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));
 
-       /* clean up the entry vector: its preds need to be deleted, too */
+       /* 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;
+
+       v.spl_lattr[0] = v.spl_ldatum;
+       v.spl_rattr[0] = v.spl_rdatum;
+       v.spl_lisnull[0] = false;
+       v.spl_risnull[0] = false;
+
+       /*
+        * 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);
+
+               /* form union of sub keys for each page (l,p) */
+               gistunionsubkey(r, giststate, itup, &v);
+
+               /*
+                * if possible, we insert equivalent tuples with control by
+                * penalty for a subkey(s)
+                */
+               if (MaxGrpId > 1)
+                       gistadjsubkey(r, itup, len, &v, giststate);
+
+               pfree(v.spl_idgrp);
+               pfree(v.spl_grpflag);
+               pfree(v.spl_ngrp);
+       }
+
+       /* clean up the entry vector: its keys need to be deleted, too */
        for (i = 1; i <= *len; i++)
-               if (decompvec[i] && ((GISTENTRY *) VARDATA(entryvec))[i].pred)
-                       pfree(((GISTENTRY *) VARDATA(entryvec))[i].pred);
-       pfree(entryvec);
+               if (decompvec[i])
+                       pfree(DatumGetPointer(((GISTENTRY *) VARDATA(entryvec))[i].key));
+       pfree(storage);                         /* pfree(entryvec); */
        pfree(decompvec);
 
-       spl_left = v.spl_left;
-       spl_right = v.spl_right;
-
        /* form left and right vector */
        lvectup = (IndexTuple *) palloc(sizeof(IndexTuple) * v.spl_nleft);
        rvectup = (IndexTuple *) palloc(sizeof(IndexTuple) * v.spl_nright);
-       leftoff = rightoff = 0;
-       for (i = 1; i <= *len; i++)
-       {
-               if (i == *(spl_left) || (i == *len && *(spl_left) != FirstOffsetNumber))
-               {
-                       lvectup[leftoff++] = itup[i - 1];
-                       spl_left++;
-               }
-               else
-               {
-                       rvectup[rightoff++] = itup[i - 1];
-                       spl_right++;
-               }
-       }
+
+       for (i = 0; i < v.spl_nleft; i++)
+               lvectup[i] = itup[v.spl_left[i] - 1];
+
+       for (i = 0; i < v.spl_nright; i++)
+               rvectup[i] = itup[v.spl_right[i] - 1];
+
 
        /* write on disk (may be need another split) */
        if (gistnospace(right, rvectup, v.spl_nright))
@@ -877,30 +1364,27 @@ gistSplit(Relation r,
                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;
 
-               l = gistwritebuffer(r, right, rvectup, v.spl_nright, FirstOffsetNumber, giststate);
+               l = gistwritebuffer(r, right, rvectup, v.spl_nright, FirstOffsetNumber);
                WriteBuffer(rightbuf);
 
                if (res)
                        ItemPointerSet(&((*res)->pointerData), rbknum, l);
-               gistcentryinit(giststate, &tmpentry, v.spl_rdatum, (Relation) NULL,
-                                          (Page) NULL, (OffsetNumber) 0,
-                                          -1, FALSE);
-               if (v.spl_rdatum != tmpentry.pred)
-                       pfree(v.spl_rdatum);
-               v.spl_rdatum = tmpentry.pred;
 
                nlen = 1;
                newtup = (IndexTuple *) palloc(sizeof(IndexTuple) * 1);
-               isnull = (v.spl_rdatum) ? ' ' : 'n';
-               newtup[0] = (IndexTuple) index_formtuple(r->rd_att, (Datum *) &(v.spl_rdatum), &isnull);
+               newtup[0] = gistFormTuple(giststate, r, v.spl_rattr, v.spl_rattrsize, v.spl_risnull);
                ItemPointerSet(&(newtup[0]->t_tid), rbknum, 1);
        }
 
+
        if (gistnospace(left, lvectup, v.spl_nleft))
        {
                int                     llen = v.spl_nleft;
@@ -910,6 +1394,10 @@ gistSplit(Relation r,
                          (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);
        }
@@ -917,7 +1405,7 @@ gistSplit(Relation r,
        {
                OffsetNumber l;
 
-               l = gistwritebuffer(r, left, lvectup, v.spl_nleft, FirstOffsetNumber, giststate);
+               l = gistwritebuffer(r, left, lvectup, v.spl_nleft, FirstOffsetNumber);
                if (BufferGetBlockNumber(buffer) != GISTP_ROOT)
                        PageRestoreTempPage(left, p);
 
@@ -925,24 +1413,13 @@ gistSplit(Relation r,
 
                if (res)
                        ItemPointerSet(&((*res)->pointerData), lbknum, l);
-               gistcentryinit(giststate, &tmpentry, v.spl_ldatum, (Relation) NULL,
-                                          (Page) NULL, (OffsetNumber) 0,
-                                          -1, FALSE);
-               if (v.spl_ldatum != tmpentry.pred)
-                       pfree(v.spl_ldatum);
-               v.spl_ldatum = tmpentry.pred;
 
                nlen += 1;
                newtup = (IndexTuple *) repalloc((void *) newtup, sizeof(IndexTuple) * nlen);
-               isnull = (v.spl_ldatum) ? ' ' : 'n';
-               newtup[nlen - 1] = (IndexTuple) index_formtuple(r->rd_att, (Datum *) &(v.spl_ldatum), &isnull);
+               newtup[nlen - 1] = gistFormTuple(giststate, r, v.spl_lattr, v.spl_lattrsize, v.spl_lisnull);
                ItemPointerSet(&(newtup[nlen - 1]->t_tid), lbknum, 1);
        }
 
-
-       /* adjust active scans */
-       gistadjscans(r, GISTOP_SPLIT, BufferGetBlockNumber(buffer), FirstOffsetNumber);
-
        /* !!! pfree */
        pfree(rvectup);
        pfree(lvectup);
@@ -954,7 +1431,7 @@ gistSplit(Relation r,
 }
 
 static void
-gistnewroot(GISTSTATE *giststate, Relation r, IndexTuple *itup, int len)
+gistnewroot(Relation r, IndexTuple *itup, int len)
 {
        Buffer          b;
        Page            p;
@@ -963,7 +1440,7 @@ gistnewroot(GISTSTATE *giststate, Relation r, IndexTuple *itup, int len)
        GISTInitBuffer(b, 0);
        p = BufferGetPage(b);
 
-       gistwritebuffer(r, p, itup, len, FirstOffsetNumber, giststate);
+       gistwritebuffer(r, p, itup, len, FirstOffsetNumber);
        WriteBuffer(b);
 }
 
@@ -977,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);
@@ -994,48 +1471,59 @@ 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 && entry.pred != datum)
-                       pfree(entry.pred);
        }
-       if (identry.pred && identry.pred != id)
-               pfree(identry.pred);
 
+       gistFreeAtt(r, identry, decompvec);
        return which;
 }
 
@@ -1054,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 */
@@ -1086,82 +1576,158 @@ 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;
-       Oid                     indexrelid;
-
-       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 = SearchSysCache(INDEXRELID,
-                                                 ObjectIdGetDatum(RelationGetRelid(index)),
-                                                 0, 0, 0);
-       if (!HeapTupleIsValid(htup))
-               elog(ERROR, "initGISTstate: index %u not found",
-                        RelationGetRelid(index));
-       itupform = (Form_pg_index) GETSTRUCT(htup);
-       indexrelid = itupform->indexrelid;
-       ReleaseSysCache(htup);
-
-       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 = SearchSysCache(ATTNUM,
-                                                         ObjectIdGetDatum(indexrelid),
-                                                         UInt16GetDatum(FirstOffsetNumber),
-                                                         0, 0);
-               if (!HeapTupleIsValid(htup))
-                       elog(ERROR, "initGISTstate: no attribute tuple %u %d",
-                                indexrelid, FirstOffsetNumber);
-               giststate->keytypbyval = (((Form_pg_attribute) htup)->attbyval);
-               ReleaseSysCache(htup);
+               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;
 }
 
+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) || (Size) entry.bytes == 0)
+       /*
+        * 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 &= ~INDEX_SIZE_MASK;
                /* or in new size */
@@ -1176,61 +1742,167 @@ gist_tuple_replacekey(Relation r, GISTENTRY entry, IndexTuple t)
                IndexTuple      newtup;
                char            isnull;
 
-               isnull = (entry.pred) ? ' ' : 'n';
+               isnull = DatumGetPointer(entry.key) != NULL ? ' ' : 'n';
                newtup = (IndexTuple) index_formtuple(tupDesc,
-                                                                                         (Datum *) &(entry.pred),
+                                                                                         &(entry.key),
                                                                                          &isnull);
                newtup->t_tid = t->t_tid;
                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]));
+
+       return tup;
+}
+
+static void
+gistDeCompressAtt(GISTSTATE *giststate, Relation r, IndexTuple tuple, Page p,
+       OffsetNumber o, GISTENTRY attdata[], bool decompvec[], bool isnull[])
+{
+       int                     i;
+       Datum           datum;
+
+       for (i = 0; i < r->rd_att->natts; i++)
+       {
+               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
+               {
+                       if (attdata[i].key == datum || isnull[i])
+                               decompvec[i] = FALSE;
+                       else
+                               decompvec[i] = TRUE;
+               }
        }
 }
 
+static void
+gistFreeAtt(Relation r, GISTENTRY attdata[], bool decompvec[])
+{
+       int                     i;
+
+       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));
+}
+
 #ifdef GISTDEBUG
 static void
 gist_dumptree(Relation r, int level, BlockNumber blk, OffsetNumber coff)
@@ -1255,7 +1927,9 @@ gist_dumptree(Relation r, int level, BlockNumber blk, OffsetNumber coff)
 
        maxoff = PageGetMaxOffsetNumber(page);
 
-       elog(NOTICE, "%sPage: %d %s blk: %d maxoff: %d free: %d", pred, coff, (opaque->flags & F_LEAF) ? "LEAF" : "INTE", (int) blk, (int) maxoff, PageGetFreeSpace(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));
 
        for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
        {
@@ -1263,7 +1937,8 @@ gist_dumptree(Relation r, int level, BlockNumber blk, OffsetNumber coff)
                which = (IndexTuple) PageGetItem(page, iid);
                cblk = ItemPointerGetBlockNumber(&(which->t_tid));
 #ifdef PRINTTUPLE
-               elog(NOTICE, "%s  Tuple. blk: %d size: %d", pred, (int) cblk, IndexTupleSize(which));
+               elog(DEBUG3, "%s  Tuple. blk: %d size: %d", pred, (int) cblk,
+                        IndexTupleSize(which));
 #endif
 
                if (!(opaque->flags & F_LEAF))
@@ -1272,19 +1947,18 @@ gist_dumptree(Relation r, int level, BlockNumber blk, OffsetNumber coff)
        ReleaseBuffer(buffer);
        pfree(pred);
 }
-
-#endif  /* defined GISTDEBUG */
+#endif   /* defined GISTDEBUG */
 
 void
 gist_redo(XLogRecPtr lsn, XLogRecord *record)
 {
-       elog(STOP, "gist_redo: unimplemented");
+       elog(PANIC, "gist_redo: unimplemented");
 }
 
 void
 gist_undo(XLogRecPtr lsn, XLogRecord *record)
 {
-       elog(STOP, "gist_undo: unimplemented");
+       elog(PANIC, "gist_undo: unimplemented");
 }
 
 void