spgist_stat
[gevel.git] / gevel.c
1 #include "postgres.h"
2
3 #include "access/genam.h"
4 #include "access/gin.h"
5 #if PG_VERSION_NUM >= 90100
6 #include "access/gin_private.h"
7 #endif
8 #include "access/gist.h"
9 #include "access/gist_private.h"
10 #include "access/gistscan.h"
11 #if PG_VERSION_NUM >= 90200
12 #include "access/spgist_private.h"
13 #include "access/spgist.h"
14 #endif
15 #include "access/heapam.h"
16 #include "catalog/index.h"
17 #include "miscadmin.h"
18 #include "storage/lmgr.h"
19 #include "catalog/namespace.h"
20 #if PG_VERSION_NUM >= 80300
21 #include <tsearch/ts_utils.h>
22 #endif
23 #include <utils/tqual.h>
24 #include "utils/builtins.h"
25 #include "utils/lsyscache.h"
26 #include "utils/datum.h"
27 #include "utils/fmgroids.h"
28 #include <fmgr.h>
29 #include <funcapi.h>
30 #include <access/heapam.h>
31 #include <catalog/pg_type.h>
32 #include <access/relscan.h>
33
34
35 #define PAGESIZE        (BLCKSZ - MAXALIGN(sizeof(PageHeaderData) + sizeof(ItemIdData)))
36
37 #ifndef PG_NARGS
38 #define PG_NARGS() (fcinfo->nargs)
39 #endif
40
41 static char
42 *t2c(text* in) {
43         char *out=palloc( VARSIZE(in) );
44         memcpy(out, VARDATA(in), VARSIZE(in)-VARHDRSZ);
45         out[ VARSIZE(in)-VARHDRSZ ] ='\0';
46         return out;
47 }
48
49 typedef struct {
50         int maxlevel;
51         text    *txt;
52         char    *ptr;
53         int     len;    
54 } IdxInfo;
55
56 static Relation checkOpenedRelation(Relation r, Oid PgAmOid);
57
58 #ifdef PG_MODULE_MAGIC
59 /* >= 8.2 */ 
60
61 PG_MODULE_MAGIC;
62
63 static Relation
64 gist_index_open(RangeVar *relvar) {
65 #if PG_VERSION_NUM <= 90100 
66         Oid relOid = RangeVarGetRelid(relvar, false);
67 #else
68         Oid relOid = RangeVarGetRelid(relvar, NoLock, false);
69 #endif
70         return checkOpenedRelation(
71                                 index_open(relOid, AccessExclusiveLock), GIST_AM_OID);
72 }
73
74 #define gist_index_close(r)     index_close((r), AccessExclusiveLock)
75
76 static Relation
77 gin_index_open(RangeVar *relvar) {
78 #if PG_VERSION_NUM <= 90100 
79         Oid relOid = RangeVarGetRelid(relvar, false);
80 #else
81         Oid relOid = RangeVarGetRelid(relvar, NoLock, false);
82 #endif
83         return checkOpenedRelation(
84                                 index_open(relOid, AccessShareLock), GIN_AM_OID);
85 }
86
87 #define gin_index_close(r) index_close((r), AccessShareLock)
88
89 #else /* <8.2 */
90
91 static Relation
92 gist_index_open(RangeVar *relvar) {
93         Relation rel = index_openrv(relvar);
94
95         LockRelation(rel, AccessExclusiveLock);
96         return checkOpenedRelation(rel, GIST_AM_OID);
97 }
98
99 static void
100 gist_index_close(Relation rel) {
101         UnlockRelation(rel, AccessExclusiveLock);
102         index_close(rel);
103 }
104
105 static Relation
106 gin_index_open(RangeVar *relvar) {
107         Relation rel = index_openrv(relvar);
108
109         LockRelation(rel, AccessShareLock);
110         return checkOpenedRelation(rel, GIN_AM_OID);
111 }
112
113 static void
114 gin_index_close(Relation rel) {
115         UnlockRelation(rel, AccessShareLock);
116         index_close(rel);
117 }
118
119 #endif
120
121 #if PG_VERSION_NUM >= 80300
122 #define stringToQualifiedNameList(x,y)  stringToQualifiedNameList(x)
123 #endif
124
125 #if PG_VERSION_NUM < 80300
126 #define SET_VARSIZE(p,l)        VARATT_SIZEP(p)=(l)
127 #endif
128
129 static Relation 
130 checkOpenedRelation(Relation r, Oid PgAmOid) {
131         if ( r->rd_am == NULL )
132                 elog(ERROR, "Relation %s.%s is not an index",
133                                         get_namespace_name(RelationGetNamespace(r)),
134                                         RelationGetRelationName(r)
135                         );
136
137         if ( r->rd_rel->relam != PgAmOid )
138                 elog(ERROR, "Index %s.%s has wrong type",
139                                         get_namespace_name(RelationGetNamespace(r)),
140                                         RelationGetRelationName(r)
141                         );
142         
143         return r;
144 }
145
146 static void
147 gist_dumptree(Relation r, int level, BlockNumber blk, OffsetNumber coff, IdxInfo *info) {
148         Buffer          buffer;
149         Page            page;
150         IndexTuple      which;
151         ItemId          iid;
152         OffsetNumber i,
153                                 maxoff;
154         BlockNumber cblk;
155         char       *pred;
156
157         pred = (char *) palloc(sizeof(char) * level * 4 + 1);
158         MemSet(pred, ' ', level*4);
159         pred[level*4] = '\0';
160
161         buffer = ReadBuffer(r, blk);
162         page = (Page) BufferGetPage(buffer);
163
164         maxoff = PageGetMaxOffsetNumber(page);
165
166
167         while ( (info->ptr-((char*)info->txt)) + level*4 + 128 >= info->len ) {
168                 int dist=info->ptr-((char*)info->txt);
169                 info->len *= 2;
170                 info->txt=(text*)repalloc(info->txt, info->len);
171                 info->ptr = ((char*)info->txt)+dist;
172         }
173
174         sprintf(info->ptr, "%s%d(l:%d) blk: %u numTuple: %d free: %db(%.2f%%) rightlink:%u (%s)\n", 
175                 pred,
176                 coff, 
177                 level, 
178                 blk,
179                 (int) maxoff, 
180                 (int) PageGetFreeSpace(page),  
181                 100.0*(((float)PAGESIZE)-(float)PageGetFreeSpace(page))/((float)PAGESIZE),
182                 GistPageGetOpaque(page)->rightlink,
183                 ( GistPageGetOpaque(page)->rightlink == InvalidBlockNumber ) ? "InvalidBlockNumber" : "OK" );
184         info->ptr=strchr(info->ptr,'\0');
185
186         if (!GistPageIsLeaf(page) && ( info->maxlevel<0 || level<info->maxlevel ) )
187                 for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) {
188                         iid = PageGetItemId(page, i);
189                         which = (IndexTuple) PageGetItem(page, iid);
190                         cblk = ItemPointerGetBlockNumber(&(which->t_tid));
191                         gist_dumptree(r, level + 1, cblk, i, info);
192                 }
193         ReleaseBuffer(buffer);
194         pfree(pred);
195 }
196
197 PG_FUNCTION_INFO_V1(gist_tree);
198 Datum   gist_tree(PG_FUNCTION_ARGS);
199 Datum
200 gist_tree(PG_FUNCTION_ARGS) {
201         text    *name=PG_GETARG_TEXT_P(0);
202         char *relname=t2c(name);
203         RangeVar   *relvar;
204         Relation        index;
205         List       *relname_list;
206         IdxInfo info;
207
208         relname_list = stringToQualifiedNameList(relname, "gist_tree");
209         relvar = makeRangeVarFromNameList(relname_list);
210         index = gist_index_open(relvar);
211         PG_FREE_IF_COPY(name,0);
212
213         info.maxlevel = ( PG_NARGS() > 1 ) ? PG_GETARG_INT32(1) : -1; 
214         info.len=1024;
215         info.txt=(text*)palloc( info.len );
216         info.ptr=((char*)info.txt)+VARHDRSZ;
217
218         gist_dumptree(index, 0, GIST_ROOT_BLKNO, 0, &info);
219
220         gist_index_close(index);
221         pfree(relname);
222
223         SET_VARSIZE(info.txt, info.ptr-((char*)info.txt));
224         PG_RETURN_POINTER(info.txt);
225 }
226
227 typedef struct {
228         int     level;
229         int     numpages;
230         int     numleafpages;
231         int     numtuple;
232         int     numinvalidtuple;
233         int     numleaftuple;
234         uint64  tuplesize;
235         uint64  leaftuplesize;
236         uint64  totalsize;
237 } IdxStat;
238
239 static void
240 gist_stattree(Relation r, int level, BlockNumber blk, OffsetNumber coff, IdxStat *info) {
241         Buffer          buffer;
242         Page            page;
243         IndexTuple      which;
244         ItemId          iid;
245         OffsetNumber i,
246                                 maxoff;
247         BlockNumber cblk;
248         char       *pred;
249
250         pred = (char *) palloc(sizeof(char) * level * 4 + 1);
251         MemSet(pred, ' ', level*4);
252         pred[level*4] = '\0';
253
254         buffer = ReadBuffer(r, blk);
255         page = (Page) BufferGetPage(buffer);
256
257         maxoff = PageGetMaxOffsetNumber(page);
258
259         info->numpages++;
260         info->tuplesize+=PAGESIZE-PageGetFreeSpace(page);
261         info->totalsize+=BLCKSZ;
262         info->numtuple+=maxoff;
263         if ( info->level < level )
264                 info->level = level;
265
266         if (GistPageIsLeaf(page)) {
267                 info->numleafpages++;
268                 info->leaftuplesize+=PAGESIZE-PageGetFreeSpace(page);
269                 info->numleaftuple+=maxoff;
270         } else {
271                 for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) {
272                         iid = PageGetItemId(page, i);
273                         which = (IndexTuple) PageGetItem(page, iid);
274                         if ( GistTupleIsInvalid(which) )
275                                 info->numinvalidtuple++;
276                         cblk = ItemPointerGetBlockNumber(&(which->t_tid));
277                                 gist_stattree(r, level + 1, cblk, i, info);
278                 }
279         }
280
281         ReleaseBuffer(buffer);
282         pfree(pred);
283 }
284
285 PG_FUNCTION_INFO_V1(gist_stat);
286 Datum   gist_stat(PG_FUNCTION_ARGS);
287 Datum
288 gist_stat(PG_FUNCTION_ARGS) {
289         text    *name=PG_GETARG_TEXT_P(0);
290         char *relname=t2c(name);
291         RangeVar   *relvar;
292         Relation        index;
293         List       *relname_list;
294         IdxStat info;
295         text *out=(text*)palloc(1024);
296         char *ptr=((char*)out)+VARHDRSZ;
297
298
299         relname_list = stringToQualifiedNameList(relname, "gist_tree");
300         relvar = makeRangeVarFromNameList(relname_list);
301         index = gist_index_open(relvar);
302         PG_FREE_IF_COPY(name,0);
303
304         memset(&info, 0, sizeof(IdxStat));
305
306         gist_stattree(index, 0, GIST_ROOT_BLKNO, 0, &info);
307
308         gist_index_close(index);
309         pfree(relname);
310
311         sprintf(ptr, 
312                 "Number of levels:          %d\n"
313                 "Number of pages:           %d\n"
314                 "Number of leaf pages:      %d\n"
315                 "Number of tuples:          %d\n"
316                 "Number of invalid tuples:  %d\n"
317                 "Number of leaf tuples:     %d\n"
318                 "Total size of tuples:      "INT64_FORMAT" bytes\n"
319                 "Total size of leaf tuples: "INT64_FORMAT" bytes\n"
320                 "Total size of index:       "INT64_FORMAT" bytes\n",
321                 info.level+1,
322                 info.numpages,
323                 info.numleafpages,
324                 info.numtuple,
325                 info.numinvalidtuple,
326                 info.numleaftuple,
327                 info.tuplesize,
328                 info.leaftuplesize,
329                 info.totalsize);
330
331         ptr=strchr(ptr,'\0');
332                  
333         SET_VARSIZE(out, ptr-((char*)out));
334         PG_RETURN_POINTER(out);
335 }
336
337 typedef struct GPItem {
338         Buffer  buffer;
339         Page    page;
340         OffsetNumber    offset;
341         int     level;
342         struct GPItem *next;
343 } GPItem;
344
345 typedef struct {
346         List    *relname_list;
347         RangeVar   *relvar;
348         Relation        index;
349         Datum   *dvalues;
350         char    *nulls;
351         GPItem  *item;
352 } TypeStorage;
353
354 static GPItem*
355 openGPPage( FuncCallContext *funcctx, BlockNumber blk ) {
356         GPItem  *nitem;
357         MemoryContext     oldcontext;
358         Relation index = ( (TypeStorage*)(funcctx->user_fctx) )->index;
359         
360         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
361         nitem = (GPItem*)palloc( sizeof(GPItem) );
362         memset(nitem,0,sizeof(GPItem));
363
364         nitem->buffer = ReadBuffer(index, blk);
365         nitem->page = (Page) BufferGetPage(nitem->buffer);
366         nitem->offset=FirstOffsetNumber;
367         nitem->next = ( (TypeStorage*)(funcctx->user_fctx) )->item;
368         nitem->level = ( nitem->next ) ? nitem->next->level+1 : 1; 
369         ( (TypeStorage*)(funcctx->user_fctx) )->item = nitem;
370
371         MemoryContextSwitchTo(oldcontext);
372         return nitem;
373
374
375 static GPItem*
376 closeGPPage( FuncCallContext *funcctx ) {
377         GPItem  *oitem = ( (TypeStorage*)(funcctx->user_fctx) )->item;
378
379         ( (TypeStorage*)(funcctx->user_fctx) )->item = oitem->next;
380         
381         ReleaseBuffer(oitem->buffer);
382         pfree( oitem );
383         return ( (TypeStorage*)(funcctx->user_fctx) )->item; 
384 }
385
386 static void
387 setup_firstcall(FuncCallContext  *funcctx, text *name) {
388         MemoryContext     oldcontext;
389         TypeStorage     *st;
390         char *relname=t2c(name);
391         TupleDesc            tupdesc;
392         char            attname[NAMEDATALEN];
393         int i;
394
395         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
396
397         st=(TypeStorage*)palloc( sizeof(TypeStorage) );
398         memset(st,0,sizeof(TypeStorage));
399         st->relname_list = stringToQualifiedNameList(relname, "gist_tree");
400         st->relvar = makeRangeVarFromNameList(st->relname_list);
401         st->index = gist_index_open(st->relvar);
402         funcctx->user_fctx = (void*)st;
403
404         tupdesc = CreateTemplateTupleDesc(st->index->rd_att->natts+2, false);
405         TupleDescInitEntry(tupdesc, 1, "level", INT4OID, -1, 0);
406         TupleDescInitEntry(tupdesc, 2, "valid", BOOLOID, -1, 0);
407         for (i = 0; i < st->index->rd_att->natts; i++) {
408                 sprintf(attname, "z%d", i+2);
409                 TupleDescInitEntry(
410                         tupdesc,
411                         i+3,
412                         attname,
413                         st->index->rd_att->attrs[i]->atttypid,
414                         st->index->rd_att->attrs[i]->atttypmod,
415                         st->index->rd_att->attrs[i]->attndims
416                 );
417         }
418
419         st->dvalues = (Datum *) palloc((tupdesc->natts+2) * sizeof(Datum));
420         st->nulls = (char *) palloc((tupdesc->natts+2) * sizeof(char));
421
422         funcctx->slot = TupleDescGetSlot(tupdesc);
423         funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
424
425         MemoryContextSwitchTo(oldcontext);
426         pfree(relname);
427
428         st->item=openGPPage(funcctx, GIST_ROOT_BLKNO);
429
430
431 static void 
432 close_call( FuncCallContext  *funcctx ) {
433         TypeStorage *st = (TypeStorage*)(funcctx->user_fctx);
434         
435         while( st->item && closeGPPage(funcctx) );
436         
437         pfree( st->dvalues );
438         pfree( st->nulls );
439
440         gist_index_close(st->index);
441 }
442
443 PG_FUNCTION_INFO_V1(gist_print);
444 Datum   gist_print(PG_FUNCTION_ARGS);
445 Datum
446 gist_print(PG_FUNCTION_ARGS) {
447         FuncCallContext  *funcctx;
448         TypeStorage     *st;
449         Datum result=(Datum)0;
450         ItemId          iid;
451         IndexTuple      ituple;
452         HeapTuple       htuple;
453         int i;
454         bool isnull;
455
456         if (SRF_IS_FIRSTCALL()) {
457                 text    *name=PG_GETARG_TEXT_P(0);
458                 funcctx = SRF_FIRSTCALL_INIT();
459                 setup_firstcall(funcctx, name);
460                 PG_FREE_IF_COPY(name,0);
461         }
462
463         funcctx = SRF_PERCALL_SETUP();  
464         st = (TypeStorage*)(funcctx->user_fctx);
465
466         if ( !st->item ) {
467                 close_call(funcctx);
468                 SRF_RETURN_DONE(funcctx);
469         }
470
471         while( st->item->offset > PageGetMaxOffsetNumber( st->item->page ) ) {
472                 if ( ! closeGPPage(funcctx) ) {
473                         close_call(funcctx);
474                         SRF_RETURN_DONE(funcctx);
475                 } 
476         }
477
478         iid = PageGetItemId( st->item->page, st->item->offset );
479         ituple = (IndexTuple) PageGetItem(st->item->page, iid);
480
481         st->dvalues[0] = Int32GetDatum( st->item->level );
482         st->nulls[0] = ' ';
483         st->dvalues[1] = BoolGetDatum( (!GistPageIsLeaf(st->item->page) && GistTupleIsInvalid(ituple)) ? false : true );
484         st->nulls[1] = ' ';
485         for(i=2; i<funcctx->attinmeta->tupdesc->natts; i++) {
486                 if ( !GistPageIsLeaf(st->item->page) && GistTupleIsInvalid(ituple) ) {
487                         st->dvalues[i] = (Datum)0;
488                         st->nulls[i] = 'n';
489                 } else {
490                         st->dvalues[i] = index_getattr(ituple, i-1, st->index->rd_att, &isnull); 
491                         st->nulls[i] = ( isnull ) ? 'n' : ' ';
492                 }
493         }
494
495         htuple = heap_formtuple(funcctx->attinmeta->tupdesc, st->dvalues, st->nulls);
496         result = TupleGetDatum(funcctx->slot, htuple);
497         st->item->offset = OffsetNumberNext(st->item->offset);
498         if ( !GistPageIsLeaf(st->item->page) )
499                 openGPPage(funcctx, ItemPointerGetBlockNumber(&(ituple->t_tid)) );
500
501         SRF_RETURN_NEXT(funcctx, result);
502 }
503
504 typedef struct GinStatState {
505         Relation                index;
506         GinState                ginstate;
507         OffsetNumber    attnum;
508
509         Buffer                  buffer;
510         OffsetNumber    offset;
511         Datum                   curval;
512 #if PG_VERSION_NUM >= 90100
513         GinNullCategory category;
514 #endif
515         Datum                   dvalues[2];
516         char                    nulls[2];
517 } GinStatState;
518
519 static bool
520 moveRightIfItNeeded( GinStatState *st )
521 {
522         Page page = BufferGetPage(st->buffer);
523
524         if ( st->offset > PageGetMaxOffsetNumber(page) ) {
525                 /*
526                 * We scaned the whole page, so we should take right page
527                 */
528                 BlockNumber blkno = GinPageGetOpaque(page)->rightlink;               
529
530                 if ( GinPageRightMost(page) )
531                         return false;  /* no more page */
532
533                 LockBuffer(st->buffer, GIN_UNLOCK);
534                 st->buffer = ReleaseAndReadBuffer(st->buffer, st->index, blkno);
535                 LockBuffer(st->buffer, GIN_SHARE);
536                 st->offset = FirstOffsetNumber;
537         }
538
539         return true;
540 }
541
542 /*
543  * Refinds a previois position, at returns it has correctly 
544  * set offset and buffer is locked
545  */
546 static bool
547 refindPosition(GinStatState *st)
548 {
549         Page    page;        
550
551         /* find left if needed (it causes only for first search) */
552         for (;;) {
553                 IndexTuple  itup;
554                 BlockNumber blkno;
555
556                 LockBuffer(st->buffer, GIN_SHARE);
557
558                 page = BufferGetPage(st->buffer);
559                 if (GinPageIsLeaf(page))
560                         break;
561
562                 itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber));
563                 blkno = GinItemPointerGetBlockNumber(&(itup)->t_tid);
564
565                 LockBuffer(st->buffer,GIN_UNLOCK);
566                 st->buffer = ReleaseAndReadBuffer(st->buffer, st->index, blkno);
567         }
568
569         if (st->offset == InvalidOffsetNumber) {
570                 return (PageGetMaxOffsetNumber(page) >= FirstOffsetNumber ) ? true : false; /* first one */
571         }
572
573         for(;;) {
574                 int                     cmp;
575 #if PG_VERSION_NUM >= 90100
576                 GinNullCategory category;
577 #elif PG_VERSION_NUM < 80400
578                 bool                    isnull = false;
579 #endif
580                 Datum                   datum;
581                 IndexTuple              itup;
582
583                 if (moveRightIfItNeeded(st)==false)
584                         return false;
585
586                 itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, st->offset));
587 #if PG_VERSION_NUM >= 90100
588                 datum = gintuple_get_key(&st->ginstate, itup, &category); 
589                 cmp = ginCompareAttEntries(&st->ginstate,
590                                                                         st->attnum + 1, st->curval, st->category,
591                                                                         gintuple_get_attrnum(&st->ginstate, itup), datum, category);
592 #elif PG_VERSION_NUM >= 80400
593                 datum = gin_index_getattr(&st->ginstate, itup);
594
595                 cmp = compareAttEntries(&st->ginstate,
596                                                                         st->attnum + 1, st->curval,
597                                                                         gintuple_get_attrnum(&st->ginstate, itup), datum);
598 #else
599                 datum = index_getattr(itup, FirstOffsetNumber, st->ginstate.tupdesc, &isnull);
600
601                 cmp = DatumGetInt32(
602                                 FunctionCall2(
603                                                 &st->ginstate.compareFn,
604                                                 st->curval,
605                                                 datum
606                                         ));
607 #endif
608                 if ( cmp == 0 )
609                 {
610                         if ( !st->index->rd_att->attrs[st->attnum]->attbyval )
611                                 pfree( (void*) st->curval );
612                         return true;
613                 }
614
615                 st->offset++;
616         }
617
618         return false;
619 }
620
621 static void
622 gin_setup_firstcall(FuncCallContext  *funcctx, text *name, int attnum) {
623         MemoryContext     oldcontext;
624         GinStatState     *st;
625         char *relname=t2c(name);
626         TupleDesc            tupdesc;
627
628         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
629
630         st=(GinStatState*)palloc( sizeof(GinStatState) );
631         memset(st,0,sizeof(GinStatState));
632         st->index = gin_index_open(
633                  makeRangeVarFromNameList(stringToQualifiedNameList(relname, "gin_stat")));
634         initGinState( &st->ginstate, st->index );
635
636 #if PG_VERSION_NUM >= 80400
637         if (attnum < 0 || attnum >= st->index->rd_att->natts)
638                 elog(ERROR,"Wrong column's number");
639         st->attnum = attnum;
640 #else
641         st->attnum = 0;
642 #endif
643
644         funcctx->user_fctx = (void*)st;
645
646         tupdesc = CreateTemplateTupleDesc(2, false);
647         TupleDescInitEntry(tupdesc, 1, "value", 
648                         st->index->rd_att->attrs[st->attnum]->atttypid, 
649                         st->index->rd_att->attrs[st->attnum]->atttypmod,
650                         st->index->rd_att->attrs[st->attnum]->attndims);
651         TupleDescInitEntry(tupdesc, 2, "nrow", INT4OID, -1, 0);
652
653         memset( st->nulls, ' ', 2*sizeof(char) );
654
655         funcctx->slot = TupleDescGetSlot(tupdesc);
656         funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
657
658         MemoryContextSwitchTo(oldcontext);
659         pfree(relname);
660
661         st->offset = InvalidOffsetNumber;
662         st->buffer = ReadBuffer( st->index, GIN_ROOT_BLKNO );
663 }
664
665 static void
666 processTuple( FuncCallContext  *funcctx,  GinStatState *st, IndexTuple itup ) {
667         MemoryContext           oldcontext;
668 #if PG_VERSION_NUM < 80400
669         bool                            isnull;
670 #endif
671
672         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
673         st->curval = datumCopy(
674 #if PG_VERSION_NUM >= 90100
675                                         gintuple_get_key(&st->ginstate, itup, &st->category),
676 #elif PG_VERSION_NUM >= 80400
677                                         gin_index_getattr(&st->ginstate, itup),
678 #else
679                                         index_getattr(itup, FirstOffsetNumber, st->ginstate.tupdesc, &isnull),
680 #endif
681                                         st->index->rd_att->attrs[st->attnum]->attbyval,
682                                         st->index->rd_att->attrs[st->attnum]->attlen );
683         MemoryContextSwitchTo(oldcontext);
684
685         st->dvalues[0] = st->curval;
686 #if PG_VERSION_NUM >= 90100
687         /* do no distiguish various null category */
688         st->nulls[0] = (st->category == GIN_CAT_NORM_KEY) ? ' ' : 'n';
689 #endif
690                 
691         if ( GinIsPostingTree(itup) ) {
692                 BlockNumber     rootblkno = GinGetPostingTree(itup);
693                 GinPostingTreeScan *gdi;
694                 Buffer          entrybuffer;              
695                 Page        page;
696
697                 LockBuffer(st->buffer, GIN_UNLOCK);
698 #if PG_VERSION_NUM >= 90100
699                 gdi = ginPrepareScanPostingTree(st->index, rootblkno, TRUE);
700                 entrybuffer = ginScanBeginPostingTree(gdi);
701 #else
702                 gdi = prepareScanPostingTree(st->index, rootblkno, TRUE);
703                 entrybuffer = scanBeginPostingTree(gdi);
704 #endif
705
706                 page = BufferGetPage(entrybuffer);
707                 st->dvalues[1] = Int32GetDatum( gdi->stack->predictNumber * GinPageGetOpaque(page)->maxoff );
708
709                 LockBuffer(entrybuffer, GIN_UNLOCK);
710                 freeGinBtreeStack(gdi->stack);
711                 pfree(gdi);
712         } else {
713                 st->dvalues[1] = Int32GetDatum( GinGetNPosting(itup) );
714                 LockBuffer(st->buffer, GIN_UNLOCK);
715         }
716 }
717
718 PG_FUNCTION_INFO_V1(gin_stat);
719 Datum   gin_stat(PG_FUNCTION_ARGS);
720 Datum
721 gin_stat(PG_FUNCTION_ARGS) {
722         FuncCallContext  *funcctx;
723         GinStatState     *st;
724         Datum result=(Datum)0;
725         IndexTuple      ituple;
726         HeapTuple       htuple;
727         Page page;
728
729         if (SRF_IS_FIRSTCALL()) {
730                 text    *name=PG_GETARG_TEXT_P(0);
731                 funcctx = SRF_FIRSTCALL_INIT();
732                 gin_setup_firstcall(funcctx, name, (PG_NARGS()==2) ? PG_GETARG_INT32(1) : 0 );
733                 PG_FREE_IF_COPY(name,0);
734         }
735
736         funcctx = SRF_PERCALL_SETUP();  
737         st = (GinStatState*)(funcctx->user_fctx);
738
739         if ( refindPosition(st) == false ) {
740                 UnlockReleaseBuffer( st->buffer );
741                 gin_index_close(st->index);
742
743                 SRF_RETURN_DONE(funcctx);
744         }
745
746         for(;;) {
747                 st->offset++;
748         
749                 if (moveRightIfItNeeded(st)==false) { 
750                         UnlockReleaseBuffer( st->buffer );
751                         gin_index_close(st->index);
752
753                         SRF_RETURN_DONE(funcctx);
754                 }
755
756                 page = BufferGetPage(st->buffer);
757                 ituple = (IndexTuple) PageGetItem(page, PageGetItemId(page, st->offset)); 
758
759 #if PG_VERSION_NUM >= 80400
760                 if (st->attnum + 1 == gintuple_get_attrnum(&st->ginstate, ituple))
761 #endif
762                         break;
763         }
764
765         processTuple( funcctx,  st, ituple );
766         
767         htuple = heap_formtuple(funcctx->attinmeta->tupdesc, st->dvalues, st->nulls);
768         result = TupleGetDatum(funcctx->slot, htuple);
769
770         SRF_RETURN_NEXT(funcctx, result);
771 }
772
773 PG_FUNCTION_INFO_V1(gin_count_estimate);
774 Datum gin_count_estimate(PG_FUNCTION_ARGS);
775 #if PG_VERSION_NUM >= 80300
776 Datum
777 gin_count_estimate(PG_FUNCTION_ARGS) {
778         text                    *name=PG_GETARG_TEXT_P(0);
779         Relation                index;
780         IndexScanDesc   scan;
781         int64                   count = 0;
782         char                    *relname=t2c(name);
783         ScanKeyData             key;
784 #if PG_VERSION_NUM >= 80400
785         TIDBitmap               *bitmap = tbm_create(work_mem * 1024L);
786 #else
787 #define MAXTIDS         1024
788         ItemPointerData tids[MAXTIDS];
789         int32                   returned_tids;
790         bool                    more;
791 #endif
792
793         index = gin_index_open(
794                  makeRangeVarFromNameList(stringToQualifiedNameList(relname, "gin_count_estimate")));
795
796         if ( index->rd_opcintype[0] != TSVECTOROID ) {
797                 gin_index_close(index);
798                 elog(ERROR, "Column type is not a tsvector");
799         }
800
801         key.sk_flags    = 0;
802         key.sk_attno    = 1;
803         key.sk_strategy = TSearchStrategyNumber;
804         key.sk_subtype  = 0;
805         key.sk_argument = PG_GETARG_DATUM(1);
806
807         fmgr_info( F_TS_MATCH_VQ , &key.sk_func );
808
809 #if PG_VERSION_NUM >= 90100
810         scan = index_beginscan_bitmap(index, SnapshotNow, 1);
811         index_rescan(scan, &key, 1, NULL, 0);
812
813         count = index_getbitmap(scan, bitmap);
814         tbm_free(bitmap);
815 #elif PG_VERSION_NUM >= 80400
816         scan = index_beginscan_bitmap(index, SnapshotNow, 1, &key);
817
818         count = index_getbitmap(scan, bitmap);
819         tbm_free(bitmap);
820 #else
821         scan = index_beginscan_multi(index, SnapshotNow, 1, &key);
822
823         do {
824                 more = index_getmulti(scan, tids, MAXTIDS, &returned_tids);
825                 count += returned_tids;
826         } while(more);
827 #endif
828
829         index_endscan( scan );
830         gin_index_close(index);
831
832         PG_RETURN_INT64(count);
833 }
834 #else
835 Datum
836 gin_count_estimate(PG_FUNCTION_ARGS) {
837         elog(NOTICE, "Function is not working under PgSQL < 8.3");
838
839         PG_RETURN_INT64(0);
840 }
841 #endif
842
843 PG_FUNCTION_INFO_V1(spgist_stat);
844 Datum spgist_stat(PG_FUNCTION_ARGS);
845 Datum
846 spgist_stat(PG_FUNCTION_ARGS)
847 {
848 #if PG_VERSION_NUM < 90200
849         elog(NOTICE, "Function is not working under PgSQL < 9.2");
850
851         PG_RETURN_TEXT_P(CStringGetTextDatum("???"));
852 #else
853         text       *name = PG_GETARG_TEXT_P(0);
854         RangeVar   *relvar;
855         Relation        index;
856         BlockNumber blkno;
857         BlockNumber totalPages = 0,
858                                 innerPages = 0,
859                                 leafPages = 0,
860                                 emptyPages = 0,
861                                 deletedPages = 0;
862         double    usedSpace = 0.0;
863         char            res[1024];
864         int              bufferSize = -1;
865         int64      innerTuples = 0,
866                                 leafTuples = 0,
867                                 nAllTheSame = 0,
868                                 nLeafPlaceholder = 0,
869                                 nInnerPlaceholder = 0,
870                                 nLeafRedirect = 0,
871                                 nInnerRedirect = 0;
872
873 #define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX)
874 #define IS_SPGIST(r) ((r)->rd_rel->relam == SPGIST_AM_OID)
875
876         relvar = makeRangeVarFromNameList(textToQualifiedNameList(name));
877         index = relation_openrv(relvar, AccessExclusiveLock);
878
879         if (!IS_INDEX(index) || !IS_SPGIST(index))
880                 elog(ERROR, "relation \"%s\" is not an SPGiST index",
881                          RelationGetRelationName(index));
882
883         totalPages = RelationGetNumberOfBlocks(index);
884
885         for (blkno = SPGIST_HEAD_BLKNO; blkno < totalPages; blkno++)
886         {
887                 Buffer    buffer;
888                 Page            page;
889                 int              pageFree;
890
891                 buffer = ReadBuffer(index, blkno);
892                 LockBuffer(buffer, BUFFER_LOCK_SHARE);
893
894                 page = BufferGetPage(buffer);
895
896                 if (PageIsNew(page) || SpGistPageIsDeleted(page))
897                 {
898                         deletedPages++;
899                         UnlockReleaseBuffer(buffer);
900                         continue;
901                 }
902
903                 if (SpGistPageIsLeaf(page))
904                 {
905                         leafPages++;
906                         leafTuples += PageGetMaxOffsetNumber(page);
907                         nLeafPlaceholder += SpGistPageGetOpaque(page)->nPlaceholder;
908                         nLeafRedirect += SpGistPageGetOpaque(page)->nRedirection;
909                 }
910                 else
911                 {
912                         int      i,
913                                         max;
914
915                         innerPages++;
916                         max = PageGetMaxOffsetNumber(page);
917                         innerTuples += max;
918                         nInnerPlaceholder += SpGistPageGetOpaque(page)->nPlaceholder;
919                         nInnerRedirect += SpGistPageGetOpaque(page)->nRedirection;
920                         for (i = FirstOffsetNumber; i <= max; i++)
921                         {
922                                 SpGistInnerTuple it;
923
924                                 it = (SpGistInnerTuple) PageGetItem(page,
925                                                                                                         PageGetItemId(page, i));
926                                 if (it->allTheSame)
927                                         nAllTheSame++;
928                         }
929                 }
930
931                 if (bufferSize < 0)
932                         bufferSize = BufferGetPageSize(buffer)
933                                 - MAXALIGN(sizeof(SpGistPageOpaqueData))
934                                 - SizeOfPageHeaderData;
935
936                 pageFree = PageGetExactFreeSpace(page);
937
938                 usedSpace += bufferSize - pageFree;
939
940                 if (pageFree == bufferSize)
941                         emptyPages++;
942
943                 UnlockReleaseBuffer(buffer);
944         }
945
946         index_close(index, AccessExclusiveLock);
947
948         totalPages--;                      /* discount metapage */
949
950         snprintf(res, sizeof(res),
951                          "totalPages:        %u\n"
952                          "deletedPages:      %u\n"
953                          "innerPages:        %u\n"
954                          "leafPages:         %u\n"
955                          "emptyPages:        %u\n"
956                          "usedSpace:         %.2f kbytes\n"
957                          "freeSpace:         %.2f kbytes\n"
958                          "fillRatio:         %.2f%%\n"
959                          "leafTuples:        " INT64_FORMAT "\n"
960                          "innerTuples:       " INT64_FORMAT "\n"
961                          "innerAllTheSame:   " INT64_FORMAT "\n"
962                          "leafPlaceholders:  " INT64_FORMAT "\n"
963                          "innerPlaceholders: " INT64_FORMAT "\n"
964                          "leafRedirects:     " INT64_FORMAT "\n"
965                          "innerRedirects:    " INT64_FORMAT,
966                          totalPages, deletedPages, innerPages, leafPages, emptyPages,
967                          usedSpace / 1024.0,
968                          (((double) bufferSize) * ((double) totalPages) - usedSpace) / 1024,
969                          100.0 * (usedSpace / (((double) bufferSize) * ((double) totalPages))),
970                          leafTuples, innerTuples, nAllTheSame,
971                          nLeafPlaceholder, nInnerPlaceholder,
972                          nLeafRedirect, nInnerRedirect);
973
974         PG_RETURN_TEXT_P(CStringGetTextDatum(res));
975 #endif
976 }
977