Age Owner 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 :
3075 alvherre 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 :
2568 54 5 : if (!superuser())
2568 alvherre 55 UBC 0 : ereport(ERROR,
56 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
57 : errmsg("must be superuser to use raw page functions")));
58 :
389 michael 59 CBC 5 : page = get_page_from_raw(raw_page);
60 :
360 61 5 : if (PageIsNew(page))
62 1 : PG_RETURN_NULL();
63 :
64 : /* verify the special space has the expected size */
378 65 4 : if (PageGetSpecialSize(page) != MAXALIGN(sizeof(BrinSpecialSpace)))
332 tgl 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 :
2952 alvherre 73 3 : switch (BrinPageType(page))
74 : {
3075 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;
3075 alvherre 84 UBC 0 : default:
2952 85 0 : type = psprintf("unknown (%02x)", BrinPageType(page));
3075 86 0 : break;
87 : }
88 :
3075 alvherre 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 : {
389 michael 99 29 : Page page = get_page_from_raw(raw_page);
100 :
360 101 29 : if (PageIsNew(page))
102 3 : return page;
103 :
104 : /* verify the special space has the expected size */
378 105 26 : if (PageGetSpecialSize(page) != MAXALIGN(sizeof(BrinSpecialSpace)))
332 tgl 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 */
2952 alvherre 114 23 : if (BrinPageType(page) != type)
3075 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 : {
2796 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 :
3075 145 12 : if (!superuser())
3075 alvherre 146 UBC 0 : ereport(ERROR,
147 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
148 : errmsg("must be superuser to use raw page functions")));
149 :
173 michael 150 CBC 12 : InitMaterializedSRF(fcinfo, 0);
151 :
2796 alvherre 152 12 : indexRel = index_open(indexRelid, AccessShareLock);
153 :
389 michael 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 :
2796 alvherre 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 :
360 michael 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 : */
2796 alvherre 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 (;;)
3075 203 8 : {
204 : Datum values[7];
267 peter 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 : */
2796 alvherre 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);
3075 219 17 : if (ItemIdIsUsed(itemId))
220 : {
2796 221 17 : dtup = brin_deform_tuple(bdesc,
2118 tgl 222 17 : (BrinTuple *) PageGetItem(page, itemId),
223 : NULL);
2796 alvherre 224 17 : attno = 1;
225 17 : unusedItem = false;
226 : }
227 : else
2796 alvherre 228 UBC 0 : unusedItem = true;
229 : }
230 : else
231 0 : attno++;
232 :
2796 alvherre 233 GBC 17 : if (unusedItem)
3075 alvherre 234 EUB : {
2796 alvherre 235 UBC 0 : values[0] = UInt16GetDatum(offset);
3075 236 0 : nulls[1] = true;
237 0 : nulls[2] = true;
238 0 : nulls[3] = true;
239 0 : nulls[4] = true;
3075 alvherre 240 UIC 0 : nulls[5] = true;
241 0 : nulls[6] = true;
242 : }
3075 alvherre 243 ECB : else
244 : {
2796 alvherre 245 CBC 17 : int att = attno - 1;
2796 alvherre 246 ECB :
2796 alvherre 247 GIC 17 : values[0] = UInt16GetDatum(offset);
397 michael 248 CBC 17 : switch (TupleDescAttr(rsinfo->setDesc, 1)->atttypid)
810 peter 249 ECB : {
810 peter 250 CBC 17 : case INT8OID:
810 peter 251 GBC 17 : values[1] = Int64GetDatum((int64) dtup->bt_blkno);
810 peter 252 GIC 17 : break;
810 peter 253 UBC 0 : case INT4OID:
810 peter 254 EUB : /* support for old extension version */
810 peter 255 UBC 0 : values[1] = UInt32GetDatum(dtup->bt_blkno);
256 0 : break;
810 peter 257 UIC 0 : default:
810 peter 258 LBC 0 : elog(ERROR, "incorrect output types");
810 peter 259 ECB : }
2796 alvherre 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);
2796 alvherre 263 GIC 17 : values[5] = BoolGetDatum(dtup->bt_placeholder);
2796 alvherre 264 CBC 17 : if (!dtup->bt_columns[att].bv_allnulls)
265 : {
2796 alvherre 266 GIC 16 : BrinValues *bvalues = &dtup->bt_columns[att];
267 : StringInfoData s;
268 : bool first;
3075 alvherre 269 ECB : int i;
270 :
3075 alvherre 271 GIC 16 : initStringInfo(&s);
3075 alvherre 272 CBC 16 : appendStringInfoChar(&s, '{');
3075 alvherre 273 ECB :
3075 alvherre 274 GIC 16 : first = true;
2796 275 48 : for (i = 0; i < columns[att]->nstored; i++)
276 : {
2878 bruce 277 ECB : char *val;
3075 alvherre 278 :
3075 alvherre 279 CBC 32 : if (!first)
280 16 : appendStringInfoString(&s, " .. ");
281 32 : first = false;
2796 282 32 : val = OutputFunctionCall(&columns[att]->outputFn[i],
3075 283 32 : bvalues->bv_values[i]);
3075 alvherre 284 GIC 32 : appendStringInfoString(&s, val);
3075 alvherre 285 CBC 32 : pfree(val);
286 : }
287 16 : appendStringInfoChar(&s, '}');
3075 alvherre 288 ECB :
3075 alvherre 289 GIC 16 : values[6] = CStringGetTextDatum(s.data);
290 16 : pfree(s.data);
291 : }
3075 alvherre 292 ECB : else
293 : {
3075 alvherre 294 GIC 1 : nulls[6] = true;
295 : }
3075 alvherre 296 ECB : }
297 :
397 michael 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
3075 alvherre 303 ECB : * tuple in the following iteration, by freeing the current one.
3075 alvherre 304 EUB : */
2796 alvherre 305 CBC 17 : if (unusedItem)
2796 alvherre 306 UIC 0 : offset = OffsetNumberNext(offset);
2796 alvherre 307 CBC 17 : else if (attno >= bdesc->bd_tupdesc->natts)
3075 alvherre 308 ECB : {
2796 alvherre 309 CBC 17 : pfree(dtup);
2796 alvherre 310 GIC 17 : dtup = NULL;
311 17 : offset = OffsetNumberNext(offset);
312 : }
313 :
314 : /*
2796 alvherre 315 ECB : * If we're beyond the end of the page, we're done.
3075 316 : */
2796 alvherre 317 GIC 17 : if (offset > PageGetMaxOffsetNumber(page))
318 9 : break;
3075 alvherre 319 ECB : }
320 :
2796 alvherre 321 GIC 9 : brin_free_desc(bdesc);
2796 alvherre 322 CBC 9 : index_close(indexRel, AccessShareLock);
323 :
2796 alvherre 324 GIC 9 : return (Datum) 0;
325 : }
3075 alvherre 326 ECB :
327 : Datum
3075 alvherre 328 CBC 14 : brin_metapage_info(PG_FUNCTION_ARGS)
329 : {
3075 alvherre 330 GIC 14 : bytea *raw_page = PG_GETARG_BYTEA_P(0);
331 : Page page;
332 : BrinMetaPageData *meta;
3075 alvherre 333 ECB : TupleDesc tupdesc;
334 : Datum values[4];
267 peter 335 GNC 14 : bool nulls[4] = {0};
3075 alvherre 336 ECB : HeapTuple htup;
3075 alvherre 337 EUB :
2568 alvherre 338 GIC 14 : if (!superuser())
2568 alvherre 339 UIC 0 : ereport(ERROR,
340 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1165 alvherre 341 ECB : errmsg("must be superuser to use raw page functions")));
342 :
3075 alvherre 343 CBC 14 : page = verify_brin_page(raw_page, BRIN_PAGETYPE_META, "metapage");
3075 alvherre 344 ECB :
360 michael 345 GIC 12 : if (PageIsNew(page))
346 1 : PG_RETURN_NULL();
360 michael 347 ECB :
3075 alvherre 348 EUB : /* Build a tuple descriptor for our result type */
3075 alvherre 349 CBC 11 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
3075 alvherre 350 UIC 0 : elog(ERROR, "return type must be a row type");
3075 alvherre 351 GIC 11 : tupdesc = BlessTupleDesc(tupdesc);
3075 alvherre 352 ECB :
353 : /* Extract values from the metapage */
3075 alvherre 354 CBC 11 : meta = (BrinMetaPageData *) PageGetContents(page);
355 11 : values[0] = CStringGetTextDatum(psprintf("0x%08X", meta->brinMagic));
3075 alvherre 356 GIC 11 : values[1] = Int32GetDatum(meta->brinVersion);
3075 alvherre 357 CBC 11 : values[2] = Int32GetDatum(meta->pagesPerRange);
3075 alvherre 358 GIC 11 : values[3] = Int64GetDatum(meta->lastRevmapPage);
3075 alvherre 359 ECB :
3075 alvherre 360 GIC 11 : htup = heap_form_tuple(tupdesc, values, nulls);
361 :
362 11 : PG_RETURN_DATUM(HeapTupleGetDatum(htup));
363 : }
364 :
365 : /*
3075 alvherre 366 ECB : * Return the TID array stored in a BRIN revmap page
367 : */
368 : Datum
3075 alvherre 369 GIC 1364 : brin_revmap_data(PG_FUNCTION_ARGS)
370 : {
371 : struct
372 : {
373 : ItemPointerData *tids;
374 : int idx;
2878 bruce 375 ECB : } *state;
3075 alvherre 376 EUB : FuncCallContext *fctx;
377 :
3075 alvherre 378 GIC 1364 : if (!superuser())
3075 alvherre 379 UIC 0 : ereport(ERROR,
3075 alvherre 380 ECB : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
381 : errmsg("must be superuser to use raw page functions")));
382 :
3075 alvherre 383 GIC 1364 : if (SRF_IS_FIRSTCALL())
384 : {
385 4 : bytea *raw_page = PG_GETARG_BYTEA_P(0);
386 : MemoryContext mctx;
3075 alvherre 387 ECB : Page page;
388 :
389 : /* create a function context for cross-call persistence */
3075 alvherre 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 :
389 michael 395 ECB : /* minimally verify the page we got */
389 michael 396 GIC 4 : page = verify_brin_page(raw_page, BRIN_PAGETYPE_REVMAP, "revmap");
389 michael 397 ECB :
360 michael 398 CBC 2 : if (PageIsNew(page))
399 : {
360 michael 400 GIC 1 : MemoryContextSwitchTo(mctx);
360 michael 401 CBC 1 : PG_RETURN_NULL();
360 michael 402 ECB : }
403 :
3075 alvherre 404 GIC 1 : state = palloc(sizeof(*state));
3075 alvherre 405 CBC 1 : state->tids = ((RevmapContents *) PageGetContents(page))->rm_tids;
3075 alvherre 406 GIC 1 : state->idx = 0;
3075 alvherre 407 ECB :
3075 alvherre 408 GIC 1 : fctx->user_fctx = state;
409 :
3075 alvherre 410 CBC 1 : MemoryContextSwitchTo(mctx);
3075 alvherre 411 ECB : }
412 :
3075 alvherre 413 CBC 1361 : fctx = SRF_PERCALL_SETUP();
414 1361 : state = fctx->user_fctx;
415 :
416 1361 : if (state->idx < REVMAP_PAGE_MAXITEMS)
3075 alvherre 417 GIC 1360 : SRF_RETURN_NEXT(fctx, PointerGetDatum(&state->tids[state->idx++]));
418 :
419 1 : SRF_RETURN_DONE(fctx);
420 : }
|