Age Owner Branch data TLA 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 : :
3446 alvherre@alvh.no-ip. 30 :CBC 7 : PG_FUNCTION_INFO_V1(brin_page_type);
31 : 28 : 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 : :
2939 54 [ - + ]: 5 : if (!superuser())
2939 alvherre@alvh.no-ip. 55 [ # # ]:UBC 0 : ereport(ERROR,
56 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
57 : : errmsg("must be superuser to use raw page functions")));
58 : :
760 michael@paquier.xyz 59 :CBC 5 : page = get_page_from_raw(raw_page);
60 : :
731 61 [ + + ]: 5 : if (PageIsNew(page))
62 : 1 : PG_RETURN_NULL();
63 : :
64 : : /* verify the special space has the expected size */
749 65 [ + + ]: 4 : if (PageGetSpecialSize(page) != MAXALIGN(sizeof(BrinSpecialSpace)))
703 tgl@sss.pgh.pa.us 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 : :
3323 alvherre@alvh.no-ip. 73 [ + + + - ]: 3 : switch (BrinPageType(page))
74 : : {
3446 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;
3446 alvherre@alvh.no-ip. 84 :UBC 0 : default:
3323 85 : 0 : type = psprintf("unknown (%02x)", BrinPageType(page));
3446 86 : 0 : break;
87 : : }
88 : :
3446 alvherre@alvh.no-ip. 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 : 44 : verify_brin_page(bytea *raw_page, uint16 type, const char *strtype)
98 : : {
760 michael@paquier.xyz 99 : 44 : Page page = get_page_from_raw(raw_page);
100 : :
731 101 [ + + ]: 44 : if (PageIsNew(page))
102 : 3 : return page;
103 : :
104 : : /* verify the special space has the expected size */
749 105 [ + + ]: 41 : if (PageGetSpecialSize(page) != MAXALIGN(sizeof(BrinSpecialSpace)))
703 tgl@sss.pgh.pa.us 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 */
3323 alvherre@alvh.no-ip. 114 [ + + ]: 38 : if (BrinPageType(page) != type)
3446 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 : 36 : 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 : 26 : brin_page_items(PG_FUNCTION_ARGS)
132 : : {
3167 133 : 26 : bytea *raw_page = PG_GETARG_BYTEA_P(0);
134 : 26 : Oid indexRelid = PG_GETARG_OID(1);
135 : 26 : 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 : :
3446 145 [ - + ]: 26 : if (!superuser())
3446 alvherre@alvh.no-ip. 146 [ # # ]:UBC 0 : ereport(ERROR,
147 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
148 : : errmsg("must be superuser to use raw page functions")));
149 : :
544 michael@paquier.xyz 150 :CBC 26 : InitMaterializedSRF(fcinfo, 0);
151 : :
3167 alvherre@alvh.no-ip. 152 : 26 : indexRel = index_open(indexRelid, AccessShareLock);
153 : :
760 michael@paquier.xyz 154 [ + + ]: 26 : 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 : :
3167 alvherre@alvh.no-ip. 160 : 25 : bdesc = brin_build_desc(indexRel);
161 : :
162 : : /* minimally verify the page we got */
163 : 25 : page = verify_brin_page(raw_page, BRIN_PAGETYPE_REGULAR, "regular");
164 : :
731 michael@paquier.xyz 165 [ + + ]: 24 : 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 : : */
3167 alvherre@alvh.no-ip. 176 : 23 : columns = palloc(sizeof(brin_column_state *) * RelationGetDescr(indexRel)->natts);
177 [ + + ]: 70 : 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 : 47 : opcinfo = bdesc->bd_info[attno - 1];
186 : 47 : column = palloc(offsetof(brin_column_state, outputFn) +
187 : 47 : sizeof(FmgrInfo) * opcinfo->oi_nstored);
188 : :
189 : 47 : column->nstored = opcinfo->oi_nstored;
190 [ + + ]: 125 : for (i = 0; i < opcinfo->oi_nstored; i++)
191 : : {
192 : 78 : getTypeOutputInfo(opcinfo->oi_typcache[i]->type_id, &output, &isVarlena);
193 : 78 : fmgr_info(output, &column->outputFn[i]);
194 : : }
195 : :
196 : 47 : columns[attno - 1] = column;
197 : : }
198 : :
199 : 23 : offset = FirstOffsetNumber;
200 : 23 : unusedItem = false;
201 : 23 : dtup = NULL;
202 : : for (;;)
3446 203 : 516 : {
204 : : Datum values[8];
331 tomas.vondra@postgre 205 : 539 : 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 : : */
3167 alvherre@alvh.no-ip. 213 [ + + ]: 539 : if (dtup == NULL)
214 : : {
215 : : ItemId itemId;
216 : :
217 : : /* verify item status: if there's no data, we can't decode */
218 : 155 : itemId = PageGetItemId(page, offset);
3446 219 [ + - ]: 155 : if (ItemIdIsUsed(itemId))
220 : : {
3167 221 : 155 : dtup = brin_deform_tuple(bdesc,
2489 tgl@sss.pgh.pa.us 222 : 155 : (BrinTuple *) PageGetItem(page, itemId),
223 : : NULL);
3167 alvherre@alvh.no-ip. 224 : 155 : attno = 1;
225 : 155 : unusedItem = false;
226 : : }
227 : : else
3167 alvherre@alvh.no-ip. 228 :UBC 0 : unusedItem = true;
229 : : }
230 : : else
3167 alvherre@alvh.no-ip. 231 :GBC 384 : attno++;
232 : :
3167 alvherre@alvh.no-ip. 233 [ - + ]:CBC 539 : if (unusedItem)
234 : : {
3167 alvherre@alvh.no-ip. 235 :UBC 0 : values[0] = UInt16GetDatum(offset);
3446 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;
331 tomas.vondra@postgre 242 : 0 : nulls[7] = true;
243 : : }
244 : : else
245 : : {
3167 alvherre@alvh.no-ip. 246 :CBC 539 : int att = attno - 1;
247 : :
248 : 539 : values[0] = UInt16GetDatum(offset);
768 michael@paquier.xyz 249 [ + - - ]: 539 : switch (TupleDescAttr(rsinfo->setDesc, 1)->atttypid)
250 : : {
1181 peter@eisentraut.org 251 : 539 : case INT8OID:
252 : 539 : values[1] = Int64GetDatum((int64) dtup->bt_blkno);
253 : 539 : break;
1181 peter@eisentraut.org 254 :UBC 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 : : }
3167 alvherre@alvh.no-ip. 261 :CBC 539 : values[2] = UInt16GetDatum(attno);
262 : 539 : values[3] = BoolGetDatum(dtup->bt_columns[att].bv_allnulls);
263 : 539 : values[4] = BoolGetDatum(dtup->bt_columns[att].bv_hasnulls);
264 : 539 : values[5] = BoolGetDatum(dtup->bt_placeholder);
331 tomas.vondra@postgre 265 : 539 : values[6] = BoolGetDatum(dtup->bt_empty_range);
3167 alvherre@alvh.no-ip. 266 [ + + ]: 539 : if (!dtup->bt_columns[att].bv_allnulls)
267 : : {
268 : 509 : BrinValues *bvalues = &dtup->bt_columns[att];
269 : : StringInfoData s;
270 : : bool first;
271 : : int i;
272 : :
3446 273 : 509 : initStringInfo(&s);
274 : 509 : appendStringInfoChar(&s, '{');
275 : :
276 : 509 : first = true;
3167 277 [ + + ]: 1287 : for (i = 0; i < columns[att]->nstored; i++)
278 : : {
279 : : char *val;
280 : :
3446 281 [ + + ]: 778 : if (!first)
282 : 269 : appendStringInfoString(&s, " .. ");
283 : 778 : first = false;
3167 284 : 778 : val = OutputFunctionCall(&columns[att]->outputFn[i],
3446 285 : 778 : bvalues->bv_values[i]);
286 : 778 : appendStringInfoString(&s, val);
287 : 778 : pfree(val);
288 : : }
289 : 509 : appendStringInfoChar(&s, '}');
290 : :
331 tomas.vondra@postgre 291 : 509 : values[7] = CStringGetTextDatum(s.data);
3446 alvherre@alvh.no-ip. 292 : 509 : pfree(s.data);
293 : : }
294 : : else
295 : : {
331 tomas.vondra@postgre 296 : 30 : nulls[7] = true;
297 : : }
298 : : }
299 : :
768 michael@paquier.xyz 300 : 539 : 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 : : */
3167 alvherre@alvh.no-ip. 307 [ - + ]: 539 : if (unusedItem)
3167 alvherre@alvh.no-ip. 308 :UBC 0 : offset = OffsetNumberNext(offset);
3167 alvherre@alvh.no-ip. 309 [ + + ]:CBC 539 : else if (attno >= bdesc->bd_tupdesc->natts)
310 : : {
311 : 155 : pfree(dtup);
312 : 155 : dtup = NULL;
313 : 155 : offset = OffsetNumberNext(offset);
314 : : }
315 : :
316 : : /*
317 : : * If we're beyond the end of the page, we're done.
318 : : */
319 [ + + ]: 539 : if (offset > PageGetMaxOffsetNumber(page))
320 : 23 : break;
321 : : }
322 : :
323 : 23 : brin_free_desc(bdesc);
324 : 23 : index_close(indexRel, AccessShareLock);
325 : :
326 : 23 : return (Datum) 0;
327 : : }
328 : :
329 : : Datum
3446 330 : 15 : brin_metapage_info(PG_FUNCTION_ARGS)
331 : : {
332 : 15 : bytea *raw_page = PG_GETARG_BYTEA_P(0);
333 : : Page page;
334 : : BrinMetaPageData *meta;
335 : : TupleDesc tupdesc;
336 : : Datum values[4];
638 peter@eisentraut.org 337 : 15 : bool nulls[4] = {0};
338 : : HeapTuple htup;
339 : :
2939 alvherre@alvh.no-ip. 340 [ - + ]: 15 : if (!superuser())
2939 alvherre@alvh.no-ip. 341 [ # # ]:UBC 0 : ereport(ERROR,
342 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
343 : : errmsg("must be superuser to use raw page functions")));
344 : :
3446 alvherre@alvh.no-ip. 345 :CBC 15 : page = verify_brin_page(raw_page, BRIN_PAGETYPE_META, "metapage");
346 : :
731 michael@paquier.xyz 347 [ + + ]: 13 : if (PageIsNew(page))
348 : 1 : PG_RETURN_NULL();
349 : :
350 : : /* Build a tuple descriptor for our result type */
3446 alvherre@alvh.no-ip. 351 [ - + ]: 12 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
3446 alvherre@alvh.no-ip. 352 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
3446 alvherre@alvh.no-ip. 353 :CBC 12 : tupdesc = BlessTupleDesc(tupdesc);
354 : :
355 : : /* Extract values from the metapage */
356 : 12 : meta = (BrinMetaPageData *) PageGetContents(page);
357 : 12 : values[0] = CStringGetTextDatum(psprintf("0x%08X", meta->brinMagic));
358 : 12 : values[1] = Int32GetDatum(meta->brinVersion);
359 : 12 : values[2] = Int32GetDatum(meta->pagesPerRange);
360 : 12 : values[3] = Int64GetDatum(meta->lastRevmapPage);
361 : :
362 : 12 : htup = heap_form_tuple(tupdesc, values, nulls);
363 : :
364 : 12 : PG_RETURN_DATUM(HeapTupleGetDatum(htup));
365 : : }
366 : :
367 : : /*
368 : : * Return the TID array stored in a BRIN revmap page
369 : : */
370 : : Datum
371 : 1364 : brin_revmap_data(PG_FUNCTION_ARGS)
372 : : {
373 : : struct
374 : : {
375 : : ItemPointerData *tids;
376 : : int idx;
377 : : } *state;
378 : : FuncCallContext *fctx;
379 : :
380 [ - + ]: 1364 : if (!superuser())
3446 alvherre@alvh.no-ip. 381 [ # # ]:UBC 0 : ereport(ERROR,
382 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
383 : : errmsg("must be superuser to use raw page functions")));
384 : :
3446 alvherre@alvh.no-ip. 385 [ + + ]:CBC 1364 : if (SRF_IS_FIRSTCALL())
386 : : {
387 : 4 : 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 : 4 : fctx = SRF_FIRSTCALL_INIT();
393 : :
394 : : /* switch to memory context appropriate for multiple function calls */
395 : 4 : mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
396 : :
397 : : /* minimally verify the page we got */
760 michael@paquier.xyz 398 : 4 : page = verify_brin_page(raw_page, BRIN_PAGETYPE_REVMAP, "revmap");
399 : :
731 400 [ + + ]: 2 : if (PageIsNew(page))
401 : : {
402 : 1 : MemoryContextSwitchTo(mctx);
403 : 1 : PG_RETURN_NULL();
404 : : }
405 : :
3446 alvherre@alvh.no-ip. 406 : 1 : state = palloc(sizeof(*state));
407 : 1 : state->tids = ((RevmapContents *) PageGetContents(page))->rm_tids;
408 : 1 : state->idx = 0;
409 : :
410 : 1 : fctx->user_fctx = state;
411 : :
412 : 1 : MemoryContextSwitchTo(mctx);
413 : : }
414 : :
415 : 1361 : fctx = SRF_PERCALL_SETUP();
416 : 1361 : state = fctx->user_fctx;
417 : :
418 [ + + ]: 1361 : if (state->idx < REVMAP_PAGE_MAXITEMS)
419 : 1360 : SRF_RETURN_NEXT(fctx, PointerGetDatum(&state->tids[state->idx++]));
420 : :
421 : 1 : SRF_RETURN_DONE(fctx);
422 : : }
|