LCOV - code coverage report
Current view: top level - contrib/pageinspect - brinfuncs.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 0.0 % 213 0
Test Date: 2026-01-26 10:56:24 Functions: 0.0 % 9 0
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*
       2              :  * brinfuncs.c
       3              :  *              Functions to investigate BRIN indexes
       4              :  *
       5              :  * Copyright (c) 2014-2026, PostgreSQL Global Development Group
       6              :  *
       7              :  * IDENTIFICATION
       8              :  *              contrib/pageinspect/brinfuncs.c
       9              :  */
      10              : #include "postgres.h"
      11              : 
      12              : #include "access/brin_internal.h"
      13              : #include "access/brin_page.h"
      14              : #include "access/brin_tuple.h"
      15              : #include "access/htup_details.h"
      16              : #include "catalog/pg_am_d.h"
      17              : #include "catalog/pg_type.h"
      18              : #include "funcapi.h"
      19              : #include "lib/stringinfo.h"
      20              : #include "miscadmin.h"
      21              : #include "pageinspect.h"
      22              : #include "utils/builtins.h"
      23              : #include "utils/lsyscache.h"
      24              : #include "utils/rel.h"
      25              : 
      26            0 : PG_FUNCTION_INFO_V1(brin_page_type);
      27            0 : PG_FUNCTION_INFO_V1(brin_page_items);
      28            0 : PG_FUNCTION_INFO_V1(brin_metapage_info);
      29            0 : PG_FUNCTION_INFO_V1(brin_revmap_data);
      30              : 
      31              : #define IS_BRIN(r) ((r)->rd_rel->relam == BRIN_AM_OID)
      32              : 
      33              : typedef struct brin_column_state
      34              : {
      35              :         int                     nstored;
      36              :         FmgrInfo        outputFn[FLEXIBLE_ARRAY_MEMBER];
      37              : } brin_column_state;
      38              : 
      39              : 
      40              : static Page verify_brin_page(bytea *raw_page, uint16 type,
      41              :                                                          const char *strtype);
      42              : 
      43              : Datum
      44            0 : brin_page_type(PG_FUNCTION_ARGS)
      45              : {
      46            0 :         bytea      *raw_page = PG_GETARG_BYTEA_P(0);
      47            0 :         Page            page;
      48            0 :         char       *type;
      49              : 
      50            0 :         if (!superuser())
      51            0 :                 ereport(ERROR,
      52              :                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
      53              :                                  errmsg("must be superuser to use raw page functions")));
      54              : 
      55            0 :         page = get_page_from_raw(raw_page);
      56              : 
      57            0 :         if (PageIsNew(page))
      58            0 :                 PG_RETURN_NULL();
      59              : 
      60              :         /* verify the special space has the expected size */
      61            0 :         if (PageGetSpecialSize(page) != MAXALIGN(sizeof(BrinSpecialSpace)))
      62            0 :                 ereport(ERROR,
      63              :                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      64              :                                  errmsg("input page is not a valid %s page", "BRIN"),
      65              :                                  errdetail("Expected special size %d, got %d.",
      66              :                                                    (int) MAXALIGN(sizeof(BrinSpecialSpace)),
      67              :                                                    (int) PageGetSpecialSize(page))));
      68              : 
      69            0 :         switch (BrinPageType(page))
      70              :         {
      71              :                 case BRIN_PAGETYPE_META:
      72            0 :                         type = "meta";
      73            0 :                         break;
      74              :                 case BRIN_PAGETYPE_REVMAP:
      75            0 :                         type = "revmap";
      76            0 :                         break;
      77              :                 case BRIN_PAGETYPE_REGULAR:
      78            0 :                         type = "regular";
      79            0 :                         break;
      80              :                 default:
      81            0 :                         type = psprintf("unknown (%02x)", BrinPageType(page));
      82            0 :                         break;
      83              :         }
      84              : 
      85            0 :         PG_RETURN_TEXT_P(cstring_to_text(type));
      86            0 : }
      87              : 
      88              : /*
      89              :  * Verify that the given bytea contains a BRIN page of the indicated page
      90              :  * type, or die in the attempt.  A pointer to the page is returned.
      91              :  */
      92              : static Page
      93            0 : verify_brin_page(bytea *raw_page, uint16 type, const char *strtype)
      94              : {
      95            0 :         Page            page = get_page_from_raw(raw_page);
      96              : 
      97            0 :         if (PageIsNew(page))
      98            0 :                 return page;
      99              : 
     100              :         /* verify the special space has the expected size */
     101            0 :         if (PageGetSpecialSize(page) != MAXALIGN(sizeof(BrinSpecialSpace)))
     102            0 :                 ereport(ERROR,
     103              :                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     104              :                                  errmsg("input page is not a valid %s page", "BRIN"),
     105              :                                  errdetail("Expected special size %d, got %d.",
     106              :                                                    (int) MAXALIGN(sizeof(BrinSpecialSpace)),
     107              :                                                    (int) PageGetSpecialSize(page))));
     108              : 
     109              :         /* verify the special space says this page is what we want */
     110            0 :         if (BrinPageType(page) != type)
     111            0 :                 ereport(ERROR,
     112              :                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     113              :                                  errmsg("page is not a BRIN page of type \"%s\"", strtype),
     114              :                                  errdetail("Expected special type %08x, got %08x.",
     115              :                                                    type, BrinPageType(page))));
     116              : 
     117            0 :         return page;
     118            0 : }
     119              : 
     120              : /* Number of output arguments (columns) for brin_page_items() */
     121              : #define BRIN_PAGE_ITEMS_V1_12   8
     122              : 
     123              : /*
     124              :  * Extract all item values from a BRIN index page
     125              :  *
     126              :  * Usage: SELECT * FROM brin_page_items(get_raw_page('idx', 1), 'idx'::regclass);
     127              :  */
     128              : Datum
     129            0 : brin_page_items(PG_FUNCTION_ARGS)
     130              : {
     131            0 :         bytea      *raw_page = PG_GETARG_BYTEA_P(0);
     132            0 :         Oid                     indexRelid = PG_GETARG_OID(1);
     133            0 :         ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
     134            0 :         Relation        indexRel;
     135            0 :         brin_column_state **columns;
     136            0 :         BrinDesc   *bdesc;
     137            0 :         BrinMemTuple *dtup;
     138            0 :         Page            page;
     139            0 :         OffsetNumber offset;
     140            0 :         AttrNumber      attno;
     141            0 :         bool            unusedItem;
     142              : 
     143            0 :         if (!superuser())
     144            0 :                 ereport(ERROR,
     145              :                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     146              :                                  errmsg("must be superuser to use raw page functions")));
     147              : 
     148            0 :         InitMaterializedSRF(fcinfo, 0);
     149              : 
     150              :         /*
     151              :          * Version 1.12 added a new output column for the empty range flag. But as
     152              :          * it was added in the middle, it may cause crashes with function
     153              :          * definitions from older versions of the extension.
     154              :          *
     155              :          * There is no way to reliably avoid the problems created by the old
     156              :          * function definition at this point, so insist that the user update the
     157              :          * extension.
     158              :          */
     159            0 :         if (rsinfo->setDesc->natts < BRIN_PAGE_ITEMS_V1_12)
     160            0 :                 ereport(ERROR,
     161              :                                 (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
     162              :                                  errmsg("function has wrong number of declared columns"),
     163              :                                  errhint("To resolve the problem, update the \"pageinspect\" extension to the latest version.")));
     164              : 
     165            0 :         indexRel = index_open(indexRelid, AccessShareLock);
     166              : 
     167            0 :         if (!IS_BRIN(indexRel))
     168            0 :                 ereport(ERROR,
     169              :                                 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     170              :                                  errmsg("\"%s\" is not a %s index",
     171              :                                                 RelationGetRelationName(indexRel), "BRIN")));
     172              : 
     173            0 :         bdesc = brin_build_desc(indexRel);
     174              : 
     175              :         /* minimally verify the page we got */
     176            0 :         page = verify_brin_page(raw_page, BRIN_PAGETYPE_REGULAR, "regular");
     177              : 
     178            0 :         if (PageIsNew(page))
     179              :         {
     180            0 :                 brin_free_desc(bdesc);
     181            0 :                 index_close(indexRel, AccessShareLock);
     182            0 :                 PG_RETURN_NULL();
     183            0 :         }
     184              : 
     185              :         /*
     186              :          * Initialize output functions for all indexed datatypes; simplifies
     187              :          * calling them later.
     188              :          */
     189            0 :         columns = palloc_array(brin_column_state *, RelationGetDescr(indexRel)->natts);
     190            0 :         for (attno = 1; attno <= bdesc->bd_tupdesc->natts; attno++)
     191              :         {
     192            0 :                 Oid                     output;
     193            0 :                 bool            isVarlena;
     194            0 :                 BrinOpcInfo *opcinfo;
     195            0 :                 int                     i;
     196            0 :                 brin_column_state *column;
     197              : 
     198            0 :                 opcinfo = bdesc->bd_info[attno - 1];
     199            0 :                 column = palloc(offsetof(brin_column_state, outputFn) +
     200            0 :                                                 sizeof(FmgrInfo) * opcinfo->oi_nstored);
     201              : 
     202            0 :                 column->nstored = opcinfo->oi_nstored;
     203            0 :                 for (i = 0; i < opcinfo->oi_nstored; i++)
     204              :                 {
     205            0 :                         getTypeOutputInfo(opcinfo->oi_typcache[i]->type_id, &output, &isVarlena);
     206            0 :                         fmgr_info(output, &column->outputFn[i]);
     207            0 :                 }
     208              : 
     209            0 :                 columns[attno - 1] = column;
     210            0 :         }
     211              : 
     212            0 :         offset = FirstOffsetNumber;
     213            0 :         unusedItem = false;
     214            0 :         dtup = NULL;
     215            0 :         for (;;)
     216              :         {
     217            0 :                 Datum           values[8];
     218            0 :                 bool            nulls[8] = {0};
     219              : 
     220              :                 /*
     221              :                  * This loop is called once for every attribute of every tuple in the
     222              :                  * page.  At the start of a tuple, we get a NULL dtup; that's our
     223              :                  * signal for obtaining and decoding the next one.  If that's not the
     224              :                  * case, we output the next attribute.
     225              :                  */
     226            0 :                 if (dtup == NULL)
     227              :                 {
     228            0 :                         ItemId          itemId;
     229              : 
     230              :                         /* verify item status: if there's no data, we can't decode */
     231            0 :                         itemId = PageGetItemId(page, offset);
     232            0 :                         if (ItemIdIsUsed(itemId))
     233              :                         {
     234            0 :                                 dtup = brin_deform_tuple(bdesc,
     235            0 :                                                                                  (BrinTuple *) PageGetItem(page, itemId),
     236              :                                                                                  NULL);
     237            0 :                                 attno = 1;
     238            0 :                                 unusedItem = false;
     239            0 :                         }
     240              :                         else
     241            0 :                                 unusedItem = true;
     242            0 :                 }
     243              :                 else
     244            0 :                         attno++;
     245              : 
     246            0 :                 if (unusedItem)
     247              :                 {
     248            0 :                         values[0] = UInt16GetDatum(offset);
     249            0 :                         nulls[1] = true;
     250            0 :                         nulls[2] = true;
     251            0 :                         nulls[3] = true;
     252            0 :                         nulls[4] = true;
     253            0 :                         nulls[5] = true;
     254            0 :                         nulls[6] = true;
     255            0 :                         nulls[7] = true;
     256            0 :                 }
     257              :                 else
     258              :                 {
     259            0 :                         int                     att = attno - 1;
     260              : 
     261            0 :                         values[0] = UInt16GetDatum(offset);
     262            0 :                         switch (TupleDescAttr(rsinfo->setDesc, 1)->atttypid)
     263              :                         {
     264              :                                 case INT8OID:
     265            0 :                                         values[1] = Int64GetDatum((int64) dtup->bt_blkno);
     266            0 :                                         break;
     267              :                                 case INT4OID:
     268              :                                         /* support for old extension version */
     269            0 :                                         values[1] = UInt32GetDatum(dtup->bt_blkno);
     270            0 :                                         break;
     271              :                                 default:
     272            0 :                                         elog(ERROR, "incorrect output types");
     273            0 :                         }
     274            0 :                         values[2] = UInt16GetDatum(attno);
     275            0 :                         values[3] = BoolGetDatum(dtup->bt_columns[att].bv_allnulls);
     276            0 :                         values[4] = BoolGetDatum(dtup->bt_columns[att].bv_hasnulls);
     277            0 :                         values[5] = BoolGetDatum(dtup->bt_placeholder);
     278            0 :                         values[6] = BoolGetDatum(dtup->bt_empty_range);
     279            0 :                         if (!dtup->bt_columns[att].bv_allnulls)
     280              :                         {
     281            0 :                                 BrinValues *bvalues = &dtup->bt_columns[att];
     282            0 :                                 StringInfoData s;
     283            0 :                                 bool            first;
     284            0 :                                 int                     i;
     285              : 
     286            0 :                                 initStringInfo(&s);
     287            0 :                                 appendStringInfoChar(&s, '{');
     288              : 
     289            0 :                                 first = true;
     290            0 :                                 for (i = 0; i < columns[att]->nstored; i++)
     291              :                                 {
     292            0 :                                         char       *val;
     293              : 
     294            0 :                                         if (!first)
     295            0 :                                                 appendStringInfoString(&s, " .. ");
     296            0 :                                         first = false;
     297            0 :                                         val = OutputFunctionCall(&columns[att]->outputFn[i],
     298            0 :                                                                                          bvalues->bv_values[i]);
     299            0 :                                         appendStringInfoString(&s, val);
     300            0 :                                         pfree(val);
     301            0 :                                 }
     302            0 :                                 appendStringInfoChar(&s, '}');
     303              : 
     304            0 :                                 values[7] = CStringGetTextDatum(s.data);
     305            0 :                                 pfree(s.data);
     306            0 :                         }
     307              :                         else
     308              :                         {
     309            0 :                                 nulls[7] = true;
     310              :                         }
     311            0 :                 }
     312              : 
     313            0 :                 tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
     314              : 
     315              :                 /*
     316              :                  * If the item was unused, jump straight to the next one; otherwise,
     317              :                  * the only cleanup needed here is to set our signal to go to the next
     318              :                  * tuple in the following iteration, by freeing the current one.
     319              :                  */
     320            0 :                 if (unusedItem)
     321            0 :                         offset = OffsetNumberNext(offset);
     322            0 :                 else if (attno >= bdesc->bd_tupdesc->natts)
     323              :                 {
     324            0 :                         pfree(dtup);
     325            0 :                         dtup = NULL;
     326            0 :                         offset = OffsetNumberNext(offset);
     327            0 :                 }
     328              : 
     329              :                 /*
     330              :                  * If we're beyond the end of the page, we're done.
     331              :                  */
     332            0 :                 if (offset > PageGetMaxOffsetNumber(page))
     333            0 :                         break;
     334            0 :         }
     335              : 
     336            0 :         brin_free_desc(bdesc);
     337            0 :         index_close(indexRel, AccessShareLock);
     338              : 
     339            0 :         return (Datum) 0;
     340            0 : }
     341              : 
     342              : Datum
     343            0 : brin_metapage_info(PG_FUNCTION_ARGS)
     344              : {
     345            0 :         bytea      *raw_page = PG_GETARG_BYTEA_P(0);
     346            0 :         Page            page;
     347            0 :         BrinMetaPageData *meta;
     348            0 :         TupleDesc       tupdesc;
     349            0 :         Datum           values[4];
     350            0 :         bool            nulls[4] = {0};
     351            0 :         HeapTuple       htup;
     352              : 
     353            0 :         if (!superuser())
     354            0 :                 ereport(ERROR,
     355              :                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     356              :                                  errmsg("must be superuser to use raw page functions")));
     357              : 
     358            0 :         page = verify_brin_page(raw_page, BRIN_PAGETYPE_META, "metapage");
     359              : 
     360            0 :         if (PageIsNew(page))
     361            0 :                 PG_RETURN_NULL();
     362              : 
     363              :         /* Build a tuple descriptor for our result type */
     364            0 :         if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
     365            0 :                 elog(ERROR, "return type must be a row type");
     366            0 :         tupdesc = BlessTupleDesc(tupdesc);
     367              : 
     368              :         /* Extract values from the metapage */
     369            0 :         meta = (BrinMetaPageData *) PageGetContents(page);
     370            0 :         values[0] = CStringGetTextDatum(psprintf("0x%08X", meta->brinMagic));
     371            0 :         values[1] = Int32GetDatum(meta->brinVersion);
     372            0 :         values[2] = Int32GetDatum(meta->pagesPerRange);
     373            0 :         values[3] = Int64GetDatum(meta->lastRevmapPage);
     374              : 
     375            0 :         htup = heap_form_tuple(tupdesc, values, nulls);
     376              : 
     377            0 :         PG_RETURN_DATUM(HeapTupleGetDatum(htup));
     378            0 : }
     379              : 
     380              : /*
     381              :  * Return the TID array stored in a BRIN revmap page
     382              :  */
     383              : Datum
     384            0 : brin_revmap_data(PG_FUNCTION_ARGS)
     385              : {
     386            0 :         struct
     387              :         {
     388              :                 ItemPointerData *tids;
     389              :                 int                     idx;
     390              :         }                  *state;
     391            0 :         FuncCallContext *fctx;
     392              : 
     393            0 :         if (!superuser())
     394            0 :                 ereport(ERROR,
     395              :                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     396              :                                  errmsg("must be superuser to use raw page functions")));
     397              : 
     398            0 :         if (SRF_IS_FIRSTCALL())
     399              :         {
     400            0 :                 bytea      *raw_page = PG_GETARG_BYTEA_P(0);
     401            0 :                 MemoryContext mctx;
     402            0 :                 Page            page;
     403              : 
     404              :                 /* create a function context for cross-call persistence */
     405            0 :                 fctx = SRF_FIRSTCALL_INIT();
     406              : 
     407              :                 /* switch to memory context appropriate for multiple function calls */
     408            0 :                 mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
     409              : 
     410              :                 /* minimally verify the page we got */
     411            0 :                 page = verify_brin_page(raw_page, BRIN_PAGETYPE_REVMAP, "revmap");
     412              : 
     413            0 :                 if (PageIsNew(page))
     414              :                 {
     415            0 :                         MemoryContextSwitchTo(mctx);
     416            0 :                         PG_RETURN_NULL();
     417            0 :                 }
     418              : 
     419            0 :                 state = palloc(sizeof(*state));
     420            0 :                 state->tids = ((RevmapContents *) PageGetContents(page))->rm_tids;
     421            0 :                 state->idx = 0;
     422              : 
     423            0 :                 fctx->user_fctx = state;
     424              : 
     425            0 :                 MemoryContextSwitchTo(mctx);
     426            0 :         }
     427              : 
     428            0 :         fctx = SRF_PERCALL_SETUP();
     429            0 :         state = fctx->user_fctx;
     430              : 
     431            0 :         if (state->idx < REVMAP_PAGE_MAXITEMS)
     432            0 :                 SRF_RETURN_NEXT(fctx, PointerGetDatum(&state->tids[state->idx++]));
     433              : 
     434            0 :         SRF_RETURN_DONE(fctx);
     435            0 : }
        

Generated by: LCOV version 2.3.2-1