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