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 : }
|