7887114e35aab38c0ddcdf2fce1792f241c355f8
[gevel.git] / gevel.c
1 #include "postgres.h"
2
3 #include "access/genam.h"
4 #include "access/gin.h"
5 #if PG_VERSION_NUM >= 90400
6 #include "utils/snapmgr.h"
7 #endif
8 #if PG_VERSION_NUM >= 90100
9 #include "access/gin_private.h"
10 #endif
11 #include "access/gist.h"
12 #include "access/gist_private.h"
13 #include "access/gistscan.h"
14 #if PG_VERSION_NUM >= 90200
15 #include "access/spgist_private.h"
16 #include "access/spgist.h"
17 #include "utils/datum.h"
18 #endif
19 #include "access/heapam.h"
20 #include "catalog/index.h"
21 #if PG_VERSION_NUM >= 90600
22 #include <catalog/pg_am.h>
23 #endif
24 #include "miscadmin.h"
25 #include "storage/lmgr.h"
26 #include "catalog/namespace.h"
27 #if PG_VERSION_NUM >= 80300
28 #include <tsearch/ts_utils.h>
29 #endif
30 #include <utils/tqual.h>
31 #include "utils/builtins.h"
32 #include "utils/lsyscache.h"
33 #include "utils/datum.h"
34 #include "utils/fmgroids.h"
35 #include <fmgr.h>
36 #include <funcapi.h>
37 #include <access/heapam.h>
38 #include <catalog/pg_type.h>
39 #include <access/relscan.h>
40
41
42 #define PAGESIZE        (BLCKSZ - MAXALIGN(sizeof(PageHeaderData) + sizeof(ItemIdData)))
43
44 #ifndef PG_NARGS
45 #define PG_NARGS() (fcinfo->nargs)
46 #endif
47
48 #if PG_VERSION_NUM >= 90600
49 #define ISNULL          true
50 #define ISNOTNULL       false
51 #define heap_formtuple  heap_form_tuple
52 #else
53 #define ISNULL          'n'
54 #define ISNOTNULL       ' '
55 #endif
56
57 static char
58 *t2c(text* in) {
59         char *out=palloc(VARSIZE(in));
60
61         memcpy(out, VARDATA(in), VARSIZE(in)-VARHDRSZ);
62         out[ VARSIZE(in)-VARHDRSZ ] ='\0';
63         return out;
64 }
65
66 typedef struct {
67         int maxlevel;
68         text    *txt;
69         char    *ptr;
70         int             len;
71 } IdxInfo;
72
73 static Relation checkOpenedRelation(Relation r, Oid PgAmOid);
74
75 #ifdef PG_MODULE_MAGIC
76 /* >= 8.2 */
77
78 PG_MODULE_MAGIC;
79
80 static Relation
81 gist_index_open(RangeVar *relvar) {
82 #if PG_VERSION_NUM < 90200
83         Oid relOid = RangeVarGetRelid(relvar, false);
84 #else
85         Oid relOid = RangeVarGetRelid(relvar, NoLock, false);
86 #endif
87         return checkOpenedRelation(
88                                 index_open(relOid, AccessExclusiveLock), GIST_AM_OID);
89 }
90
91 #define gist_index_close(r)     index_close((r), AccessExclusiveLock)
92
93 static Relation
94 gin_index_open(RangeVar *relvar) {
95 #if PG_VERSION_NUM < 90200
96         Oid relOid = RangeVarGetRelid(relvar, false);
97 #else
98         Oid relOid = RangeVarGetRelid(relvar, NoLock, false);
99 #endif
100         return checkOpenedRelation(
101                                 index_open(relOid, AccessShareLock), GIN_AM_OID);
102 }
103
104 #define gin_index_close(r) index_close((r), AccessShareLock)
105
106 #else /* <8.2 */
107
108 static Relation
109 gist_index_open(RangeVar *relvar) {
110         Relation rel = index_openrv(relvar);
111
112         LockRelation(rel, AccessExclusiveLock);
113         return checkOpenedRelation(rel, GIST_AM_OID);
114 }
115
116 static void
117 gist_index_close(Relation rel) {
118         UnlockRelation(rel, AccessExclusiveLock);
119         index_close(rel);
120 }
121
122 static Relation
123 gin_index_open(RangeVar *relvar) {
124         Relation rel = index_openrv(relvar);
125
126         LockRelation(rel, AccessShareLock);
127         return checkOpenedRelation(rel, GIN_AM_OID);
128 }
129
130 static void
131 gin_index_close(Relation rel) {
132         UnlockRelation(rel, AccessShareLock);
133         index_close(rel);
134 }
135
136 #endif
137
138 #if PG_VERSION_NUM >= 80300
139 #define stringToQualifiedNameList(x,y)  stringToQualifiedNameList(x)
140 #endif
141
142 #if PG_VERSION_NUM < 80300
143 #define SET_VARSIZE(p,l)        VARATT_SIZEP(p)=(l)
144 #endif
145
146 static Relation
147 checkOpenedRelation(Relation r, Oid PgAmOid) {
148         if ( r->rd_index == NULL )
149                 elog(ERROR, "Relation %s.%s is not an index",
150                                         get_namespace_name(RelationGetNamespace(r)),
151                                         RelationGetRelationName(r)
152                         );
153
154         if ( r->rd_rel->relam != PgAmOid )
155                 elog(ERROR, "Index %s.%s has wrong type",
156                                         get_namespace_name(RelationGetNamespace(r)),
157                                         RelationGetRelationName(r)
158                         );
159
160         return r;
161 }
162
163 static void
164 gist_dumptree(Relation r, int level, BlockNumber blk, OffsetNumber coff, IdxInfo *info) {
165         Buffer          buffer;
166         Page            page;
167         IndexTuple      which;
168         ItemId          iid;
169         OffsetNumber i,
170                                 maxoff;
171         BlockNumber cblk;
172         char       *pred;
173
174         pred = (char *) palloc(sizeof(char) * level * 4 + 1);
175         MemSet(pred, ' ', level*4);
176         pred[level*4] = '\0';
177
178         buffer = ReadBuffer(r, blk);
179         page = (Page) BufferGetPage(buffer);
180
181         maxoff = PageGetMaxOffsetNumber(page);
182
183
184         while ( (info->ptr-((char*)info->txt)) + level*4 + 128 >= info->len ) {
185                 int dist=info->ptr-((char*)info->txt);
186                 info->len *= 2;
187                 info->txt=(text*)repalloc(info->txt, info->len);
188                 info->ptr = ((char*)info->txt)+dist;
189         }
190
191         sprintf(info->ptr, "%s%d(l:%d) blk: %u numTuple: %d free: %db(%.2f%%) rightlink:%u (%s)\n",
192                 pred,
193                 coff,
194                 level,
195                 blk,
196                 (int) maxoff,
197                 (int) PageGetFreeSpace(page),
198                 100.0*(((float)PAGESIZE)-(float)PageGetFreeSpace(page))/((float)PAGESIZE),
199                 GistPageGetOpaque(page)->rightlink,
200                 ( GistPageGetOpaque(page)->rightlink == InvalidBlockNumber ) ? "InvalidBlockNumber" : "OK" );
201         info->ptr=strchr(info->ptr,'\0');
202
203         if (!GistPageIsLeaf(page) && ( info->maxlevel<0 || level<info->maxlevel ) )
204                 for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) {
205                         iid = PageGetItemId(page, i);
206                         which = (IndexTuple) PageGetItem(page, iid);
207                         cblk = ItemPointerGetBlockNumber(&(which->t_tid));
208                         gist_dumptree(r, level + 1, cblk, i, info);
209                 }
210         ReleaseBuffer(buffer);
211         pfree(pred);
212 }
213
214 PG_FUNCTION_INFO_V1(gist_tree);
215 Datum   gist_tree(PG_FUNCTION_ARGS);
216 Datum
217 gist_tree(PG_FUNCTION_ARGS) {
218         text    *name=PG_GETARG_TEXT_P(0);
219         char *relname=t2c(name);
220         RangeVar   *relvar;
221         Relation        index;
222         List       *relname_list;
223         IdxInfo info;
224
225         relname_list = stringToQualifiedNameList(relname, "gist_tree");
226         relvar = makeRangeVarFromNameList(relname_list);
227         index = gist_index_open(relvar);
228         PG_FREE_IF_COPY(name,0);
229
230         info.maxlevel = ( PG_NARGS() > 1 ) ? PG_GETARG_INT32(1) : -1;
231         info.len=1024;
232         info.txt=(text*)palloc( info.len );
233         info.ptr=((char*)info.txt)+VARHDRSZ;
234
235         gist_dumptree(index, 0, GIST_ROOT_BLKNO, 0, &info);
236
237         gist_index_close(index);
238         pfree(relname);
239
240         SET_VARSIZE(info.txt, info.ptr-((char*)info.txt));
241         PG_RETURN_POINTER(info.txt);
242 }
243
244 typedef struct {
245         int             level;
246         int             numpages;
247         int             numleafpages;
248         int             numtuple;
249         int             numinvalidtuple;
250         int             numleaftuple;
251         uint64  tuplesize;
252         uint64  leaftuplesize;
253         uint64  totalsize;
254 } IdxStat;
255
256 static void
257 gist_stattree(Relation r, int level, BlockNumber blk, OffsetNumber coff, IdxStat *info) {
258         Buffer          buffer;
259         Page            page;
260         IndexTuple      which;
261         ItemId          iid;
262         OffsetNumber i,
263                                 maxoff;
264         BlockNumber cblk;
265         char       *pred;
266
267         pred = (char *) palloc(sizeof(char) * level * 4 + 1);
268         MemSet(pred, ' ', level*4);
269         pred[level*4] = '\0';
270
271         buffer = ReadBuffer(r, blk);
272         page = (Page) BufferGetPage(buffer);
273
274         maxoff = PageGetMaxOffsetNumber(page);
275
276         info->numpages++;
277         info->tuplesize+=PAGESIZE-PageGetFreeSpace(page);
278         info->totalsize+=BLCKSZ;
279         info->numtuple+=maxoff;
280         if ( info->level < level )
281                 info->level = level;
282
283         if (GistPageIsLeaf(page)) {
284                 info->numleafpages++;
285                 info->leaftuplesize+=PAGESIZE-PageGetFreeSpace(page);
286                 info->numleaftuple+=maxoff;
287         } else {
288                 for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) {
289                         iid = PageGetItemId(page, i);
290                         which = (IndexTuple) PageGetItem(page, iid);
291                         if ( GistTupleIsInvalid(which) )
292                                 info->numinvalidtuple++;
293                         cblk = ItemPointerGetBlockNumber(&(which->t_tid));
294                                 gist_stattree(r, level + 1, cblk, i, info);
295                 }
296         }
297
298         ReleaseBuffer(buffer);
299         pfree(pred);
300 }
301
302 PG_FUNCTION_INFO_V1(gist_stat);
303 Datum   gist_stat(PG_FUNCTION_ARGS);
304 Datum
305 gist_stat(PG_FUNCTION_ARGS) {
306         text    *name=PG_GETARG_TEXT_P(0);
307         char *relname=t2c(name);
308         RangeVar   *relvar;
309         Relation        index;
310         List       *relname_list;
311         IdxStat info;
312         text *out=(text*)palloc(1024);
313         char *ptr=((char*)out)+VARHDRSZ;
314
315
316         relname_list = stringToQualifiedNameList(relname, "gist_tree");
317         relvar = makeRangeVarFromNameList(relname_list);
318         index = gist_index_open(relvar);
319         PG_FREE_IF_COPY(name,0);
320
321         memset(&info, 0, sizeof(IdxStat));
322
323         gist_stattree(index, 0, GIST_ROOT_BLKNO, 0, &info);
324
325         gist_index_close(index);
326         pfree(relname);
327
328         sprintf(ptr,
329                 "Number of levels:          %d\n"
330                 "Number of pages:           %d\n"
331                 "Number of leaf pages:      %d\n"
332                 "Number of tuples:          %d\n"
333                 "Number of invalid tuples:  %d\n"
334                 "Number of leaf tuples:     %d\n"
335                 "Total size of tuples:      "INT64_FORMAT" bytes\n"
336                 "Total size of leaf tuples: "INT64_FORMAT" bytes\n"
337                 "Total size of index:       "INT64_FORMAT" bytes\n",
338                 info.level+1,
339                 info.numpages,
340                 info.numleafpages,
341                 info.numtuple,
342                 info.numinvalidtuple,
343                 info.numleaftuple,
344                 info.tuplesize,
345                 info.leaftuplesize,
346                 info.totalsize);
347
348         ptr=strchr(ptr,'\0');
349
350         SET_VARSIZE(out, ptr-((char*)out));
351         PG_RETURN_POINTER(out);
352 }
353
354 typedef struct GPItem {
355         Buffer  buffer;
356         Page    page;
357         OffsetNumber    offset;
358         int     level;
359         struct GPItem *next;
360 } GPItem;
361
362 typedef struct {
363         List    *relname_list;
364         RangeVar   *relvar;
365         Relation        index;
366         Datum   *dvalues;
367 #if PG_VERSION_NUM >= 90600
368         bool    *nulls;
369 #else
370         char    *nulls;
371 #endif
372         GPItem  *item;
373 } TypeStorage;
374
375 static GPItem*
376 openGPPage( FuncCallContext *funcctx, BlockNumber blk ) {
377         GPItem  *nitem;
378         MemoryContext     oldcontext;
379         Relation index = ( (TypeStorage*)(funcctx->user_fctx) )->index;
380
381         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
382         nitem = (GPItem*)palloc( sizeof(GPItem) );
383         memset(nitem,0,sizeof(GPItem));
384
385         nitem->buffer = ReadBuffer(index, blk);
386         nitem->page = (Page) BufferGetPage(nitem->buffer);
387         nitem->offset=FirstOffsetNumber;
388         nitem->next = ( (TypeStorage*)(funcctx->user_fctx) )->item;
389         nitem->level = ( nitem->next ) ? nitem->next->level+1 : 1;
390         ( (TypeStorage*)(funcctx->user_fctx) )->item = nitem;
391
392         MemoryContextSwitchTo(oldcontext);
393         return nitem;
394 }
395
396 static GPItem*
397 closeGPPage( FuncCallContext *funcctx ) {
398         GPItem  *oitem = ( (TypeStorage*)(funcctx->user_fctx) )->item;
399
400         ( (TypeStorage*)(funcctx->user_fctx) )->item = oitem->next;
401
402         ReleaseBuffer(oitem->buffer);
403         pfree( oitem );
404         return ( (TypeStorage*)(funcctx->user_fctx) )->item;
405 }
406
407 static void
408 setup_firstcall(FuncCallContext  *funcctx, text *name) {
409         MemoryContext     oldcontext;
410         TypeStorage     *st;
411         char *relname=t2c(name);
412         TupleDesc            tupdesc;
413         char            attname[NAMEDATALEN];
414         int i;
415
416         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
417
418         st=(TypeStorage*)palloc( sizeof(TypeStorage) );
419         memset(st,0,sizeof(TypeStorage));
420         st->relname_list = stringToQualifiedNameList(relname, "gist_tree");
421         st->relvar = makeRangeVarFromNameList(st->relname_list);
422         st->index = gist_index_open(st->relvar);
423         funcctx->user_fctx = (void*)st;
424
425         tupdesc = CreateTemplateTupleDesc(st->index->rd_att->natts+2, false);
426         TupleDescInitEntry(tupdesc, 1, "level", INT4OID, -1, 0);
427         TupleDescInitEntry(tupdesc, 2, "valid", BOOLOID, -1, 0);
428         for (i = 0; i < st->index->rd_att->natts; i++) {
429                 sprintf(attname, "z%d", i+2);
430                 TupleDescInitEntry(
431                         tupdesc,
432                         i+3,
433                         attname,
434                         st->index->rd_att->attrs[i]->atttypid,
435                         st->index->rd_att->attrs[i]->atttypmod,
436                         st->index->rd_att->attrs[i]->attndims
437                 );
438         }
439
440         st->dvalues = (Datum *) palloc((tupdesc->natts+2) * sizeof(Datum));
441         st->nulls = (char *) palloc((tupdesc->natts+2) * sizeof(*st->nulls));
442
443         funcctx->slot = TupleDescGetSlot(tupdesc);
444         funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
445
446         MemoryContextSwitchTo(oldcontext);
447         pfree(relname);
448
449         st->item=openGPPage(funcctx, GIST_ROOT_BLKNO);
450 }
451
452 static void
453 close_call( FuncCallContext  *funcctx ) {
454         TypeStorage *st = (TypeStorage*)(funcctx->user_fctx);
455
456         while(st->item && closeGPPage(funcctx));
457
458         pfree(st->dvalues);
459         pfree(st->nulls);
460
461         gist_index_close(st->index);
462 }
463
464 PG_FUNCTION_INFO_V1(gist_print);
465 Datum   gist_print(PG_FUNCTION_ARGS);
466 Datum
467 gist_print(PG_FUNCTION_ARGS) {
468         FuncCallContext  *funcctx;
469         TypeStorage     *st;
470         Datum result=(Datum)0;
471         ItemId          iid;
472         IndexTuple      ituple;
473         HeapTuple       htuple;
474         int i;
475         bool isnull;
476
477         if (SRF_IS_FIRSTCALL()) {
478                 text    *name=PG_GETARG_TEXT_P(0);
479                 funcctx = SRF_FIRSTCALL_INIT();
480                 setup_firstcall(funcctx, name);
481                 PG_FREE_IF_COPY(name,0);
482         }
483
484         funcctx = SRF_PERCALL_SETUP();
485         st = (TypeStorage*)(funcctx->user_fctx);
486
487         if ( !st->item ) {
488                 close_call(funcctx);
489                 SRF_RETURN_DONE(funcctx);
490         }
491
492         while( st->item->offset > PageGetMaxOffsetNumber( st->item->page ) ) {
493                 if ( ! closeGPPage(funcctx) ) {
494                         close_call(funcctx);
495                         SRF_RETURN_DONE(funcctx);
496                 }
497         }
498
499         iid = PageGetItemId( st->item->page, st->item->offset );
500         ituple = (IndexTuple) PageGetItem(st->item->page, iid);
501
502         st->dvalues[0] = Int32GetDatum( st->item->level );
503         st->nulls[0] = ISNOTNULL;
504         st->dvalues[1] = BoolGetDatum( (!GistPageIsLeaf(st->item->page) && GistTupleIsInvalid(ituple)) ? false : true );
505         st->nulls[1] = ISNOTNULL;
506         for(i=2; i<funcctx->attinmeta->tupdesc->natts; i++) {
507                 if ( !GistPageIsLeaf(st->item->page) && GistTupleIsInvalid(ituple) ) {
508                         st->dvalues[i] = (Datum)0;
509                         st->nulls[i] = ISNULL;
510                 } else {
511                         st->dvalues[i] = index_getattr(ituple, i-1, st->index->rd_att, &isnull);
512                         st->nulls[i] = ( isnull ) ? ISNULL : ISNOTNULL;
513                 }
514         }
515
516         htuple = heap_formtuple(funcctx->attinmeta->tupdesc, st->dvalues, st->nulls);
517         result = TupleGetDatum(funcctx->slot, htuple);
518         st->item->offset = OffsetNumberNext(st->item->offset);
519         if ( !GistPageIsLeaf(st->item->page) )
520                 openGPPage(funcctx, ItemPointerGetBlockNumber(&(ituple->t_tid)) );
521
522         SRF_RETURN_NEXT(funcctx, result);
523 }
524
525 typedef struct GinStatState {
526         Relation                index;
527         GinState                ginstate;
528         OffsetNumber    attnum;
529
530         Buffer                  buffer;
531         OffsetNumber    offset;
532         Datum                   curval;
533 #if PG_VERSION_NUM >= 90100
534         GinNullCategory category;
535 #endif
536         Datum                   dvalues[2];
537         char                    nulls[2];
538 } GinStatState;
539
540 static bool
541 moveRightIfItNeeded( GinStatState *st )
542 {
543         Page page = BufferGetPage(st->buffer);
544
545         if ( st->offset > PageGetMaxOffsetNumber(page) ) {
546                 /*
547                 * We scaned the whole page, so we should take right page
548                 */
549                 BlockNumber blkno = GinPageGetOpaque(page)->rightlink;
550
551                 if ( GinPageRightMost(page) )
552                         return false;  /* no more page */
553
554                 LockBuffer(st->buffer, GIN_UNLOCK);
555                 st->buffer = ReleaseAndReadBuffer(st->buffer, st->index, blkno);
556                 LockBuffer(st->buffer, GIN_SHARE);
557                 st->offset = FirstOffsetNumber;
558         }
559
560         return true;
561 }
562
563 /*
564  * Refinds a previois position, at returns it has correctly
565  * set offset and buffer is locked
566  */
567 static bool
568 refindPosition(GinStatState *st)
569 {
570         Page    page;
571
572         /* find left if needed (it causes only for first search) */
573         for (;;) {
574                 IndexTuple  itup;
575                 BlockNumber blkno;
576
577                 LockBuffer(st->buffer, GIN_SHARE);
578
579                 page = BufferGetPage(st->buffer);
580                 if (GinPageIsLeaf(page))
581                         break;
582
583                 itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber));
584                 blkno = GinItemPointerGetBlockNumber(&(itup)->t_tid);
585
586                 LockBuffer(st->buffer,GIN_UNLOCK);
587                 st->buffer = ReleaseAndReadBuffer(st->buffer, st->index, blkno);
588         }
589
590         if (st->offset == InvalidOffsetNumber) {
591                 return (PageGetMaxOffsetNumber(page) >= FirstOffsetNumber ) ? true : false; /* first one */
592         }
593
594         for(;;) {
595                 int                             cmp;
596 #if PG_VERSION_NUM >= 90100
597                 GinNullCategory category;
598 #elif PG_VERSION_NUM < 80400
599                 bool                    isnull = false;
600 #endif
601                 Datum                   datum;
602                 IndexTuple              itup;
603
604                 if (moveRightIfItNeeded(st)==false)
605                         return false;
606
607                 itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, st->offset));
608 #if PG_VERSION_NUM >= 90100
609                 datum = gintuple_get_key(&st->ginstate, itup, &category); 
610                 cmp = ginCompareAttEntries(&st->ginstate,
611                                                                         st->attnum + 1, st->curval, st->category,
612                                                                         gintuple_get_attrnum(&st->ginstate, itup), datum, category);
613 #elif PG_VERSION_NUM >= 80400
614                 datum = gin_index_getattr(&st->ginstate, itup);
615
616                 cmp = compareAttEntries(&st->ginstate,
617                                                                         st->attnum + 1, st->curval,
618                                                                         gintuple_get_attrnum(&st->ginstate, itup), datum);
619 #else
620                 datum = index_getattr(itup, FirstOffsetNumber, st->ginstate.tupdesc, &isnull);
621
622                 cmp = DatumGetInt32(
623                                 FunctionCall2(
624                                                 &st->ginstate.compareFn,
625                                                 st->curval,
626                                                 datum
627                                         ));
628 #endif
629                 if ( cmp == 0 )
630                 {
631                         if ( st->curval && !st->index->rd_att->attrs[st->attnum]->attbyval )
632                                 pfree( (void*) st->curval );
633                         return true;
634                 }
635
636                 st->offset++;
637         }
638
639         return false;
640 }
641
642 static void
643 gin_setup_firstcall(FuncCallContext  *funcctx, text *name, int attnum) {
644         MemoryContext     oldcontext;
645         GinStatState     *st;
646         char *relname=t2c(name);
647         TupleDesc            tupdesc;
648
649         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
650
651         st=(GinStatState*)palloc( sizeof(GinStatState) );
652         memset(st,0,sizeof(GinStatState));
653         st->index = gin_index_open(
654                  makeRangeVarFromNameList(stringToQualifiedNameList(relname, "gin_stat")));
655         initGinState( &st->ginstate, st->index );
656
657 #if PG_VERSION_NUM >= 80400
658         if (attnum < 0 || attnum >= st->index->rd_att->natts)
659                 elog(ERROR,"Wrong column's number");
660         st->attnum = attnum;
661 #else
662         st->attnum = 0;
663 #endif
664
665         funcctx->user_fctx = (void*)st;
666
667         tupdesc = CreateTemplateTupleDesc(2, false);
668         TupleDescInitEntry(tupdesc, 1, "value",
669                         st->index->rd_att->attrs[st->attnum]->atttypid,
670                         st->index->rd_att->attrs[st->attnum]->atttypmod,
671                         st->index->rd_att->attrs[st->attnum]->attndims);
672         TupleDescInitEntry(tupdesc, 2, "nrow", INT4OID, -1, 0);
673
674         memset( st->nulls, ISNOTNULL, 2*sizeof(*st->nulls) );
675
676         funcctx->slot = TupleDescGetSlot(tupdesc);
677         funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
678
679         MemoryContextSwitchTo(oldcontext);
680         pfree(relname);
681
682         st->offset = InvalidOffsetNumber;
683         st->buffer = ReadBuffer( st->index, GIN_ROOT_BLKNO );
684 }
685
686 static void
687 processTuple( FuncCallContext  *funcctx,  GinStatState *st, IndexTuple itup ) {
688         MemoryContext           oldcontext;
689 #if PG_VERSION_NUM >= 90100
690         Datum                           key;
691 #elif PG_VERSION_NUM < 80400
692         bool                            isnull;
693 #endif
694
695         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
696
697 #if PG_VERSION_NUM >= 90100
698         key = gintuple_get_key(&st->ginstate, itup, &st->category);
699
700         if (st->category != GIN_CAT_NORM_KEY)
701                 st->curval = (Datum)0;
702         else
703 #endif
704         st->curval = datumCopy(
705 #if PG_VERSION_NUM >= 90100
706                                         key,
707 #elif PG_VERSION_NUM >= 80400
708                                         gin_index_getattr(&st->ginstate, itup),
709 #else
710                                         index_getattr(itup, FirstOffsetNumber, st->ginstate.tupdesc, &isnull),
711 #endif
712                                         st->index->rd_att->attrs[st->attnum]->attbyval,
713                                         st->index->rd_att->attrs[st->attnum]->attlen );
714         MemoryContextSwitchTo(oldcontext);
715
716         st->dvalues[0] = st->curval;
717 #if PG_VERSION_NUM >= 90100
718         /* do no distiguish various null category */
719         st->nulls[0] = (st->category == GIN_CAT_NORM_KEY) ? ISNOTNULL : ISNULL;
720 #endif
721
722         if ( GinIsPostingTree(itup) ) {
723                 BlockNumber     rootblkno = GinGetPostingTree(itup);
724 #if PG_VERSION_NUM >= 90400
725                 GinBtreeData    btree;
726                 GinBtreeStack   *stack;
727                 ItemPointerData minItem;
728                 int                             nlist;
729                 ItemPointer             list;
730 #else
731                 GinPostingTreeScan *gdi;
732                 Buffer                  entrybuffer;
733 #endif
734                 Page        page;
735                 uint32          predictNumber;
736
737                 LockBuffer(st->buffer, GIN_UNLOCK);
738 #if PG_VERSION_NUM >= 90400
739                 stack = ginScanBeginPostingTree(&btree, st->index, rootblkno);
740                 page = BufferGetPage(stack->buffer);
741                 ItemPointerSetMin(&minItem);
742                 list = GinDataLeafPageGetItems(page, &nlist, minItem);
743                 pfree(list);
744                 predictNumber = stack->predictNumber;
745                 st->dvalues[1] = Int32GetDatum( predictNumber * nlist );
746 #elif PG_VERSION_NUM >= 90100
747                 gdi = ginPrepareScanPostingTree(st->index, rootblkno, TRUE);
748                 entrybuffer = ginScanBeginPostingTree(gdi);
749                 page = BufferGetPage(entrybuffer);
750                 predictNumber = gdi->stack->predictNumber;
751                 st->dvalues[1] = Int32GetDatum( predictNumber * GinPageGetOpaque(page)->maxoff );
752 #else
753                 gdi = prepareScanPostingTree(st->index, rootblkno, TRUE);
754                 entrybuffer = scanBeginPostingTree(gdi);
755                 page = BufferGetPage(entrybuffer);
756                 predictNumber = gdi->stack->predictNumber;
757                 st->dvalues[1] = Int32GetDatum( predictNumber * GinPageGetOpaque(page)->maxoff );
758 #endif
759
760 #if PG_VERSION_NUM < 90400
761                 LockBuffer(entrybuffer, GIN_UNLOCK);
762                 freeGinBtreeStack(gdi->stack);
763                 pfree(gdi);
764 #else
765                 LockBuffer(stack->buffer, GIN_UNLOCK);
766                 freeGinBtreeStack(stack);
767 #endif
768         } else {
769                 st->dvalues[1] = Int32GetDatum( GinGetNPosting(itup) );
770                 LockBuffer(st->buffer, GIN_UNLOCK);
771         }
772 }
773
774 PG_FUNCTION_INFO_V1(gin_stat);
775 Datum   gin_stat(PG_FUNCTION_ARGS);
776 Datum
777 gin_stat(PG_FUNCTION_ARGS) {
778         FuncCallContext  *funcctx;
779         GinStatState     *st;
780         Datum result=(Datum)0;
781         IndexTuple      ituple;
782         HeapTuple       htuple;
783         Page page;
784
785         if (SRF_IS_FIRSTCALL()) {
786                 text    *name=PG_GETARG_TEXT_P(0);
787                 funcctx = SRF_FIRSTCALL_INIT();
788                 gin_setup_firstcall(funcctx, name, (PG_NARGS()==2) ? PG_GETARG_INT32(1) : 0 );
789                 PG_FREE_IF_COPY(name,0);
790         }
791
792         funcctx = SRF_PERCALL_SETUP();
793         st = (GinStatState*)(funcctx->user_fctx);
794
795         if ( refindPosition(st) == false ) {
796                 UnlockReleaseBuffer( st->buffer );
797                 gin_index_close(st->index);
798
799                 SRF_RETURN_DONE(funcctx);
800         }
801
802         for(;;) {
803                 st->offset++;
804
805                 if (moveRightIfItNeeded(st)==false) {
806                         UnlockReleaseBuffer( st->buffer );
807                         gin_index_close(st->index);
808
809                         SRF_RETURN_DONE(funcctx);
810                 }
811
812                 page = BufferGetPage(st->buffer);
813                 ituple = (IndexTuple) PageGetItem(page, PageGetItemId(page, st->offset));
814
815 #if PG_VERSION_NUM >= 80400
816                 if (st->attnum + 1 == gintuple_get_attrnum(&st->ginstate, ituple))
817 #endif
818                         break;
819         }
820
821         processTuple( funcctx,  st, ituple );
822
823         htuple = heap_formtuple(funcctx->attinmeta->tupdesc, st->dvalues, st->nulls);
824         result = TupleGetDatum(funcctx->slot, htuple);
825
826         SRF_RETURN_NEXT(funcctx, result);
827 }
828
829 PG_FUNCTION_INFO_V1(gin_count_estimate);
830 Datum gin_count_estimate(PG_FUNCTION_ARGS);
831 #if PG_VERSION_NUM >= 80300
832 Datum
833 gin_count_estimate(PG_FUNCTION_ARGS) {
834         text                    *name=PG_GETARG_TEXT_P(0);
835         Relation                index;
836         IndexScanDesc   scan;
837         int64                   count = 0;
838         char                    *relname=t2c(name);
839         ScanKeyData             key;
840 #if PG_VERSION_NUM >= 80400
841         TIDBitmap               *bitmap = tbm_create(work_mem * 1024L);
842 #else
843 #define MAXTIDS         1024
844         ItemPointerData tids[MAXTIDS];
845         int32                   returned_tids;
846         bool                    more;
847 #endif
848
849         index = gin_index_open(
850                  makeRangeVarFromNameList(stringToQualifiedNameList(relname, "gin_count_estimate")));
851
852         if ( index->rd_opcintype[0] != TSVECTOROID ) {
853                 gin_index_close(index);
854                 elog(ERROR, "Column type is not a tsvector");
855         }
856
857         key.sk_flags = 0;
858         key.sk_attno = 1;
859         key.sk_strategy = TSearchStrategyNumber;
860         key.sk_subtype  = 0;
861         key.sk_argument = PG_GETARG_DATUM(1);
862
863         fmgr_info( F_TS_MATCH_VQ , &key.sk_func );
864
865 #if PG_VERSION_NUM >= 90100
866 #if PG_VERSION_NUM >= 90400
867         scan = index_beginscan_bitmap(index, SnapshotSelf, 1);
868 #else
869         scan = index_beginscan_bitmap(index, SnapshotNow, 1);
870 #endif
871         index_rescan(scan, &key, 1, NULL, 0);
872
873         count = index_getbitmap(scan, bitmap);
874         tbm_free(bitmap);
875 #elif PG_VERSION_NUM >= 80400
876         scan = index_beginscan_bitmap(index, SnapshotNow, 1, &key);
877
878         count = index_getbitmap(scan, bitmap);
879         tbm_free(bitmap);
880 #else
881         scan = index_beginscan_multi(index, SnapshotNow, 1, &key);
882
883         do {
884                 more = index_getmulti(scan, tids, MAXTIDS, &returned_tids);
885                 count += returned_tids;
886         } while(more);
887 #endif
888
889         index_endscan( scan );
890         gin_index_close(index);
891
892         PG_RETURN_INT64(count);
893 }
894 #else
895 Datum
896 gin_count_estimate(PG_FUNCTION_ARGS) {
897         elog(NOTICE, "Function is not working under PgSQL < 8.3");
898
899         PG_RETURN_INT64(0);
900 }
901 #endif
902
903 PG_FUNCTION_INFO_V1(spgist_stat);
904 Datum spgist_stat(PG_FUNCTION_ARGS);
905 Datum
906 spgist_stat(PG_FUNCTION_ARGS)
907 {
908 #if PG_VERSION_NUM < 90200
909         elog(NOTICE, "Function is not working under PgSQL < 9.2");
910
911         PG_RETURN_TEXT_P(CStringGetTextDatum("???"));
912 #else
913         text       *name = PG_GETARG_TEXT_P(0);
914         RangeVar   *relvar;
915         Relation        index;
916         BlockNumber blkno;
917         BlockNumber totalPages = 0,
918                                 innerPages = 0,
919                                 leafPages = 0,
920                                 emptyPages = 0,
921                                 deletedPages = 0;
922         double    usedSpace = 0.0,
923                           usedLeafSpace = 0.0,
924                           usedInnerSpace = 0.0;
925         char            res[1024];
926         int              bufferSize = -1;
927         int64      innerTuples = 0,
928                                 leafTuples = 0,
929                                 nAllTheSame = 0,
930                                 nLeafPlaceholder = 0,
931                                 nInnerPlaceholder = 0,
932                                 nLeafRedirect = 0,
933                                 nInnerRedirect = 0;
934
935 #define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX)
936 #define IS_SPGIST(r) ((r)->rd_rel->relam == SPGIST_AM_OID)
937
938         relvar = makeRangeVarFromNameList(textToQualifiedNameList(name));
939         index = relation_openrv(relvar, AccessExclusiveLock);
940
941         if (!IS_INDEX(index) || !IS_SPGIST(index))
942                 elog(ERROR, "relation \"%s\" is not an SPGiST index",
943                          RelationGetRelationName(index));
944
945         totalPages = RelationGetNumberOfBlocks(index);
946
947         for (blkno = SPGIST_ROOT_BLKNO; blkno < totalPages; blkno++)
948         {
949                 Buffer    buffer;
950                 Page            page;
951                 int              pageFree;
952
953                 buffer = ReadBuffer(index, blkno);
954                 LockBuffer(buffer, BUFFER_LOCK_SHARE);
955
956                 page = BufferGetPage(buffer);
957
958                 if (PageIsNew(page) || SpGistPageIsDeleted(page))
959                 {
960                         deletedPages++;
961                         UnlockReleaseBuffer(buffer);
962                         continue;
963                 }
964
965                 if (SpGistPageIsLeaf(page))
966                 {
967                         leafPages++;
968                         leafTuples += PageGetMaxOffsetNumber(page);
969                         nLeafPlaceholder += SpGistPageGetOpaque(page)->nPlaceholder;
970                         nLeafRedirect += SpGistPageGetOpaque(page)->nRedirection;
971                 }
972                 else
973                 {
974                         int      i,
975                                         max;
976
977                         innerPages++;
978                         max = PageGetMaxOffsetNumber(page);
979                         innerTuples += max;
980                         nInnerPlaceholder += SpGistPageGetOpaque(page)->nPlaceholder;
981                         nInnerRedirect += SpGistPageGetOpaque(page)->nRedirection;
982                         for (i = FirstOffsetNumber; i <= max; i++)
983                         {
984                                 SpGistInnerTuple it;
985
986                                 it = (SpGistInnerTuple) PageGetItem(page,
987                                                                                                         PageGetItemId(page, i));
988                                 if (it->allTheSame)
989                                         nAllTheSame++;
990                         }
991                 }
992
993                 if (bufferSize < 0)
994                         bufferSize = BufferGetPageSize(buffer)
995                                 - MAXALIGN(sizeof(SpGistPageOpaqueData))
996                                 - SizeOfPageHeaderData;
997
998                 pageFree = PageGetExactFreeSpace(page);
999
1000                 usedSpace += bufferSize - pageFree;
1001                 if (SpGistPageIsLeaf(page))
1002                         usedLeafSpace += bufferSize - pageFree;
1003                 else
1004                         usedInnerSpace += bufferSize - pageFree;
1005
1006                 if (pageFree == bufferSize)
1007                         emptyPages++;
1008
1009                 UnlockReleaseBuffer(buffer);
1010         }
1011
1012         index_close(index, AccessExclusiveLock);
1013
1014         totalPages--;                      /* discount metapage */
1015
1016         snprintf(res, sizeof(res),
1017                          "totalPages:        %u\n"
1018                          "deletedPages:      %u\n"
1019                          "innerPages:        %u\n"
1020                          "leafPages:         %u\n"
1021                          "emptyPages:        %u\n"
1022                          "usedSpace:         %.2f kbytes\n"
1023                          "usedInnerSpace:    %.2f kbytes\n"
1024                          "usedLeafSpace:     %.2f kbytes\n"
1025                          "freeSpace:         %.2f kbytes\n"
1026                          "fillRatio:         %.2f%%\n"
1027                          "leafTuples:        " INT64_FORMAT "\n"
1028                          "innerTuples:       " INT64_FORMAT "\n"
1029                          "innerAllTheSame:   " INT64_FORMAT "\n"
1030                          "leafPlaceholders:  " INT64_FORMAT "\n"
1031                          "innerPlaceholders: " INT64_FORMAT "\n"
1032                          "leafRedirects:     " INT64_FORMAT "\n"
1033                          "innerRedirects:    " INT64_FORMAT,
1034                          totalPages, deletedPages, innerPages, leafPages, emptyPages,
1035                          usedSpace / 1024.0,
1036                          usedInnerSpace / 1024.0,
1037                          usedLeafSpace / 1024.0,
1038                          (((double) bufferSize) * ((double) totalPages) - usedSpace) / 1024,
1039                          100.0 * (usedSpace / (((double) bufferSize) * ((double) totalPages))),
1040                          leafTuples, innerTuples, nAllTheSame,
1041                          nLeafPlaceholder, nInnerPlaceholder,
1042                          nLeafRedirect, nInnerRedirect);
1043
1044         PG_RETURN_TEXT_P(CStringGetTextDatum(res));
1045 #endif
1046 }
1047
1048 #if PG_VERSION_NUM >= 90200
1049
1050 typedef struct SPGistPrintStackElem {
1051         ItemPointerData         iptr;
1052         int16                           nlabel;
1053         int                                     level;
1054 } SPGistPrintStackElem;
1055
1056 typedef struct SPGistPrint {
1057         SpGistState     state;
1058         Relation        index;
1059         Datum           dvalues[8 /* see CreateTemplateTupleDesc call */];
1060         char            nulls[8 /* see CreateTemplateTupleDesc call */];
1061         List            *stack;
1062 } SPGistPrint;
1063
1064 static void
1065 pushSPGistPrint(FuncCallContext *funcctx, SPGistPrint *prst, ItemPointer ip, int level) {
1066         MemoryContext   oldcontext;
1067         SPGistPrintStackElem    *e;
1068
1069         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
1070
1071         e = palloc(sizeof(*e));
1072         e->iptr = *ip;
1073         e->nlabel = 0;
1074         e->level = level;
1075         prst->stack = lcons(e, prst->stack);
1076
1077         MemoryContextSwitchTo(oldcontext);
1078 }
1079
1080 static void
1081 close_spgist_print(SPGistPrint *prst) {
1082         index_close(prst->index, AccessExclusiveLock);
1083 }
1084 #endif
1085
1086 PG_FUNCTION_INFO_V1(spgist_print);
1087 Datum spgist_print(PG_FUNCTION_ARGS);
1088 Datum
1089 spgist_print(PG_FUNCTION_ARGS)
1090 {
1091 #if PG_VERSION_NUM < 90200
1092         elog(NOTICE, "Function is not working under PgSQL < 9.2");
1093
1094         PG_RETURN_TEXT_P(CStringGetTextDatum("???"));
1095 #else
1096         FuncCallContext                 *funcctx;
1097         SPGistPrint                             *prst;
1098         SPGistPrintStackElem    *s;
1099         HeapTuple                               htuple;
1100         Datum                                   result;
1101         MemoryContext                   oldcontext;
1102
1103         if (SRF_IS_FIRSTCALL()) {
1104                 text                    *name=PG_GETARG_TEXT_P(0);
1105                 RangeVar                *relvar;
1106                 Relation                index;
1107                 ItemPointerData ipd;
1108                 TupleDesc               tupdesc;
1109
1110                 funcctx = SRF_FIRSTCALL_INIT();
1111                 relvar = makeRangeVarFromNameList(textToQualifiedNameList(name));
1112                 index = relation_openrv(relvar, AccessExclusiveLock);
1113
1114                 if (!IS_INDEX(index) || !IS_SPGIST(index))
1115                         elog(ERROR, "relation \"%s\" is not an SPGiST index",
1116                                  RelationGetRelationName(index));
1117
1118                 oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
1119
1120                 prst = palloc(sizeof(*prst));
1121
1122                 prst->index = index;
1123                 initSpGistState(&prst->state, index);
1124
1125                 tupdesc = CreateTemplateTupleDesc(3 /* types */ + 1 /* level */ + 1 /* nlabel */ +  2 /* tids */ + 1, false);
1126                 TupleDescInitEntry(tupdesc, 1, "tid", TIDOID, -1, 0);
1127                 TupleDescInitEntry(tupdesc, 2, "allthesame", BOOLOID, -1, 0);
1128                 TupleDescInitEntry(tupdesc, 3, "node", INT4OID, -1, 0);
1129                 TupleDescInitEntry(tupdesc, 4, "level", INT4OID, -1, 0);
1130                 TupleDescInitEntry(tupdesc, 5, "tid_pointer", TIDOID, -1, 0);
1131                 TupleDescInitEntry(tupdesc, 6, "prefix",
1132                                 (prst->state.attPrefixType.type == VOIDOID) ? INT4OID : prst->state.attPrefixType.type, -1, 0);
1133                 TupleDescInitEntry(tupdesc, 7, "label",
1134                                 (prst->state.attLabelType.type == VOIDOID) ? INT4OID : prst->state.attLabelType.type, -1, 0);
1135                 TupleDescInitEntry(tupdesc, 8, "leaf",
1136                                 (prst->state.attType.type == VOIDOID) ? INT4OID : prst->state.attType.type, -1, 0);
1137
1138                 funcctx->slot = TupleDescGetSlot(tupdesc);
1139                 funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
1140
1141                 funcctx->user_fctx = (void*)prst;
1142
1143                 MemoryContextSwitchTo(oldcontext);
1144
1145                 ItemPointerSet(&ipd, SPGIST_ROOT_BLKNO, FirstOffsetNumber);
1146                 prst->stack = NIL;
1147                 pushSPGistPrint(funcctx, prst, &ipd, 1);
1148
1149                 PG_FREE_IF_COPY(name,0);
1150         }
1151
1152         funcctx = SRF_PERCALL_SETUP();
1153         prst = (SPGistPrint*)(funcctx->user_fctx);
1154
1155 next:
1156         for(;;) {
1157                 if ( prst->stack == NIL ) {
1158                         close_spgist_print(prst);
1159                         SRF_RETURN_DONE(funcctx);
1160                 }
1161
1162                 CHECK_FOR_INTERRUPTS();
1163
1164                 s = (SPGistPrintStackElem*)linitial(prst->stack);
1165                 prst->stack = list_delete_first(prst->stack);
1166
1167                 if (ItemPointerIsValid(&s->iptr))
1168                         break;
1169                 free(s);
1170         }
1171
1172         do {
1173                 Buffer                          buffer;
1174                 Page                            page;
1175                 SpGistDeadTuple         dtuple;
1176                 ItemPointer                     tid;
1177
1178                 buffer = ReadBuffer(prst->index, ItemPointerGetBlockNumber(&s->iptr));
1179                 LockBuffer(buffer, BUFFER_LOCK_SHARE);
1180
1181                 page = BufferGetPage(buffer);
1182                 if (ItemPointerGetOffsetNumber(&s->iptr) > PageGetMaxOffsetNumber(page)) {
1183                         UnlockReleaseBuffer(buffer);
1184                         pfree(s);
1185                         goto next;
1186                 }
1187
1188                 dtuple = (SpGistDeadTuple)PageGetItem(page, PageGetItemId(page, ItemPointerGetOffsetNumber(&s->iptr)));
1189
1190                 if (dtuple->tupstate != SPGIST_LIVE)  {
1191                         UnlockReleaseBuffer(buffer);
1192                         pfree(s);
1193                         goto next;
1194                 }
1195
1196                 if (SpGistPageIsLeaf(page)) {
1197                                 SpGistLeafTuple leafTuple = (SpGistLeafTuple)dtuple;
1198
1199                                 tid = palloc(sizeof(ItemPointerData));
1200                                 *tid = s->iptr;
1201                                 prst->dvalues[0] = PointerGetDatum(tid);
1202                                 prst->nulls[0] = ISNOTNULL;
1203                                 prst->nulls[1] = ISNULL;
1204                                 prst->nulls[2] = ISNULL;
1205                                 prst->dvalues[3]  = s->level;
1206                                 prst->nulls[3] = ISNOTNULL;
1207                                 prst->nulls[4] = ISNULL;
1208                                 prst->nulls[5] = ISNULL;
1209                                 prst->nulls[6] = ISNULL;
1210                                 prst->dvalues[7]  = datumCopy(SGLTDATUM(leafTuple, &prst->state),
1211                                                                                         prst->state.attType.attbyval, prst->state.attType.attlen);
1212                                 prst->nulls[7] = ISNOTNULL;
1213                 } else {
1214                         SpGistInnerTuple        innerTuple = (SpGistInnerTuple)dtuple;
1215                         int                                     i;
1216                         SpGistNodeTuple         node;
1217
1218                         SGITITERATE(innerTuple, i, node) {
1219                                 if (ItemPointerIsValid(&node->t_tid)) {
1220                                         if (i >= s->nlabel)
1221                                                 break;
1222                                 }
1223                         }
1224
1225                         if (i >= innerTuple->nNodes) {
1226                                 UnlockReleaseBuffer(buffer);
1227                                 pfree(s);
1228                                 goto next;
1229                         }
1230
1231                         tid = palloc(sizeof(ItemPointerData));
1232                         *tid = s->iptr;
1233                         prst->dvalues[0] = PointerGetDatum(tid);
1234                         prst->nulls[0] = ISNOTNULL;
1235                         prst->dvalues[1] = innerTuple->allTheSame;
1236                         prst->nulls[1] = ISNOTNULL;
1237                         prst->dvalues[2] = Int32GetDatum(s->nlabel);
1238                         prst->nulls[2] = ISNOTNULL;
1239                         prst->dvalues[3]  = s->level;
1240                         prst->nulls[3] = ISNOTNULL;
1241                         tid = palloc(sizeof(ItemPointerData));
1242                         *tid = node->t_tid;
1243                         prst->dvalues[4] = PointerGetDatum(tid);
1244                         prst->nulls[5] = ISNOTNULL;
1245                         if (innerTuple->prefixSize > 0) {
1246                                 prst->dvalues[5]  = datumCopy(SGITDATUM(innerTuple, &prst->state),
1247                                                                                         prst->state.attPrefixType.attbyval, prst->state.attPrefixType.attlen);
1248                                 prst->nulls[5] = ISNOTNULL;
1249                         } else
1250                                 prst->nulls[5] = ISNULL;
1251                         if (!IndexTupleHasNulls(node)) {
1252                                 prst->dvalues[6]  = datumCopy(SGNTDATUM(node, &prst->state),
1253                                                                                         prst->state.attLabelType.attbyval, prst->state.attLabelType.attlen);
1254                                 prst->nulls[6] = ISNOTNULL;
1255                         } else
1256                                 prst->nulls[6] = ISNULL;
1257                         prst->nulls[7] = ISNULL;
1258
1259                         pushSPGistPrint(funcctx, prst, &node->t_tid, s->level + 1);
1260                         s->nlabel = i + 1;
1261                         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
1262                         prst->stack = lcons(s, prst->stack);
1263                         MemoryContextSwitchTo(oldcontext);
1264                         s = NULL;
1265                 }
1266
1267                 UnlockReleaseBuffer(buffer);
1268                 if (s) pfree(s);
1269         } while(0);
1270
1271         htuple = heap_formtuple(funcctx->attinmeta->tupdesc, prst->dvalues, prst->nulls);
1272         result = TupleGetDatum(funcctx->slot, htuple);
1273
1274         SRF_RETURN_NEXT(funcctx, result);
1275 #endif
1276 }
1277
1278
1279 PG_FUNCTION_INFO_V1(gin_statpage);
1280 Datum gin_statpage(PG_FUNCTION_ARGS);
1281 Datum
1282 gin_statpage(PG_FUNCTION_ARGS)
1283 {
1284 #if PG_VERSION_NUM < 90400
1285         elog(NOTICE, "Function is not working under PgSQL < 9.4");
1286
1287         PG_RETURN_TEXT_P(CStringGetTextDatum("???"));
1288 #else
1289         text       *name = PG_GETARG_TEXT_P(0);
1290         RangeVar   *relvar;
1291         Relation        index;
1292         BlockNumber blkno;
1293         char            res[1024];
1294         uint32          totalPages,
1295                                 entryPages = 0,
1296                                 dataPages = 0,
1297                                 dataInnerPages = 0,
1298                                 dataLeafPages = 0,
1299                                 entryInnerPages = 0,
1300                                 entryLeafPages = 0
1301                                 ;
1302         uint64          dataInnerFreeSpace = 0,
1303                                 dataLeafFreeSpace = 0,
1304                                 dataInnerTuplesCount = 0,
1305                                 dataLeafIptrsCount = 0,
1306                                 entryInnerFreeSpace = 0,
1307                                 entryLeafFreeSpace = 0,
1308                                 entryInnerTuplesCount = 0,
1309                                 entryLeafTuplesCount = 0,
1310                                 entryPostingSize = 0,
1311                                 entryPostingCount = 0,
1312                                 entryAttrSize = 0
1313                                 ;
1314
1315         relvar = makeRangeVarFromNameList(textToQualifiedNameList(name));
1316         index = relation_openrv(relvar, AccessExclusiveLock);
1317
1318         if (index->rd_rel->relkind != RELKIND_INDEX ||
1319                         index->rd_rel->relam != GIN_AM_OID)
1320                 elog(ERROR, "relation \"%s\" is not an SPGiST index",
1321                          RelationGetRelationName(index));
1322
1323         totalPages = RelationGetNumberOfBlocks(index);
1324
1325         for (blkno = GIN_ROOT_BLKNO; blkno < totalPages; blkno++)
1326         {
1327                 Buffer          buffer;
1328                 Page            page;
1329                 PageHeader      header;
1330
1331                 buffer = ReadBuffer(index, blkno);
1332                 LockBuffer(buffer, BUFFER_LOCK_SHARE);
1333
1334                 page = BufferGetPage(buffer);
1335                 header = (PageHeader)page;
1336
1337                 if (GinPageIsData(page))
1338                 {
1339                         dataPages++;
1340                         if (GinPageIsLeaf(page))
1341                         {
1342                                 ItemPointerData minItem;
1343                                 int nlist;
1344
1345                                 dataLeafPages++;
1346                                 dataLeafFreeSpace += header->pd_upper - header->pd_lower;
1347                                 ItemPointerSetMin(&minItem);
1348                                 pfree(GinDataLeafPageGetItems(page, &nlist, minItem));
1349                                 dataLeafIptrsCount += nlist;
1350                         }
1351                         else
1352                         {
1353                                 dataInnerPages++;
1354                                 dataInnerFreeSpace += header->pd_upper - header->pd_lower;
1355                                 dataInnerTuplesCount += GinPageGetOpaque(page)->maxoff;
1356                         }
1357                 }
1358                 else
1359                 {
1360                         IndexTuple itup;
1361                         OffsetNumber i, maxoff;
1362
1363                         maxoff = PageGetMaxOffsetNumber(page);
1364
1365                         entryPages++;
1366                         if (GinPageIsLeaf(page))
1367                         {
1368                                 entryLeafPages++;
1369                                 entryLeafFreeSpace += header->pd_upper - header->pd_lower;
1370                                 entryLeafTuplesCount += maxoff;
1371                         }
1372                         else
1373                         {
1374                                 entryInnerPages++;
1375                                 entryInnerFreeSpace += header->pd_upper - header->pd_lower;
1376                                 entryInnerTuplesCount += maxoff;
1377                         }
1378
1379                         for (i = 1; i <= maxoff; i++)
1380                         {
1381                                 itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, i));
1382
1383                                 if (GinPageIsLeaf(page))
1384                                 {
1385                                         GinPostingList *list = (GinPostingList *)GinGetPosting(itup);
1386                                         entryPostingCount += GinGetNPosting(itup);
1387                                         entryPostingSize += SizeOfGinPostingList(list);
1388                                         entryAttrSize += GinGetPostingOffset(itup) - IndexInfoFindDataOffset((itup)->t_info);
1389                                 }
1390                                 else
1391                                 {
1392                                         entryAttrSize += IndexTupleSize(itup) - IndexInfoFindDataOffset((itup)->t_info);
1393                                 }
1394                         }
1395                 }
1396
1397                 UnlockReleaseBuffer(buffer);
1398         }
1399
1400         index_close(index, AccessExclusiveLock);
1401         totalPages--;
1402
1403         snprintf(res, sizeof(res),
1404                          "totalPages:            %u\n"
1405                          "dataPages:             %u\n"
1406                          "dataInnerPages:        %u\n"
1407                          "dataLeafPages:         %u\n"
1408                          "dataInnerFreeSpace:    " INT64_FORMAT "\n"
1409                          "dataLeafFreeSpace:     " INT64_FORMAT "\n"
1410                          "dataInnerTuplesCount:  " INT64_FORMAT "\n"
1411                          "dataLeafIptrsCount:    " INT64_FORMAT "\n"
1412                          "entryPages:            %u\n"
1413                          "entryInnerPages:       %u\n"
1414                          "entryLeafPages:        %u\n"
1415                          "entryInnerFreeSpace:   " INT64_FORMAT "\n"
1416                          "entryLeafFreeSpace:    " INT64_FORMAT "\n"
1417                          "entryInnerTuplesCount: " INT64_FORMAT "\n"
1418                          "entryLeafTuplesCount:  " INT64_FORMAT "\n"
1419                          "entryPostingSize:      " INT64_FORMAT "\n"
1420                          "entryPostingCount:     " INT64_FORMAT "\n"
1421                          "entryAttrSize:         " INT64_FORMAT "\n"
1422                          ,
1423                          totalPages,
1424                          dataPages,
1425                          dataInnerPages,
1426                          dataLeafPages,
1427                          dataInnerFreeSpace,
1428                          dataLeafFreeSpace,
1429                          dataInnerTuplesCount,
1430                          dataLeafIptrsCount,
1431                          entryPages,
1432                          entryInnerPages,
1433                          entryLeafPages,
1434                          entryInnerFreeSpace,
1435                          entryLeafFreeSpace,
1436                          entryInnerTuplesCount,
1437                          entryLeafTuplesCount,
1438                          entryPostingSize,
1439                          entryPostingCount,
1440                          entryAttrSize
1441                          );
1442
1443         PG_RETURN_TEXT_P(CStringGetTextDatum(res));
1444 #endif
1445 }
1446