#include "postgres.h"
#include "access/genam.h"
+#include "access/gin.h"
+#if PG_VERSION_NUM >= 90100
+#include "access/gin_private.h"
+#endif
#include "access/gist.h"
#include "access/gist_private.h"
#include "access/gistscan.h"
+#if PG_VERSION_NUM >= 90200
+#include "access/spgist_private.h"
+#include "access/spgist.h"
+#endif
#include "access/heapam.h"
#include "catalog/index.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "catalog/namespace.h"
+#if PG_VERSION_NUM >= 80300
+#include <tsearch/ts_utils.h>
+#endif
+#include <utils/tqual.h>
#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/datum.h"
+#include "utils/fmgroids.h"
#include <fmgr.h>
#include <funcapi.h>
#include <access/heapam.h>
#include <catalog/pg_type.h>
+#include <access/relscan.h>
+
#define PAGESIZE (BLCKSZ - MAXALIGN(sizeof(PageHeaderData) + sizeof(ItemIdData)))
int len;
} IdxInfo;
+static Relation checkOpenedRelation(Relation r, Oid PgAmOid);
#ifdef PG_MODULE_MAGIC
/* >= 8.2 */
static Relation
gist_index_open(RangeVar *relvar) {
+#if PG_VERSION_NUM <= 90100
Oid relOid = RangeVarGetRelid(relvar, false);
- return index_open(relOid, AccessExclusiveLock);
+#else
+ Oid relOid = RangeVarGetRelid(relvar, NoLock, false);
+#endif
+ return checkOpenedRelation(
+ index_open(relOid, AccessExclusiveLock), GIST_AM_OID);
}
#define gist_index_close(r) index_close((r), AccessExclusiveLock)
+static Relation
+gin_index_open(RangeVar *relvar) {
+#if PG_VERSION_NUM <= 90100
+ Oid relOid = RangeVarGetRelid(relvar, false);
+#else
+ Oid relOid = RangeVarGetRelid(relvar, NoLock, false);
+#endif
+ return checkOpenedRelation(
+ index_open(relOid, AccessShareLock), GIN_AM_OID);
+}
+
+#define gin_index_close(r) index_close((r), AccessShareLock)
+
#else /* <8.2 */
static Relation
Relation rel = index_openrv(relvar);
LockRelation(rel, AccessExclusiveLock);
- return rel;
+ return checkOpenedRelation(rel, GIST_AM_OID);
}
static void
index_close(rel);
}
+static Relation
+gin_index_open(RangeVar *relvar) {
+ Relation rel = index_openrv(relvar);
+
+ LockRelation(rel, AccessShareLock);
+ return checkOpenedRelation(rel, GIN_AM_OID);
+}
+
+static void
+gin_index_close(Relation rel) {
+ UnlockRelation(rel, AccessShareLock);
+ index_close(rel);
+}
+
#endif
+#if PG_VERSION_NUM >= 80300
+#define stringToQualifiedNameList(x,y) stringToQualifiedNameList(x)
+#endif
+
+#if PG_VERSION_NUM < 80300
+#define SET_VARSIZE(p,l) VARATT_SIZEP(p)=(l)
+#endif
+
+static Relation
+checkOpenedRelation(Relation r, Oid PgAmOid) {
+ if ( r->rd_am == NULL )
+ elog(ERROR, "Relation %s.%s is not an index",
+ get_namespace_name(RelationGetNamespace(r)),
+ RelationGetRelationName(r)
+ );
+
+ if ( r->rd_rel->relam != PgAmOid )
+ elog(ERROR, "Index %s.%s has wrong type",
+ get_namespace_name(RelationGetNamespace(r)),
+ RelationGetRelationName(r)
+ );
+
+ return r;
+}
+
static void
gist_dumptree(Relation r, int level, BlockNumber blk, OffsetNumber coff, IdxInfo *info) {
Buffer buffer;
info->ptr = ((char*)info->txt)+dist;
}
- sprintf(info->ptr, "%s%d(l:%d) blk: %d numTuple: %d free: %db(%.2f%%) rightlink:%u (%s)\n",
+ sprintf(info->ptr, "%s%d(l:%d) blk: %u numTuple: %d free: %db(%.2f%%) rightlink:%u (%s)\n",
pred,
coff,
level,
- (int) blk,
+ blk,
(int) maxoff,
- PageGetFreeSpace(page),
+ (int) PageGetFreeSpace(page),
100.0*(((float)PAGESIZE)-(float)PageGetFreeSpace(page))/((float)PAGESIZE),
GistPageGetOpaque(page)->rightlink,
( GistPageGetOpaque(page)->rightlink == InvalidBlockNumber ) ? "InvalidBlockNumber" : "OK" );
List *relname_list;
IdxInfo info;
-
relname_list = stringToQualifiedNameList(relname, "gist_tree");
relvar = makeRangeVarFromNameList(relname_list);
index = gist_index_open(relvar);
gist_index_close(index);
pfree(relname);
- VARATT_SIZEP(info.txt)=info.ptr-((char*)info.txt);
+ SET_VARSIZE(info.txt, info.ptr-((char*)info.txt));
PG_RETURN_POINTER(info.txt);
}
ptr=strchr(ptr,'\0');
- VARATT_SIZEP(out)=ptr-((char*)out);
+ SET_VARSIZE(out, ptr-((char*)out));
PG_RETURN_POINTER(out);
}
SRF_RETURN_NEXT(funcctx, result);
}
+typedef struct GinStatState {
+ Relation index;
+ GinState ginstate;
+ OffsetNumber attnum;
+
+ Buffer buffer;
+ OffsetNumber offset;
+ Datum curval;
+#if PG_VERSION_NUM >= 90100
+ GinNullCategory category;
+#endif
+ Datum dvalues[2];
+ char nulls[2];
+} GinStatState;
+
+static bool
+moveRightIfItNeeded( GinStatState *st )
+{
+ Page page = BufferGetPage(st->buffer);
+
+ if ( st->offset > PageGetMaxOffsetNumber(page) ) {
+ /*
+ * We scaned the whole page, so we should take right page
+ */
+ BlockNumber blkno = GinPageGetOpaque(page)->rightlink;
+
+ if ( GinPageRightMost(page) )
+ return false; /* no more page */
+
+ LockBuffer(st->buffer, GIN_UNLOCK);
+ st->buffer = ReleaseAndReadBuffer(st->buffer, st->index, blkno);
+ LockBuffer(st->buffer, GIN_SHARE);
+ st->offset = FirstOffsetNumber;
+ }
+
+ return true;
+}
+
+/*
+ * Refinds a previois position, at returns it has correctly
+ * set offset and buffer is locked
+ */
+static bool
+refindPosition(GinStatState *st)
+{
+ Page page;
+
+ /* find left if needed (it causes only for first search) */
+ for (;;) {
+ IndexTuple itup;
+ BlockNumber blkno;
+
+ LockBuffer(st->buffer, GIN_SHARE);
+
+ page = BufferGetPage(st->buffer);
+ if (GinPageIsLeaf(page))
+ break;
+
+ itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber));
+ blkno = GinItemPointerGetBlockNumber(&(itup)->t_tid);
+
+ LockBuffer(st->buffer,GIN_UNLOCK);
+ st->buffer = ReleaseAndReadBuffer(st->buffer, st->index, blkno);
+ }
+
+ if (st->offset == InvalidOffsetNumber) {
+ return (PageGetMaxOffsetNumber(page) >= FirstOffsetNumber ) ? true : false; /* first one */
+ }
+
+ for(;;) {
+ int cmp;
+#if PG_VERSION_NUM >= 90100
+ GinNullCategory category;
+#elif PG_VERSION_NUM < 80400
+ bool isnull = false;
+#endif
+ Datum datum;
+ IndexTuple itup;
+
+ if (moveRightIfItNeeded(st)==false)
+ return false;
+
+ itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, st->offset));
+#if PG_VERSION_NUM >= 90100
+ datum = gintuple_get_key(&st->ginstate, itup, &category);
+ cmp = ginCompareAttEntries(&st->ginstate,
+ st->attnum + 1, st->curval, st->category,
+ gintuple_get_attrnum(&st->ginstate, itup), datum, category);
+#elif PG_VERSION_NUM >= 80400
+ datum = gin_index_getattr(&st->ginstate, itup);
+
+ cmp = compareAttEntries(&st->ginstate,
+ st->attnum + 1, st->curval,
+ gintuple_get_attrnum(&st->ginstate, itup), datum);
+#else
+ datum = index_getattr(itup, FirstOffsetNumber, st->ginstate.tupdesc, &isnull);
+
+ cmp = DatumGetInt32(
+ FunctionCall2(
+ &st->ginstate.compareFn,
+ st->curval,
+ datum
+ ));
+#endif
+ if ( cmp == 0 )
+ {
+ if ( !st->index->rd_att->attrs[st->attnum]->attbyval )
+ pfree( (void*) st->curval );
+ return true;
+ }
+
+ st->offset++;
+ }
+
+ return false;
+}
+
+static void
+gin_setup_firstcall(FuncCallContext *funcctx, text *name, int attnum) {
+ MemoryContext oldcontext;
+ GinStatState *st;
+ char *relname=t2c(name);
+ TupleDesc tupdesc;
+
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ st=(GinStatState*)palloc( sizeof(GinStatState) );
+ memset(st,0,sizeof(GinStatState));
+ st->index = gin_index_open(
+ makeRangeVarFromNameList(stringToQualifiedNameList(relname, "gin_stat")));
+ initGinState( &st->ginstate, st->index );
+
+#if PG_VERSION_NUM >= 80400
+ if (attnum < 0 || attnum >= st->index->rd_att->natts)
+ elog(ERROR,"Wrong column's number");
+ st->attnum = attnum;
+#else
+ st->attnum = 0;
+#endif
+
+ funcctx->user_fctx = (void*)st;
+
+ tupdesc = CreateTemplateTupleDesc(2, false);
+ TupleDescInitEntry(tupdesc, 1, "value",
+ st->index->rd_att->attrs[st->attnum]->atttypid,
+ st->index->rd_att->attrs[st->attnum]->atttypmod,
+ st->index->rd_att->attrs[st->attnum]->attndims);
+ TupleDescInitEntry(tupdesc, 2, "nrow", INT4OID, -1, 0);
+
+ memset( st->nulls, ' ', 2*sizeof(char) );
+
+ funcctx->slot = TupleDescGetSlot(tupdesc);
+ funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
+
+ MemoryContextSwitchTo(oldcontext);
+ pfree(relname);
+
+ st->offset = InvalidOffsetNumber;
+ st->buffer = ReadBuffer( st->index, GIN_ROOT_BLKNO );
+}
+
+static void
+processTuple( FuncCallContext *funcctx, GinStatState *st, IndexTuple itup ) {
+ MemoryContext oldcontext;
+#if PG_VERSION_NUM < 80400
+ bool isnull;
+#endif
+
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+ st->curval = datumCopy(
+#if PG_VERSION_NUM >= 90100
+ gintuple_get_key(&st->ginstate, itup, &st->category),
+#elif PG_VERSION_NUM >= 80400
+ gin_index_getattr(&st->ginstate, itup),
+#else
+ index_getattr(itup, FirstOffsetNumber, st->ginstate.tupdesc, &isnull),
+#endif
+ st->index->rd_att->attrs[st->attnum]->attbyval,
+ st->index->rd_att->attrs[st->attnum]->attlen );
+ MemoryContextSwitchTo(oldcontext);
+
+ st->dvalues[0] = st->curval;
+#if PG_VERSION_NUM >= 90100
+ /* do no distiguish various null category */
+ st->nulls[0] = (st->category == GIN_CAT_NORM_KEY) ? ' ' : 'n';
+#endif
+
+ if ( GinIsPostingTree(itup) ) {
+ BlockNumber rootblkno = GinGetPostingTree(itup);
+ GinPostingTreeScan *gdi;
+ Buffer entrybuffer;
+ Page page;
+
+ LockBuffer(st->buffer, GIN_UNLOCK);
+#if PG_VERSION_NUM >= 90100
+ gdi = ginPrepareScanPostingTree(st->index, rootblkno, TRUE);
+ entrybuffer = ginScanBeginPostingTree(gdi);
+#else
+ gdi = prepareScanPostingTree(st->index, rootblkno, TRUE);
+ entrybuffer = scanBeginPostingTree(gdi);
+#endif
+
+ page = BufferGetPage(entrybuffer);
+ st->dvalues[1] = Int32GetDatum( gdi->stack->predictNumber * GinPageGetOpaque(page)->maxoff );
+
+ LockBuffer(entrybuffer, GIN_UNLOCK);
+ freeGinBtreeStack(gdi->stack);
+ pfree(gdi);
+ } else {
+ st->dvalues[1] = Int32GetDatum( GinGetNPosting(itup) );
+ LockBuffer(st->buffer, GIN_UNLOCK);
+ }
+}
+
+PG_FUNCTION_INFO_V1(gin_stat);
+Datum gin_stat(PG_FUNCTION_ARGS);
+Datum
+gin_stat(PG_FUNCTION_ARGS) {
+ FuncCallContext *funcctx;
+ GinStatState *st;
+ Datum result=(Datum)0;
+ IndexTuple ituple;
+ HeapTuple htuple;
+ Page page;
+
+ if (SRF_IS_FIRSTCALL()) {
+ text *name=PG_GETARG_TEXT_P(0);
+ funcctx = SRF_FIRSTCALL_INIT();
+ gin_setup_firstcall(funcctx, name, (PG_NARGS()==2) ? PG_GETARG_INT32(1) : 0 );
+ PG_FREE_IF_COPY(name,0);
+ }
+
+ funcctx = SRF_PERCALL_SETUP();
+ st = (GinStatState*)(funcctx->user_fctx);
+
+ if ( refindPosition(st) == false ) {
+ UnlockReleaseBuffer( st->buffer );
+ gin_index_close(st->index);
+
+ SRF_RETURN_DONE(funcctx);
+ }
+
+ for(;;) {
+ st->offset++;
+
+ if (moveRightIfItNeeded(st)==false) {
+ UnlockReleaseBuffer( st->buffer );
+ gin_index_close(st->index);
+
+ SRF_RETURN_DONE(funcctx);
+ }
+
+ page = BufferGetPage(st->buffer);
+ ituple = (IndexTuple) PageGetItem(page, PageGetItemId(page, st->offset));
+
+#if PG_VERSION_NUM >= 80400
+ if (st->attnum + 1 == gintuple_get_attrnum(&st->ginstate, ituple))
+#endif
+ break;
+ }
+
+ processTuple( funcctx, st, ituple );
+
+ htuple = heap_formtuple(funcctx->attinmeta->tupdesc, st->dvalues, st->nulls);
+ result = TupleGetDatum(funcctx->slot, htuple);
+
+ SRF_RETURN_NEXT(funcctx, result);
+}
+
+PG_FUNCTION_INFO_V1(gin_count_estimate);
+Datum gin_count_estimate(PG_FUNCTION_ARGS);
+#if PG_VERSION_NUM >= 80300
+Datum
+gin_count_estimate(PG_FUNCTION_ARGS) {
+ text *name=PG_GETARG_TEXT_P(0);
+ Relation index;
+ IndexScanDesc scan;
+ int64 count = 0;
+ char *relname=t2c(name);
+ ScanKeyData key;
+#if PG_VERSION_NUM >= 80400
+ TIDBitmap *bitmap = tbm_create(work_mem * 1024L);
+#else
+#define MAXTIDS 1024
+ ItemPointerData tids[MAXTIDS];
+ int32 returned_tids;
+ bool more;
+#endif
+
+ index = gin_index_open(
+ makeRangeVarFromNameList(stringToQualifiedNameList(relname, "gin_count_estimate")));
+
+ if ( index->rd_opcintype[0] != TSVECTOROID ) {
+ gin_index_close(index);
+ elog(ERROR, "Column type is not a tsvector");
+ }
+
+ key.sk_flags = 0;
+ key.sk_attno = 1;
+ key.sk_strategy = TSearchStrategyNumber;
+ key.sk_subtype = 0;
+ key.sk_argument = PG_GETARG_DATUM(1);
+
+ fmgr_info( F_TS_MATCH_VQ , &key.sk_func );
+
+#if PG_VERSION_NUM >= 90100
+ scan = index_beginscan_bitmap(index, SnapshotNow, 1);
+ index_rescan(scan, &key, 1, NULL, 0);
+
+ count = index_getbitmap(scan, bitmap);
+ tbm_free(bitmap);
+#elif PG_VERSION_NUM >= 80400
+ scan = index_beginscan_bitmap(index, SnapshotNow, 1, &key);
+
+ count = index_getbitmap(scan, bitmap);
+ tbm_free(bitmap);
+#else
+ scan = index_beginscan_multi(index, SnapshotNow, 1, &key);
+
+ do {
+ more = index_getmulti(scan, tids, MAXTIDS, &returned_tids);
+ count += returned_tids;
+ } while(more);
+#endif
+
+ index_endscan( scan );
+ gin_index_close(index);
+
+ PG_RETURN_INT64(count);
+}
+#else
+Datum
+gin_count_estimate(PG_FUNCTION_ARGS) {
+ elog(NOTICE, "Function is not working under PgSQL < 8.3");
+
+ PG_RETURN_INT64(0);
+}
+#endif
+
+PG_FUNCTION_INFO_V1(spgist_stat);
+Datum spgist_stat(PG_FUNCTION_ARGS);
+Datum
+spgist_stat(PG_FUNCTION_ARGS)
+{
+#if PG_VERSION_NUM < 90200
+ elog(NOTICE, "Function is not working under PgSQL < 9.2");
+
+ PG_RETURN_TEXT_P(CStringGetTextDatum("???"));
+#else
+ text *name = PG_GETARG_TEXT_P(0);
+ RangeVar *relvar;
+ Relation index;
+ BlockNumber blkno;
+ BlockNumber totalPages = 0,
+ innerPages = 0,
+ leafPages = 0,
+ emptyPages = 0,
+ deletedPages = 0;
+ double usedSpace = 0.0;
+ char res[1024];
+ int bufferSize = -1;
+ int64 innerTuples = 0,
+ leafTuples = 0,
+ nAllTheSame = 0,
+ nLeafPlaceholder = 0,
+ nInnerPlaceholder = 0,
+ nLeafRedirect = 0,
+ nInnerRedirect = 0;
+
+#define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX)
+#define IS_SPGIST(r) ((r)->rd_rel->relam == SPGIST_AM_OID)
+
+ relvar = makeRangeVarFromNameList(textToQualifiedNameList(name));
+ index = relation_openrv(relvar, AccessExclusiveLock);
+
+ if (!IS_INDEX(index) || !IS_SPGIST(index))
+ elog(ERROR, "relation \"%s\" is not an SPGiST index",
+ RelationGetRelationName(index));
+
+ totalPages = RelationGetNumberOfBlocks(index);
+
+ for (blkno = SPGIST_HEAD_BLKNO; blkno < totalPages; blkno++)
+ {
+ Buffer buffer;
+ Page page;
+ int pageFree;
+
+ buffer = ReadBuffer(index, blkno);
+ LockBuffer(buffer, BUFFER_LOCK_SHARE);
+
+ page = BufferGetPage(buffer);
+
+ if (PageIsNew(page) || SpGistPageIsDeleted(page))
+ {
+ deletedPages++;
+ UnlockReleaseBuffer(buffer);
+ continue;
+ }
+
+ if (SpGistPageIsLeaf(page))
+ {
+ leafPages++;
+ leafTuples += PageGetMaxOffsetNumber(page);
+ nLeafPlaceholder += SpGistPageGetOpaque(page)->nPlaceholder;
+ nLeafRedirect += SpGistPageGetOpaque(page)->nRedirection;
+ }
+ else
+ {
+ int i,
+ max;
+
+ innerPages++;
+ max = PageGetMaxOffsetNumber(page);
+ innerTuples += max;
+ nInnerPlaceholder += SpGistPageGetOpaque(page)->nPlaceholder;
+ nInnerRedirect += SpGistPageGetOpaque(page)->nRedirection;
+ for (i = FirstOffsetNumber; i <= max; i++)
+ {
+ SpGistInnerTuple it;
+
+ it = (SpGistInnerTuple) PageGetItem(page,
+ PageGetItemId(page, i));
+ if (it->allTheSame)
+ nAllTheSame++;
+ }
+ }
+
+ if (bufferSize < 0)
+ bufferSize = BufferGetPageSize(buffer)
+ - MAXALIGN(sizeof(SpGistPageOpaqueData))
+ - SizeOfPageHeaderData;
+
+ pageFree = PageGetExactFreeSpace(page);
+
+ usedSpace += bufferSize - pageFree;
+
+ if (pageFree == bufferSize)
+ emptyPages++;
+
+ UnlockReleaseBuffer(buffer);
+ }
+
+ index_close(index, AccessExclusiveLock);
+
+ totalPages--; /* discount metapage */
+
+ snprintf(res, sizeof(res),
+ "totalPages: %u\n"
+ "deletedPages: %u\n"
+ "innerPages: %u\n"
+ "leafPages: %u\n"
+ "emptyPages: %u\n"
+ "usedSpace: %.2f kbytes\n"
+ "freeSpace: %.2f kbytes\n"
+ "fillRatio: %.2f%%\n"
+ "leafTuples: " INT64_FORMAT "\n"
+ "innerTuples: " INT64_FORMAT "\n"
+ "innerAllTheSame: " INT64_FORMAT "\n"
+ "leafPlaceholders: " INT64_FORMAT "\n"
+ "innerPlaceholders: " INT64_FORMAT "\n"
+ "leafRedirects: " INT64_FORMAT "\n"
+ "innerRedirects: " INT64_FORMAT,
+ totalPages, deletedPages, innerPages, leafPages, emptyPages,
+ usedSpace / 1024.0,
+ (((double) bufferSize) * ((double) totalPages) - usedSpace) / 1024,
+ 100.0 * (usedSpace / (((double) bufferSize) * ((double) totalPages))),
+ leafTuples, innerTuples, nAllTheSame,
+ nLeafPlaceholder, nInnerPlaceholder,
+ nLeafRedirect, nInnerRedirect);
+
+ PG_RETURN_TEXT_P(CStringGetTextDatum(res));
+#endif
+}
+