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