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