LCOV - code coverage report
Current view: top level - contrib/pageinspect - brinfuncs.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 86.5 % 171 148
Test Date: 2026-07-03 19:57:34 Functions: 100.0 % 9 9
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 67.1 % 85 57

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

Generated by: LCOV version 2.0-1