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