Age Owner Branch data TLA 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 : :
3956 alvherre@alvh.no-ip. 26 :CBC 7 : PG_FUNCTION_INFO_V1(brin_page_type);
27 : 31 : PG_FUNCTION_INFO_V1(brin_page_items);
28 : 8 : PG_FUNCTION_INFO_V1(brin_metapage_info);
29 : 7 : 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 : 5 : brin_page_type(PG_FUNCTION_ARGS)
45 : : {
46 : 5 : bytea *raw_page = PG_GETARG_BYTEA_P(0);
47 : : Page page;
48 : : char *type;
49 : :
3449 50 [ - + ]: 5 : if (!superuser())
3449 alvherre@alvh.no-ip. 51 [ # # ]:UBC 0 : ereport(ERROR,
52 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
53 : : errmsg("must be superuser to use raw page functions")));
54 : :
1270 michael@paquier.xyz 55 :CBC 5 : page = get_page_from_raw(raw_page);
56 : :
1241 57 [ + + ]: 5 : if (PageIsNew(page))
58 : 1 : PG_RETURN_NULL();
59 : :
60 : : /* verify the special space has the expected size */
1259 61 [ + + ]: 4 : if (PageGetSpecialSize(page) != MAXALIGN(sizeof(BrinSpecialSpace)))
1213 tgl@sss.pgh.pa.us 62 [ + - ]: 1 : 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 : :
3833 alvherre@alvh.no-ip. 69 [ + + + - ]: 3 : switch (BrinPageType(page))
70 : : {
3956 71 : 1 : case BRIN_PAGETYPE_META:
72 : 1 : type = "meta";
73 : 1 : break;
74 : 1 : case BRIN_PAGETYPE_REVMAP:
75 : 1 : type = "revmap";
76 : 1 : break;
77 : 1 : case BRIN_PAGETYPE_REGULAR:
78 : 1 : type = "regular";
79 : 1 : break;
3956 alvherre@alvh.no-ip. 80 :UBC 0 : default:
3833 81 : 0 : type = psprintf("unknown (%02x)", BrinPageType(page));
3956 82 : 0 : break;
83 : : }
84 : :
3956 alvherre@alvh.no-ip. 85 :CBC 3 : 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 : 45 : verify_brin_page(bytea *raw_page, uint16 type, const char *strtype)
94 : : {
1270 michael@paquier.xyz 95 : 45 : Page page = get_page_from_raw(raw_page);
96 : :
1241 97 [ + + ]: 45 : if (PageIsNew(page))
98 : 3 : return page;
99 : :
100 : : /* verify the special space has the expected size */
1259 101 [ + + ]: 42 : if (PageGetSpecialSize(page) != MAXALIGN(sizeof(BrinSpecialSpace)))
1213 tgl@sss.pgh.pa.us 102 [ + - ]: 3 : 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 */
3833 alvherre@alvh.no-ip. 110 [ + + ]: 39 : if (BrinPageType(page) != type)
3956 111 [ + - ]: 2 : 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 : 37 : 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 : 28 : brin_page_items(PG_FUNCTION_ARGS)
130 : : {
3677 131 : 28 : bytea *raw_page = PG_GETARG_BYTEA_P(0);
132 : 28 : Oid indexRelid = PG_GETARG_OID(1);
133 : 28 : 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 : :
3956 143 [ - + ]: 28 : if (!superuser())
3956 alvherre@alvh.no-ip. 144 [ # # ]:UBC 0 : ereport(ERROR,
145 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
146 : : errmsg("must be superuser to use raw page functions")));
147 : :
1054 michael@paquier.xyz 148 :CBC 28 : 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 : : */
263 tomas.vondra@postgre 159 [ + + ]: 28 : if (rsinfo->setDesc->natts < BRIN_PAGE_ITEMS_V1_12)
160 [ + - ]: 1 : 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 : :
3677 alvherre@alvh.no-ip. 165 : 27 : indexRel = index_open(indexRelid, AccessShareLock);
166 : :
1270 michael@paquier.xyz 167 [ + + ]: 27 : if (!IS_BRIN(indexRel))
168 [ + - ]: 1 : ereport(ERROR,
169 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
170 : : errmsg("\"%s\" is not a %s index",
171 : : RelationGetRelationName(indexRel), "BRIN")));
172 : :
3677 alvherre@alvh.no-ip. 173 : 26 : bdesc = brin_build_desc(indexRel);
174 : :
175 : : /* minimally verify the page we got */
176 : 26 : page = verify_brin_page(raw_page, BRIN_PAGETYPE_REGULAR, "regular");
177 : :
1241 michael@paquier.xyz 178 [ + + ]: 25 : if (PageIsNew(page))
179 : : {
180 : 1 : brin_free_desc(bdesc);
181 : 1 : index_close(indexRel, AccessShareLock);
182 : 1 : PG_RETURN_NULL();
183 : : }
184 : :
185 : : /*
186 : : * Initialize output functions for all indexed datatypes; simplifies
187 : : * calling them later.
188 : : */
3677 alvherre@alvh.no-ip. 189 : 24 : columns = palloc(sizeof(brin_column_state *) * RelationGetDescr(indexRel)->natts);
190 [ + + ]: 72 : 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 : 48 : opcinfo = bdesc->bd_info[attno - 1];
199 : 48 : column = palloc(offsetof(brin_column_state, outputFn) +
200 : 48 : sizeof(FmgrInfo) * opcinfo->oi_nstored);
201 : :
202 : 48 : column->nstored = opcinfo->oi_nstored;
203 [ + + ]: 128 : for (i = 0; i < opcinfo->oi_nstored; i++)
204 : : {
205 : 80 : getTypeOutputInfo(opcinfo->oi_typcache[i]->type_id, &output, &isVarlena);
206 : 80 : fmgr_info(output, &column->outputFn[i]);
207 : : }
208 : :
209 : 48 : columns[attno - 1] = column;
210 : : }
211 : :
212 : 24 : offset = FirstOffsetNumber;
213 : 24 : unusedItem = false;
214 : 24 : dtup = NULL;
215 : : for (;;)
3956 216 : 612 : {
217 : : Datum values[8];
841 tomas.vondra@postgre 218 : 636 : 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 : : */
3677 alvherre@alvh.no-ip. 226 [ + + ]: 636 : if (dtup == NULL)
227 : : {
228 : : ItemId itemId;
229 : :
230 : : /* verify item status: if there's no data, we can't decode */
231 : 180 : itemId = PageGetItemId(page, offset);
3956 232 [ + - ]: 180 : if (ItemIdIsUsed(itemId))
233 : : {
3677 234 : 180 : dtup = brin_deform_tuple(bdesc,
2999 tgl@sss.pgh.pa.us 235 : 180 : (BrinTuple *) PageGetItem(page, itemId),
236 : : NULL);
3677 alvherre@alvh.no-ip. 237 : 180 : attno = 1;
238 : 180 : unusedItem = false;
239 : : }
240 : : else
3677 alvherre@alvh.no-ip. 241 :UBC 0 : unusedItem = true;
242 : : }
243 : : else
3677 alvherre@alvh.no-ip. 244 :CBC 456 : attno++;
245 : :
246 [ - + ]: 636 : if (unusedItem)
247 : : {
3677 alvherre@alvh.no-ip. 248 :UBC 0 : values[0] = UInt16GetDatum(offset);
3956 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;
841 tomas.vondra@postgre 255 : 0 : nulls[7] = true;
256 : : }
257 : : else
258 : : {
3677 alvherre@alvh.no-ip. 259 :CBC 636 : int att = attno - 1;
260 : :
261 : 636 : values[0] = UInt16GetDatum(offset);
1278 michael@paquier.xyz 262 [ + - - ]: 636 : switch (TupleDescAttr(rsinfo->setDesc, 1)->atttypid)
263 : : {
1691 peter@eisentraut.org 264 : 636 : case INT8OID:
265 : 636 : values[1] = Int64GetDatum((int64) dtup->bt_blkno);
266 : 636 : break;
1691 peter@eisentraut.org 267 :UBC 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 : : }
3677 alvherre@alvh.no-ip. 274 :CBC 636 : values[2] = UInt16GetDatum(attno);
275 : 636 : values[3] = BoolGetDatum(dtup->bt_columns[att].bv_allnulls);
276 : 636 : values[4] = BoolGetDatum(dtup->bt_columns[att].bv_hasnulls);
277 : 636 : values[5] = BoolGetDatum(dtup->bt_placeholder);
841 tomas.vondra@postgre 278 : 636 : values[6] = BoolGetDatum(dtup->bt_empty_range);
3677 alvherre@alvh.no-ip. 279 [ + + ]: 636 : if (!dtup->bt_columns[att].bv_allnulls)
280 : : {
281 : 526 : BrinValues *bvalues = &dtup->bt_columns[att];
282 : : StringInfoData s;
283 : : bool first;
284 : : int i;
285 : :
3956 286 : 526 : initStringInfo(&s);
287 : 526 : appendStringInfoChar(&s, '{');
288 : :
289 : 526 : first = true;
3677 290 [ + + ]: 1330 : for (i = 0; i < columns[att]->nstored; i++)
291 : : {
292 : : char *val;
293 : :
3956 294 [ + + ]: 804 : if (!first)
295 : 278 : appendStringInfoString(&s, " .. ");
296 : 804 : first = false;
3677 297 : 804 : val = OutputFunctionCall(&columns[att]->outputFn[i],
3956 298 : 804 : bvalues->bv_values[i]);
299 : 804 : appendStringInfoString(&s, val);
300 : 804 : pfree(val);
301 : : }
302 : 526 : appendStringInfoChar(&s, '}');
303 : :
841 tomas.vondra@postgre 304 : 526 : values[7] = CStringGetTextDatum(s.data);
3956 alvherre@alvh.no-ip. 305 : 526 : pfree(s.data);
306 : : }
307 : : else
308 : : {
841 tomas.vondra@postgre 309 : 110 : nulls[7] = true;
310 : : }
311 : : }
312 : :
1278 michael@paquier.xyz 313 : 636 : 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 : : */
3677 alvherre@alvh.no-ip. 320 [ - + ]: 636 : if (unusedItem)
3677 alvherre@alvh.no-ip. 321 :UBC 0 : offset = OffsetNumberNext(offset);
3677 alvherre@alvh.no-ip. 322 [ + + ]:CBC 636 : else if (attno >= bdesc->bd_tupdesc->natts)
323 : : {
324 : 180 : pfree(dtup);
325 : 180 : dtup = NULL;
326 : 180 : offset = OffsetNumberNext(offset);
327 : : }
328 : :
329 : : /*
330 : : * If we're beyond the end of the page, we're done.
331 : : */
332 [ + + ]: 636 : if (offset > PageGetMaxOffsetNumber(page))
333 : 24 : break;
334 : : }
335 : :
336 : 24 : brin_free_desc(bdesc);
337 : 24 : index_close(indexRel, AccessShareLock);
338 : :
339 : 24 : return (Datum) 0;
340 : : }
341 : :
342 : : Datum
3956 343 : 15 : brin_metapage_info(PG_FUNCTION_ARGS)
344 : : {
345 : 15 : bytea *raw_page = PG_GETARG_BYTEA_P(0);
346 : : Page page;
347 : : BrinMetaPageData *meta;
348 : : TupleDesc tupdesc;
349 : : Datum values[4];
1148 peter@eisentraut.org 350 : 15 : bool nulls[4] = {0};
351 : : HeapTuple htup;
352 : :
3449 alvherre@alvh.no-ip. 353 [ - + ]: 15 : if (!superuser())
3449 alvherre@alvh.no-ip. 354 [ # # ]:UBC 0 : ereport(ERROR,
355 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
356 : : errmsg("must be superuser to use raw page functions")));
357 : :
3956 alvherre@alvh.no-ip. 358 :CBC 15 : page = verify_brin_page(raw_page, BRIN_PAGETYPE_META, "metapage");
359 : :
1241 michael@paquier.xyz 360 [ + + ]: 13 : if (PageIsNew(page))
361 : 1 : PG_RETURN_NULL();
362 : :
363 : : /* Build a tuple descriptor for our result type */
3956 alvherre@alvh.no-ip. 364 [ - + ]: 12 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
3956 alvherre@alvh.no-ip. 365 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
3956 alvherre@alvh.no-ip. 366 :CBC 12 : tupdesc = BlessTupleDesc(tupdesc);
367 : :
368 : : /* Extract values from the metapage */
369 : 12 : meta = (BrinMetaPageData *) PageGetContents(page);
370 : 12 : values[0] = CStringGetTextDatum(psprintf("0x%08X", meta->brinMagic));
371 : 12 : values[1] = Int32GetDatum(meta->brinVersion);
372 : 12 : values[2] = Int32GetDatum(meta->pagesPerRange);
373 : 12 : values[3] = Int64GetDatum(meta->lastRevmapPage);
374 : :
375 : 12 : htup = heap_form_tuple(tupdesc, values, nulls);
376 : :
377 : 12 : PG_RETURN_DATUM(HeapTupleGetDatum(htup));
378 : : }
379 : :
380 : : /*
381 : : * Return the TID array stored in a BRIN revmap page
382 : : */
383 : : Datum
384 : 1364 : brin_revmap_data(PG_FUNCTION_ARGS)
385 : : {
386 : : struct
387 : : {
388 : : ItemPointerData *tids;
389 : : int idx;
390 : : } *state;
391 : : FuncCallContext *fctx;
392 : :
393 [ - + ]: 1364 : if (!superuser())
3956 alvherre@alvh.no-ip. 394 [ # # ]:UBC 0 : ereport(ERROR,
395 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
396 : : errmsg("must be superuser to use raw page functions")));
397 : :
3956 alvherre@alvh.no-ip. 398 [ + + ]:CBC 1364 : if (SRF_IS_FIRSTCALL())
399 : : {
400 : 4 : 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 : 4 : fctx = SRF_FIRSTCALL_INIT();
406 : :
407 : : /* switch to memory context appropriate for multiple function calls */
408 : 4 : mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
409 : :
410 : : /* minimally verify the page we got */
1270 michael@paquier.xyz 411 : 4 : page = verify_brin_page(raw_page, BRIN_PAGETYPE_REVMAP, "revmap");
412 : :
1241 413 [ + + ]: 2 : if (PageIsNew(page))
414 : : {
415 : 1 : MemoryContextSwitchTo(mctx);
416 : 1 : PG_RETURN_NULL();
417 : : }
418 : :
3956 alvherre@alvh.no-ip. 419 : 1 : state = palloc(sizeof(*state));
420 : 1 : state->tids = ((RevmapContents *) PageGetContents(page))->rm_tids;
421 : 1 : state->idx = 0;
422 : :
423 : 1 : fctx->user_fctx = state;
424 : :
425 : 1 : MemoryContextSwitchTo(mctx);
426 : : }
427 : :
428 : 1361 : fctx = SRF_PERCALL_SETUP();
429 : 1361 : state = fctx->user_fctx;
430 : :
431 [ + + ]: 1361 : if (state->idx < REVMAP_PAGE_MAXITEMS)
432 : 1360 : SRF_RETURN_NEXT(fctx, PointerGetDatum(&state->tids[state->idx++]));
433 : :
434 : 1 : SRF_RETURN_DONE(fctx);
435 : : }
|