LCOV - code coverage report
Current view: top level - contrib/pageinspect - brinfuncs.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 148 171 86.5 %
Date: 2025-01-18 04:15:08 Functions: 9 9 100.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-2025, 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          14 : PG_FUNCTION_INFO_V1(brin_page_type);
      27          58 : PG_FUNCTION_INFO_V1(brin_page_items);
      28          16 : PG_FUNCTION_INFO_V1(brin_metapage_info);
      29          14 : 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          10 : brin_page_type(PG_FUNCTION_ARGS)
      45             : {
      46          10 :     bytea      *raw_page = PG_GETARG_BYTEA_P(0);
      47             :     Page        page;
      48             :     char       *type;
      49             : 
      50          10 :     if (!superuser())
      51           0 :         ereport(ERROR,
      52             :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
      53             :                  errmsg("must be superuser to use raw page functions")));
      54             : 
      55          10 :     page = get_page_from_raw(raw_page);
      56             : 
      57          10 :     if (PageIsNew(page))
      58           2 :         PG_RETURN_NULL();
      59             : 
      60             :     /* verify the special space has the expected size */
      61           8 :     if (PageGetSpecialSize(page) != MAXALIGN(sizeof(BrinSpecialSpace)))
      62           2 :         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           6 :     switch (BrinPageType(page))
      70             :     {
      71           2 :         case BRIN_PAGETYPE_META:
      72           2 :             type = "meta";
      73           2 :             break;
      74           2 :         case BRIN_PAGETYPE_REVMAP:
      75           2 :             type = "revmap";
      76           2 :             break;
      77           2 :         case BRIN_PAGETYPE_REGULAR:
      78           2 :             type = "regular";
      79           2 :             break;
      80           0 :         default:
      81           0 :             type = psprintf("unknown (%02x)", BrinPageType(page));
      82           0 :             break;
      83             :     }
      84             : 
      85           6 :     PG_RETURN_TEXT_P(cstring_to_text(type));
      86             : }
      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          86 : verify_brin_page(bytea *raw_page, uint16 type, const char *strtype)
      94             : {
      95          86 :     Page        page = get_page_from_raw(raw_page);
      96             : 
      97          86 :     if (PageIsNew(page))
      98           6 :         return page;
      99             : 
     100             :     /* verify the special space has the expected size */
     101          80 :     if (PageGetSpecialSize(page) != MAXALIGN(sizeof(BrinSpecialSpace)))
     102           6 :         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          74 :     if (BrinPageType(page) != type)
     111           4 :         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          70 :     return page;
     118             : }
     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          52 : brin_page_items(PG_FUNCTION_ARGS)
     130             : {
     131          52 :     bytea      *raw_page = PG_GETARG_BYTEA_P(0);
     132          52 :     Oid         indexRelid = PG_GETARG_OID(1);
     133          52 :     ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
     134             :     Relation    indexRel;
     135             :     brin_column_state **columns;
     136             :     BrinDesc   *bdesc;
     137             :     BrinMemTuple *dtup;
     138             :     Page        page;
     139             :     OffsetNumber offset;
     140             :     AttrNumber  attno;
     141             :     bool        unusedItem;
     142             : 
     143          52 :     if (!superuser())
     144           0 :         ereport(ERROR,
     145             :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     146             :                  errmsg("must be superuser to use raw page functions")));
     147             : 
     148          52 :     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          52 :     if (rsinfo->setDesc->natts < BRIN_PAGE_ITEMS_V1_12)
     160           2 :         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          50 :     indexRel = index_open(indexRelid, AccessShareLock);
     166             : 
     167          50 :     if (!IS_BRIN(indexRel))
     168           2 :         ereport(ERROR,
     169             :                 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     170             :                  errmsg("\"%s\" is not a %s index",
     171             :                         RelationGetRelationName(indexRel), "BRIN")));
     172             : 
     173          48 :     bdesc = brin_build_desc(indexRel);
     174             : 
     175             :     /* minimally verify the page we got */
     176          48 :     page = verify_brin_page(raw_page, BRIN_PAGETYPE_REGULAR, "regular");
     177             : 
     178          46 :     if (PageIsNew(page))
     179             :     {
     180           2 :         brin_free_desc(bdesc);
     181           2 :         index_close(indexRel, AccessShareLock);
     182           2 :         PG_RETURN_NULL();
     183             :     }
     184             : 
     185             :     /*
     186             :      * Initialize output functions for all indexed datatypes; simplifies
     187             :      * calling them later.
     188             :      */
     189          44 :     columns = palloc(sizeof(brin_column_state *) * RelationGetDescr(indexRel)->natts);
     190         136 :     for (attno = 1; attno <= bdesc->bd_tupdesc->natts; attno++)
     191             :     {
     192             :         Oid         output;
     193             :         bool        isVarlena;
     194             :         BrinOpcInfo *opcinfo;
     195             :         int         i;
     196             :         brin_column_state *column;
     197             : 
     198          92 :         opcinfo = bdesc->bd_info[attno - 1];
     199          92 :         column = palloc(offsetof(brin_column_state, outputFn) +
     200          92 :                         sizeof(FmgrInfo) * opcinfo->oi_nstored);
     201             : 
     202          92 :         column->nstored = opcinfo->oi_nstored;
     203         244 :         for (i = 0; i < opcinfo->oi_nstored; i++)
     204             :         {
     205         152 :             getTypeOutputInfo(opcinfo->oi_typcache[i]->type_id, &output, &isVarlena);
     206         152 :             fmgr_info(output, &column->outputFn[i]);
     207             :         }
     208             : 
     209          92 :         columns[attno - 1] = column;
     210             :     }
     211             : 
     212          44 :     offset = FirstOffsetNumber;
     213          44 :     unusedItem = false;
     214          44 :     dtup = NULL;
     215             :     for (;;)
     216        1224 :     {
     217             :         Datum       values[8];
     218        1268 :         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        1268 :         if (dtup == NULL)
     227             :         {
     228             :             ItemId      itemId;
     229             : 
     230             :             /* verify item status: if there's no data, we can't decode */
     231         356 :             itemId = PageGetItemId(page, offset);
     232         356 :             if (ItemIdIsUsed(itemId))
     233             :             {
     234         356 :                 dtup = brin_deform_tuple(bdesc,
     235         356 :                                          (BrinTuple *) PageGetItem(page, itemId),
     236             :                                          NULL);
     237         356 :                 attno = 1;
     238         356 :                 unusedItem = false;
     239             :             }
     240             :             else
     241           0 :                 unusedItem = true;
     242             :         }
     243             :         else
     244         912 :             attno++;
     245             : 
     246        1268 :         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             :         }
     257             :         else
     258             :         {
     259        1268 :             int         att = attno - 1;
     260             : 
     261        1268 :             values[0] = UInt16GetDatum(offset);
     262        1268 :             switch (TupleDescAttr(rsinfo->setDesc, 1)->atttypid)
     263             :             {
     264        1268 :                 case INT8OID:
     265        1268 :                     values[1] = Int64GetDatum((int64) dtup->bt_blkno);
     266        1268 :                     break;
     267           0 :                 case INT4OID:
     268             :                     /* support for old extension version */
     269           0 :                     values[1] = UInt32GetDatum(dtup->bt_blkno);
     270           0 :                     break;
     271           0 :                 default:
     272           0 :                     elog(ERROR, "incorrect output types");
     273             :             }
     274        1268 :             values[2] = UInt16GetDatum(attno);
     275        1268 :             values[3] = BoolGetDatum(dtup->bt_columns[att].bv_allnulls);
     276        1268 :             values[4] = BoolGetDatum(dtup->bt_columns[att].bv_hasnulls);
     277        1268 :             values[5] = BoolGetDatum(dtup->bt_placeholder);
     278        1268 :             values[6] = BoolGetDatum(dtup->bt_empty_range);
     279        1268 :             if (!dtup->bt_columns[att].bv_allnulls)
     280             :             {
     281        1048 :                 BrinValues *bvalues = &dtup->bt_columns[att];
     282             :                 StringInfoData s;
     283             :                 bool        first;
     284             :                 int         i;
     285             : 
     286        1048 :                 initStringInfo(&s);
     287        1048 :                 appendStringInfoChar(&s, '{');
     288             : 
     289        1048 :                 first = true;
     290        2648 :                 for (i = 0; i < columns[att]->nstored; i++)
     291             :                 {
     292             :                     char       *val;
     293             : 
     294        1600 :                     if (!first)
     295         552 :                         appendStringInfoString(&s, " .. ");
     296        1600 :                     first = false;
     297        1600 :                     val = OutputFunctionCall(&columns[att]->outputFn[i],
     298        1600 :                                              bvalues->bv_values[i]);
     299        1600 :                     appendStringInfoString(&s, val);
     300        1600 :                     pfree(val);
     301             :                 }
     302        1048 :                 appendStringInfoChar(&s, '}');
     303             : 
     304        1048 :                 values[7] = CStringGetTextDatum(s.data);
     305        1048 :                 pfree(s.data);
     306             :             }
     307             :             else
     308             :             {
     309         220 :                 nulls[7] = true;
     310             :             }
     311             :         }
     312             : 
     313        1268 :         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        1268 :         if (unusedItem)
     321           0 :             offset = OffsetNumberNext(offset);
     322        1268 :         else if (attno >= bdesc->bd_tupdesc->natts)
     323             :         {
     324         356 :             pfree(dtup);
     325         356 :             dtup = NULL;
     326         356 :             offset = OffsetNumberNext(offset);
     327             :         }
     328             : 
     329             :         /*
     330             :          * If we're beyond the end of the page, we're done.
     331             :          */
     332        1268 :         if (offset > PageGetMaxOffsetNumber(page))
     333          44 :             break;
     334             :     }
     335             : 
     336          44 :     brin_free_desc(bdesc);
     337          44 :     index_close(indexRel, AccessShareLock);
     338             : 
     339          44 :     return (Datum) 0;
     340             : }
     341             : 
     342             : Datum
     343          30 : brin_metapage_info(PG_FUNCTION_ARGS)
     344             : {
     345          30 :     bytea      *raw_page = PG_GETARG_BYTEA_P(0);
     346             :     Page        page;
     347             :     BrinMetaPageData *meta;
     348             :     TupleDesc   tupdesc;
     349             :     Datum       values[4];
     350          30 :     bool        nulls[4] = {0};
     351             :     HeapTuple   htup;
     352             : 
     353          30 :     if (!superuser())
     354           0 :         ereport(ERROR,
     355             :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     356             :                  errmsg("must be superuser to use raw page functions")));
     357             : 
     358          30 :     page = verify_brin_page(raw_page, BRIN_PAGETYPE_META, "metapage");
     359             : 
     360          26 :     if (PageIsNew(page))
     361           2 :         PG_RETURN_NULL();
     362             : 
     363             :     /* Build a tuple descriptor for our result type */
     364          24 :     if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
     365           0 :         elog(ERROR, "return type must be a row type");
     366          24 :     tupdesc = BlessTupleDesc(tupdesc);
     367             : 
     368             :     /* Extract values from the metapage */
     369          24 :     meta = (BrinMetaPageData *) PageGetContents(page);
     370          24 :     values[0] = CStringGetTextDatum(psprintf("0x%08X", meta->brinMagic));
     371          24 :     values[1] = Int32GetDatum(meta->brinVersion);
     372          24 :     values[2] = Int32GetDatum(meta->pagesPerRange);
     373          24 :     values[3] = Int64GetDatum(meta->lastRevmapPage);
     374             : 
     375          24 :     htup = heap_form_tuple(tupdesc, values, nulls);
     376             : 
     377          24 :     PG_RETURN_DATUM(HeapTupleGetDatum(htup));
     378             : }
     379             : 
     380             : /*
     381             :  * Return the TID array stored in a BRIN revmap page
     382             :  */
     383             : Datum
     384        2728 : brin_revmap_data(PG_FUNCTION_ARGS)
     385             : {
     386             :     struct
     387             :     {
     388             :         ItemPointerData *tids;
     389             :         int         idx;
     390             :     }          *state;
     391             :     FuncCallContext *fctx;
     392             : 
     393        2728 :     if (!superuser())
     394           0 :         ereport(ERROR,
     395             :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     396             :                  errmsg("must be superuser to use raw page functions")));
     397             : 
     398        2728 :     if (SRF_IS_FIRSTCALL())
     399             :     {
     400           8 :         bytea      *raw_page = PG_GETARG_BYTEA_P(0);
     401             :         MemoryContext mctx;
     402             :         Page        page;
     403             : 
     404             :         /* create a function context for cross-call persistence */
     405           8 :         fctx = SRF_FIRSTCALL_INIT();
     406             : 
     407             :         /* switch to memory context appropriate for multiple function calls */
     408           8 :         mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
     409             : 
     410             :         /* minimally verify the page we got */
     411           8 :         page = verify_brin_page(raw_page, BRIN_PAGETYPE_REVMAP, "revmap");
     412             : 
     413           4 :         if (PageIsNew(page))
     414             :         {
     415           2 :             MemoryContextSwitchTo(mctx);
     416           2 :             PG_RETURN_NULL();
     417             :         }
     418             : 
     419           2 :         state = palloc(sizeof(*state));
     420           2 :         state->tids = ((RevmapContents *) PageGetContents(page))->rm_tids;
     421           2 :         state->idx = 0;
     422             : 
     423           2 :         fctx->user_fctx = state;
     424             : 
     425           2 :         MemoryContextSwitchTo(mctx);
     426             :     }
     427             : 
     428        2722 :     fctx = SRF_PERCALL_SETUP();
     429        2722 :     state = fctx->user_fctx;
     430             : 
     431        2722 :     if (state->idx < REVMAP_PAGE_MAXITEMS)
     432        2720 :         SRF_RETURN_NEXT(fctx, PointerGetDatum(&state->tids[state->idx++]));
     433             : 
     434           2 :     SRF_RETURN_DONE(fctx);
     435             : }

Generated by: LCOV version 1.14