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