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