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