Age Owner Branch data TLA 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 : :
4197 alvherre@alvh.no-ip. 27 :CBC 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 : :
3690 51 [ - + ]: 5 : if (!superuser())
3690 alvherre@alvh.no-ip. 52 [ # # ]:UBC 0 : ereport(ERROR,
53 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
54 : : errmsg("must be superuser to use raw page functions")));
55 : :
1511 michael@paquier.xyz 56 :CBC 5 : page = get_page_from_raw(raw_page);
57 : :
1482 58 [ + + ]: 5 : if (PageIsNew(page))
59 : 1 : PG_RETURN_NULL();
60 : :
61 : : /* verify the special space has the expected size */
1500 62 [ + + ]: 4 : if (PageGetSpecialSize(page) != MAXALIGN(sizeof(BrinSpecialSpace)))
1454 tgl@sss.pgh.pa.us 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 : :
4074 alvherre@alvh.no-ip. 70 [ + + + - ]: 3 : switch (BrinPageType(page))
71 : : {
4197 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;
4197 alvherre@alvh.no-ip. 81 :UBC 0 : default:
4074 82 : 0 : type = psprintf("unknown (%02x)", BrinPageType(page));
4197 83 : 0 : break;
84 : : }
85 : :
4197 alvherre@alvh.no-ip. 86 :CBC 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 : : {
1511 michael@paquier.xyz 96 : 42 : Page page = get_page_from_raw(raw_page);
97 : :
1482 98 [ + + ]: 42 : if (PageIsNew(page))
99 : 3 : return page;
100 : :
101 : : /* verify the special space has the expected size */
1500 102 [ + + ]: 39 : if (PageGetSpecialSize(page) != MAXALIGN(sizeof(BrinSpecialSpace)))
1454 tgl@sss.pgh.pa.us 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 */
4074 alvherre@alvh.no-ip. 111 [ + + ]: 36 : if (BrinPageType(page) != type)
4197 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 : : {
3918 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 : :
4197 144 [ - + ]: 25 : if (!superuser())
4197 alvherre@alvh.no-ip. 145 [ # # ]:UBC 0 : ereport(ERROR,
146 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
147 : : errmsg("must be superuser to use raw page functions")));
148 : :
1295 michael@paquier.xyz 149 :CBC 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 : : */
504 tomas.vondra@postgre 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 : :
3918 alvherre@alvh.no-ip. 166 : 24 : indexRel = index_open(indexRelid, AccessShareLock);
167 : :
1511 michael@paquier.xyz 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 : :
3918 alvherre@alvh.no-ip. 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 : :
1482 michael@paquier.xyz 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 : : */
151 michael@paquier.xyz 190 :GNC 21 : columns = palloc_array(brin_column_state *, RelationGetDescr(indexRel)->natts);
3918 alvherre@alvh.no-ip. 191 [ + + ]:CBC 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 (;;)
4197 217 : 618 : {
218 : : Datum values[8];
1082 tomas.vondra@postgre 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 : : */
3918 alvherre@alvh.no-ip. 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);
4197 233 [ + - ]: 183 : if (ItemIdIsUsed(itemId))
234 : : {
3918 235 : 183 : dtup = brin_deform_tuple(bdesc,
3240 tgl@sss.pgh.pa.us 236 : 183 : (BrinTuple *) PageGetItem(page, itemId),
237 : : NULL);
3918 alvherre@alvh.no-ip. 238 : 183 : attno = 1;
239 : 183 : unusedItem = false;
240 : : }
241 : : else
3918 alvherre@alvh.no-ip. 242 :UBC 0 : unusedItem = true;
243 : : }
244 : : else
3918 alvherre@alvh.no-ip. 245 :CBC 456 : attno++;
246 : :
247 [ - + ]: 639 : if (unusedItem)
248 : : {
3918 alvherre@alvh.no-ip. 249 :UBC 0 : values[0] = UInt16GetDatum(offset);
4197 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;
1082 tomas.vondra@postgre 256 : 0 : nulls[7] = true;
257 : : }
258 : : else
259 : : {
3918 alvherre@alvh.no-ip. 260 :CBC 639 : int att = attno - 1;
261 : :
262 : 639 : values[0] = UInt16GetDatum(offset);
1519 michael@paquier.xyz 263 [ + - - ]: 639 : switch (TupleDescAttr(rsinfo->setDesc, 1)->atttypid)
264 : : {
1932 peter@eisentraut.org 265 : 639 : case INT8OID:
266 : 639 : values[1] = Int64GetDatum((int64) dtup->bt_blkno);
267 : 639 : break;
1932 peter@eisentraut.org 268 :UBC 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 : : }
3918 alvherre@alvh.no-ip. 275 :CBC 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);
1082 tomas.vondra@postgre 279 : 639 : values[6] = BoolGetDatum(dtup->bt_empty_range);
3918 alvherre@alvh.no-ip. 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 : :
4197 287 : 528 : initStringInfo(&s);
288 : 528 : appendStringInfoChar(&s, '{');
289 : :
290 : 528 : first = true;
3918 291 [ + + ]: 1336 : for (i = 0; i < columns[att]->nstored; i++)
292 : : {
293 : : char *val;
294 : :
4197 295 [ + + ]: 808 : if (!first)
296 : 280 : appendStringInfoString(&s, " .. ");
297 : 808 : first = false;
3918 298 : 808 : val = OutputFunctionCall(&columns[att]->outputFn[i],
4197 299 : 808 : bvalues->bv_values[i]);
300 : 808 : appendStringInfoString(&s, val);
301 : 808 : pfree(val);
302 : : }
303 : 528 : appendStringInfoChar(&s, '}');
304 : :
1082 tomas.vondra@postgre 305 : 528 : values[7] = CStringGetTextDatum(s.data);
4197 alvherre@alvh.no-ip. 306 : 528 : pfree(s.data);
307 : : }
308 : : else
309 : : {
1082 tomas.vondra@postgre 310 : 111 : nulls[7] = true;
311 : : }
312 : : }
313 : :
1519 michael@paquier.xyz 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 : : */
3918 alvherre@alvh.no-ip. 321 [ - + ]: 639 : if (unusedItem)
3918 alvherre@alvh.no-ip. 322 :UBC 0 : offset = OffsetNumberNext(offset);
3918 alvherre@alvh.no-ip. 323 [ + + ]:CBC 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
4197 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];
1389 peter@eisentraut.org 351 : 15 : bool nulls[4] = {0};
352 : : HeapTuple htup;
353 : :
3690 alvherre@alvh.no-ip. 354 [ - + ]: 15 : if (!superuser())
3690 alvherre@alvh.no-ip. 355 [ # # ]:UBC 0 : ereport(ERROR,
356 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
357 : : errmsg("must be superuser to use raw page functions")));
358 : :
4197 alvherre@alvh.no-ip. 359 :CBC 15 : page = verify_brin_page(raw_page, BRIN_PAGETYPE_META, "metapage");
360 : :
1482 michael@paquier.xyz 361 [ + + ]: 13 : if (PageIsNew(page))
362 : 1 : PG_RETURN_NULL();
363 : :
364 : : /* Build a tuple descriptor for our result type */
4197 alvherre@alvh.no-ip. 365 [ - + ]: 12 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
4197 alvherre@alvh.no-ip. 366 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
4197 alvherre@alvh.no-ip. 367 :CBC 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())
4197 alvherre@alvh.no-ip. 395 [ # # ]:UBC 0 : ereport(ERROR,
396 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
397 : : errmsg("must be superuser to use raw page functions")));
398 : :
4197 alvherre@alvh.no-ip. 399 [ + + ]:CBC 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 */
1511 michael@paquier.xyz 412 : 4 : page = verify_brin_page(raw_page, BRIN_PAGETYPE_REVMAP, "revmap");
413 : :
1482 414 [ + + ]: 2 : if (PageIsNew(page))
415 : : {
416 : 1 : MemoryContextSwitchTo(mctx);
417 : 1 : PG_RETURN_NULL();
418 : : }
419 : :
4197 alvherre@alvh.no-ip. 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 : : }
|