3 #include "access/genam.h"
4 #include "access/gin.h"
5 #if PG_VERSION_NUM >= 90100
6 #include "access/gin_private.h"
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"
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>
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"
30 #include <access/heapam.h>
31 #include <catalog/pg_type.h>
32 #include <access/relscan.h>
35 #define PAGESIZE (BLCKSZ - MAXALIGN(sizeof(PageHeaderData) + sizeof(ItemIdData)))
38 #define PG_NARGS() (fcinfo->nargs)
43 char *out=palloc( VARSIZE(in) );
44 memcpy(out, VARDATA(in), VARSIZE(in)-VARHDRSZ);
45 out[ VARSIZE(in)-VARHDRSZ ] ='\0';
56 static Relation checkOpenedRelation(Relation r, Oid PgAmOid);
58 #ifdef PG_MODULE_MAGIC
64 gist_index_open(RangeVar *relvar) {
65 #if PG_VERSION_NUM <= 90100
66 Oid relOid = RangeVarGetRelid(relvar, false);
68 Oid relOid = RangeVarGetRelid(relvar, NoLock, false);
70 return checkOpenedRelation(
71 index_open(relOid, AccessExclusiveLock), GIST_AM_OID);
74 #define gist_index_close(r) index_close((r), AccessExclusiveLock)
77 gin_index_open(RangeVar *relvar) {
78 #if PG_VERSION_NUM <= 90100
79 Oid relOid = RangeVarGetRelid(relvar, false);
81 Oid relOid = RangeVarGetRelid(relvar, NoLock, false);
83 return checkOpenedRelation(
84 index_open(relOid, AccessShareLock), GIN_AM_OID);
87 #define gin_index_close(r) index_close((r), AccessShareLock)
92 gist_index_open(RangeVar *relvar) {
93 Relation rel = index_openrv(relvar);
95 LockRelation(rel, AccessExclusiveLock);
96 return checkOpenedRelation(rel, GIST_AM_OID);
100 gist_index_close(Relation rel) {
101 UnlockRelation(rel, AccessExclusiveLock);
106 gin_index_open(RangeVar *relvar) {
107 Relation rel = index_openrv(relvar);
109 LockRelation(rel, AccessShareLock);
110 return checkOpenedRelation(rel, GIN_AM_OID);
114 gin_index_close(Relation rel) {
115 UnlockRelation(rel, AccessShareLock);
121 #if PG_VERSION_NUM >= 80300
122 #define stringToQualifiedNameList(x,y) stringToQualifiedNameList(x)
125 #if PG_VERSION_NUM < 80300
126 #define SET_VARSIZE(p,l) VARATT_SIZEP(p)=(l)
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)
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)
147 gist_dumptree(Relation r, int level, BlockNumber blk, OffsetNumber coff, IdxInfo *info) {
157 pred = (char *) palloc(sizeof(char) * level * 4 + 1);
158 MemSet(pred, ' ', level*4);
159 pred[level*4] = '\0';
161 buffer = ReadBuffer(r, blk);
162 page = (Page) BufferGetPage(buffer);
164 maxoff = PageGetMaxOffsetNumber(page);
167 while ( (info->ptr-((char*)info->txt)) + level*4 + 128 >= info->len ) {
168 int dist=info->ptr-((char*)info->txt);
170 info->txt=(text*)repalloc(info->txt, info->len);
171 info->ptr = ((char*)info->txt)+dist;
174 sprintf(info->ptr, "%s%d(l:%d) blk: %u numTuple: %d free: %db(%.2f%%) rightlink:%u (%s)\n",
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');
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);
193 ReleaseBuffer(buffer);
197 PG_FUNCTION_INFO_V1(gist_tree);
198 Datum gist_tree(PG_FUNCTION_ARGS);
200 gist_tree(PG_FUNCTION_ARGS) {
201 text *name=PG_GETARG_TEXT_P(0);
202 char *relname=t2c(name);
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);
213 info.maxlevel = ( PG_NARGS() > 1 ) ? PG_GETARG_INT32(1) : -1;
215 info.txt=(text*)palloc( info.len );
216 info.ptr=((char*)info.txt)+VARHDRSZ;
218 gist_dumptree(index, 0, GIST_ROOT_BLKNO, 0, &info);
220 gist_index_close(index);
223 SET_VARSIZE(info.txt, info.ptr-((char*)info.txt));
224 PG_RETURN_POINTER(info.txt);
235 uint64 leaftuplesize;
240 gist_stattree(Relation r, int level, BlockNumber blk, OffsetNumber coff, IdxStat *info) {
250 pred = (char *) palloc(sizeof(char) * level * 4 + 1);
251 MemSet(pred, ' ', level*4);
252 pred[level*4] = '\0';
254 buffer = ReadBuffer(r, blk);
255 page = (Page) BufferGetPage(buffer);
257 maxoff = PageGetMaxOffsetNumber(page);
260 info->tuplesize+=PAGESIZE-PageGetFreeSpace(page);
261 info->totalsize+=BLCKSZ;
262 info->numtuple+=maxoff;
263 if ( info->level < level )
266 if (GistPageIsLeaf(page)) {
267 info->numleafpages++;
268 info->leaftuplesize+=PAGESIZE-PageGetFreeSpace(page);
269 info->numleaftuple+=maxoff;
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);
281 ReleaseBuffer(buffer);
285 PG_FUNCTION_INFO_V1(gist_stat);
286 Datum gist_stat(PG_FUNCTION_ARGS);
288 gist_stat(PG_FUNCTION_ARGS) {
289 text *name=PG_GETARG_TEXT_P(0);
290 char *relname=t2c(name);
295 text *out=(text*)palloc(1024);
296 char *ptr=((char*)out)+VARHDRSZ;
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);
304 memset(&info, 0, sizeof(IdxStat));
306 gist_stattree(index, 0, GIST_ROOT_BLKNO, 0, &info);
308 gist_index_close(index);
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",
325 info.numinvalidtuple,
331 ptr=strchr(ptr,'\0');
333 SET_VARSIZE(out, ptr-((char*)out));
334 PG_RETURN_POINTER(out);
337 typedef struct GPItem {
355 openGPPage( FuncCallContext *funcctx, BlockNumber blk ) {
357 MemoryContext oldcontext;
358 Relation index = ( (TypeStorage*)(funcctx->user_fctx) )->index;
360 oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
361 nitem = (GPItem*)palloc( sizeof(GPItem) );
362 memset(nitem,0,sizeof(GPItem));
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;
371 MemoryContextSwitchTo(oldcontext);
376 closeGPPage( FuncCallContext *funcctx ) {
377 GPItem *oitem = ( (TypeStorage*)(funcctx->user_fctx) )->item;
379 ( (TypeStorage*)(funcctx->user_fctx) )->item = oitem->next;
381 ReleaseBuffer(oitem->buffer);
383 return ( (TypeStorage*)(funcctx->user_fctx) )->item;
387 setup_firstcall(FuncCallContext *funcctx, text *name) {
388 MemoryContext oldcontext;
390 char *relname=t2c(name);
392 char attname[NAMEDATALEN];
395 oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
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;
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);
413 st->index->rd_att->attrs[i]->atttypid,
414 st->index->rd_att->attrs[i]->atttypmod,
415 st->index->rd_att->attrs[i]->attndims
419 st->dvalues = (Datum *) palloc((tupdesc->natts+2) * sizeof(Datum));
420 st->nulls = (char *) palloc((tupdesc->natts+2) * sizeof(char));
422 funcctx->slot = TupleDescGetSlot(tupdesc);
423 funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
425 MemoryContextSwitchTo(oldcontext);
428 st->item=openGPPage(funcctx, GIST_ROOT_BLKNO);
432 close_call( FuncCallContext *funcctx ) {
433 TypeStorage *st = (TypeStorage*)(funcctx->user_fctx);
435 while( st->item && closeGPPage(funcctx) );
437 pfree( st->dvalues );
440 gist_index_close(st->index);
443 PG_FUNCTION_INFO_V1(gist_print);
444 Datum gist_print(PG_FUNCTION_ARGS);
446 gist_print(PG_FUNCTION_ARGS) {
447 FuncCallContext *funcctx;
449 Datum result=(Datum)0;
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);
463 funcctx = SRF_PERCALL_SETUP();
464 st = (TypeStorage*)(funcctx->user_fctx);
468 SRF_RETURN_DONE(funcctx);
471 while( st->item->offset > PageGetMaxOffsetNumber( st->item->page ) ) {
472 if ( ! closeGPPage(funcctx) ) {
474 SRF_RETURN_DONE(funcctx);
478 iid = PageGetItemId( st->item->page, st->item->offset );
479 ituple = (IndexTuple) PageGetItem(st->item->page, iid);
481 st->dvalues[0] = Int32GetDatum( st->item->level );
483 st->dvalues[1] = BoolGetDatum( (!GistPageIsLeaf(st->item->page) && GistTupleIsInvalid(ituple)) ? false : true );
485 for(i=2; i<funcctx->attinmeta->tupdesc->natts; i++) {
486 if ( !GistPageIsLeaf(st->item->page) && GistTupleIsInvalid(ituple) ) {
487 st->dvalues[i] = (Datum)0;
490 st->dvalues[i] = index_getattr(ituple, i-1, st->index->rd_att, &isnull);
491 st->nulls[i] = ( isnull ) ? 'n' : ' ';
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)) );
501 SRF_RETURN_NEXT(funcctx, result);
504 typedef struct GinStatState {
512 #if PG_VERSION_NUM >= 90100
513 GinNullCategory category;
520 moveRightIfItNeeded( GinStatState *st )
522 Page page = BufferGetPage(st->buffer);
524 if ( st->offset > PageGetMaxOffsetNumber(page) ) {
526 * We scaned the whole page, so we should take right page
528 BlockNumber blkno = GinPageGetOpaque(page)->rightlink;
530 if ( GinPageRightMost(page) )
531 return false; /* no more page */
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;
543 * Refinds a previois position, at returns it has correctly
544 * set offset and buffer is locked
547 refindPosition(GinStatState *st)
551 /* find left if needed (it causes only for first search) */
556 LockBuffer(st->buffer, GIN_SHARE);
558 page = BufferGetPage(st->buffer);
559 if (GinPageIsLeaf(page))
562 itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber));
563 blkno = GinItemPointerGetBlockNumber(&(itup)->t_tid);
565 LockBuffer(st->buffer,GIN_UNLOCK);
566 st->buffer = ReleaseAndReadBuffer(st->buffer, st->index, blkno);
569 if (st->offset == InvalidOffsetNumber) {
570 return (PageGetMaxOffsetNumber(page) >= FirstOffsetNumber ) ? true : false; /* first one */
575 #if PG_VERSION_NUM >= 90100
576 GinNullCategory category;
577 #elif PG_VERSION_NUM < 80400
583 if (moveRightIfItNeeded(st)==false)
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);
595 cmp = compareAttEntries(&st->ginstate,
596 st->attnum + 1, st->curval,
597 gintuple_get_attrnum(&st->ginstate, itup), datum);
599 datum = index_getattr(itup, FirstOffsetNumber, st->ginstate.tupdesc, &isnull);
603 &st->ginstate.compareFn,
610 if ( !st->index->rd_att->attrs[st->attnum]->attbyval )
611 pfree( (void*) st->curval );
622 gin_setup_firstcall(FuncCallContext *funcctx, text *name, int attnum) {
623 MemoryContext oldcontext;
625 char *relname=t2c(name);
628 oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
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 );
636 #if PG_VERSION_NUM >= 80400
637 if (attnum < 0 || attnum >= st->index->rd_att->natts)
638 elog(ERROR,"Wrong column's number");
644 funcctx->user_fctx = (void*)st;
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);
653 memset( st->nulls, ' ', 2*sizeof(char) );
655 funcctx->slot = TupleDescGetSlot(tupdesc);
656 funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
658 MemoryContextSwitchTo(oldcontext);
661 st->offset = InvalidOffsetNumber;
662 st->buffer = ReadBuffer( st->index, GIN_ROOT_BLKNO );
666 processTuple( FuncCallContext *funcctx, GinStatState *st, IndexTuple itup ) {
667 MemoryContext oldcontext;
668 #if PG_VERSION_NUM < 80400
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),
679 index_getattr(itup, FirstOffsetNumber, st->ginstate.tupdesc, &isnull),
681 st->index->rd_att->attrs[st->attnum]->attbyval,
682 st->index->rd_att->attrs[st->attnum]->attlen );
683 MemoryContextSwitchTo(oldcontext);
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';
691 if ( GinIsPostingTree(itup) ) {
692 BlockNumber rootblkno = GinGetPostingTree(itup);
693 GinPostingTreeScan *gdi;
697 LockBuffer(st->buffer, GIN_UNLOCK);
698 #if PG_VERSION_NUM >= 90100
699 gdi = ginPrepareScanPostingTree(st->index, rootblkno, TRUE);
700 entrybuffer = ginScanBeginPostingTree(gdi);
702 gdi = prepareScanPostingTree(st->index, rootblkno, TRUE);
703 entrybuffer = scanBeginPostingTree(gdi);
706 page = BufferGetPage(entrybuffer);
707 st->dvalues[1] = Int32GetDatum( gdi->stack->predictNumber * GinPageGetOpaque(page)->maxoff );
709 LockBuffer(entrybuffer, GIN_UNLOCK);
710 freeGinBtreeStack(gdi->stack);
713 st->dvalues[1] = Int32GetDatum( GinGetNPosting(itup) );
714 LockBuffer(st->buffer, GIN_UNLOCK);
718 PG_FUNCTION_INFO_V1(gin_stat);
719 Datum gin_stat(PG_FUNCTION_ARGS);
721 gin_stat(PG_FUNCTION_ARGS) {
722 FuncCallContext *funcctx;
724 Datum result=(Datum)0;
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);
736 funcctx = SRF_PERCALL_SETUP();
737 st = (GinStatState*)(funcctx->user_fctx);
739 if ( refindPosition(st) == false ) {
740 UnlockReleaseBuffer( st->buffer );
741 gin_index_close(st->index);
743 SRF_RETURN_DONE(funcctx);
749 if (moveRightIfItNeeded(st)==false) {
750 UnlockReleaseBuffer( st->buffer );
751 gin_index_close(st->index);
753 SRF_RETURN_DONE(funcctx);
756 page = BufferGetPage(st->buffer);
757 ituple = (IndexTuple) PageGetItem(page, PageGetItemId(page, st->offset));
759 #if PG_VERSION_NUM >= 80400
760 if (st->attnum + 1 == gintuple_get_attrnum(&st->ginstate, ituple))
765 processTuple( funcctx, st, ituple );
767 htuple = heap_formtuple(funcctx->attinmeta->tupdesc, st->dvalues, st->nulls);
768 result = TupleGetDatum(funcctx->slot, htuple);
770 SRF_RETURN_NEXT(funcctx, result);
773 PG_FUNCTION_INFO_V1(gin_count_estimate);
774 Datum gin_count_estimate(PG_FUNCTION_ARGS);
775 #if PG_VERSION_NUM >= 80300
777 gin_count_estimate(PG_FUNCTION_ARGS) {
778 text *name=PG_GETARG_TEXT_P(0);
782 char *relname=t2c(name);
784 #if PG_VERSION_NUM >= 80400
785 TIDBitmap *bitmap = tbm_create(work_mem * 1024L);
788 ItemPointerData tids[MAXTIDS];
793 index = gin_index_open(
794 makeRangeVarFromNameList(stringToQualifiedNameList(relname, "gin_count_estimate")));
796 if ( index->rd_opcintype[0] != TSVECTOROID ) {
797 gin_index_close(index);
798 elog(ERROR, "Column type is not a tsvector");
803 key.sk_strategy = TSearchStrategyNumber;
805 key.sk_argument = PG_GETARG_DATUM(1);
807 fmgr_info( F_TS_MATCH_VQ , &key.sk_func );
809 #if PG_VERSION_NUM >= 90100
810 scan = index_beginscan_bitmap(index, SnapshotNow, 1);
811 index_rescan(scan, &key, 1, NULL, 0);
813 count = index_getbitmap(scan, bitmap);
815 #elif PG_VERSION_NUM >= 80400
816 scan = index_beginscan_bitmap(index, SnapshotNow, 1, &key);
818 count = index_getbitmap(scan, bitmap);
821 scan = index_beginscan_multi(index, SnapshotNow, 1, &key);
824 more = index_getmulti(scan, tids, MAXTIDS, &returned_tids);
825 count += returned_tids;
829 index_endscan( scan );
830 gin_index_close(index);
832 PG_RETURN_INT64(count);
836 gin_count_estimate(PG_FUNCTION_ARGS) {
837 elog(NOTICE, "Function is not working under PgSQL < 8.3");
843 PG_FUNCTION_INFO_V1(spgist_stat);
844 Datum spgist_stat(PG_FUNCTION_ARGS);
846 spgist_stat(PG_FUNCTION_ARGS)
848 #if PG_VERSION_NUM < 90200
849 elog(NOTICE, "Function is not working under PgSQL < 9.2");
851 PG_RETURN_TEXT_P(CStringGetTextDatum("???"));
853 text *name = PG_GETARG_TEXT_P(0);
857 BlockNumber totalPages = 0,
862 double usedSpace = 0.0;
865 int64 innerTuples = 0,
868 nLeafPlaceholder = 0,
869 nInnerPlaceholder = 0,
873 #define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX)
874 #define IS_SPGIST(r) ((r)->rd_rel->relam == SPGIST_AM_OID)
876 relvar = makeRangeVarFromNameList(textToQualifiedNameList(name));
877 index = relation_openrv(relvar, AccessExclusiveLock);
879 if (!IS_INDEX(index) || !IS_SPGIST(index))
880 elog(ERROR, "relation \"%s\" is not an SPGiST index",
881 RelationGetRelationName(index));
883 totalPages = RelationGetNumberOfBlocks(index);
885 for (blkno = SPGIST_HEAD_BLKNO; blkno < totalPages; blkno++)
891 buffer = ReadBuffer(index, blkno);
892 LockBuffer(buffer, BUFFER_LOCK_SHARE);
894 page = BufferGetPage(buffer);
896 if (PageIsNew(page) || SpGistPageIsDeleted(page))
899 UnlockReleaseBuffer(buffer);
903 if (SpGistPageIsLeaf(page))
906 leafTuples += PageGetMaxOffsetNumber(page);
907 nLeafPlaceholder += SpGistPageGetOpaque(page)->nPlaceholder;
908 nLeafRedirect += SpGistPageGetOpaque(page)->nRedirection;
916 max = PageGetMaxOffsetNumber(page);
918 nInnerPlaceholder += SpGistPageGetOpaque(page)->nPlaceholder;
919 nInnerRedirect += SpGistPageGetOpaque(page)->nRedirection;
920 for (i = FirstOffsetNumber; i <= max; i++)
924 it = (SpGistInnerTuple) PageGetItem(page,
925 PageGetItemId(page, i));
932 bufferSize = BufferGetPageSize(buffer)
933 - MAXALIGN(sizeof(SpGistPageOpaqueData))
934 - SizeOfPageHeaderData;
936 pageFree = PageGetExactFreeSpace(page);
938 usedSpace += bufferSize - pageFree;
940 if (pageFree == bufferSize)
943 UnlockReleaseBuffer(buffer);
946 index_close(index, AccessExclusiveLock);
948 totalPages--; /* discount metapage */
950 snprintf(res, sizeof(res),
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,
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);
974 PG_RETURN_TEXT_P(CStringGetTextDatum(res));