Revert commit 66c0185a3 and follow-on patches.
[pgsql.git] / contrib / pageinspect / brinfuncs.c
blob5a38d926689d79119b7a016c241a2376dbd7d9ed
1 /*
2 * brinfuncs.c
3 * Functions to investigate BRIN indexes
5 * Copyright (c) 2014-2024, PostgreSQL Global Development Group
7 * IDENTIFICATION
8 * contrib/pageinspect/brinfuncs.c
9 */
10 #include "postgres.h"
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"
30 PG_FUNCTION_INFO_V1(brin_page_type);
31 PG_FUNCTION_INFO_V1(brin_page_items);
32 PG_FUNCTION_INFO_V1(brin_metapage_info);
33 PG_FUNCTION_INFO_V1(brin_revmap_data);
35 #define IS_BRIN(r) ((r)->rd_rel->relam == BRIN_AM_OID)
37 typedef struct brin_column_state
39 int nstored;
40 FmgrInfo outputFn[FLEXIBLE_ARRAY_MEMBER];
41 } brin_column_state;
44 static Page verify_brin_page(bytea *raw_page, uint16 type,
45 const char *strtype);
47 Datum
48 brin_page_type(PG_FUNCTION_ARGS)
50 bytea *raw_page = PG_GETARG_BYTEA_P(0);
51 Page page;
52 char *type;
54 if (!superuser())
55 ereport(ERROR,
56 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
57 errmsg("must be superuser to use raw page functions")));
59 page = get_page_from_raw(raw_page);
61 if (PageIsNew(page))
62 PG_RETURN_NULL();
64 /* verify the special space has the expected size */
65 if (PageGetSpecialSize(page) != MAXALIGN(sizeof(BrinSpecialSpace)))
66 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))));
73 switch (BrinPageType(page))
75 case BRIN_PAGETYPE_META:
76 type = "meta";
77 break;
78 case BRIN_PAGETYPE_REVMAP:
79 type = "revmap";
80 break;
81 case BRIN_PAGETYPE_REGULAR:
82 type = "regular";
83 break;
84 default:
85 type = psprintf("unknown (%02x)", BrinPageType(page));
86 break;
89 PG_RETURN_TEXT_P(cstring_to_text(type));
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.
96 static Page
97 verify_brin_page(bytea *raw_page, uint16 type, const char *strtype)
99 Page page = get_page_from_raw(raw_page);
101 if (PageIsNew(page))
102 return page;
104 /* verify the special space has the expected size */
105 if (PageGetSpecialSize(page) != MAXALIGN(sizeof(BrinSpecialSpace)))
106 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))));
113 /* verify the special space says this page is what we want */
114 if (BrinPageType(page) != type)
115 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))));
121 return page;
126 * Extract all item values from a BRIN index page
128 * Usage: SELECT * FROM brin_page_items(get_raw_page('idx', 1), 'idx'::regclass);
130 Datum
131 brin_page_items(PG_FUNCTION_ARGS)
133 bytea *raw_page = PG_GETARG_BYTEA_P(0);
134 Oid indexRelid = PG_GETARG_OID(1);
135 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;
145 if (!superuser())
146 ereport(ERROR,
147 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
148 errmsg("must be superuser to use raw page functions")));
150 InitMaterializedSRF(fcinfo, 0);
152 indexRel = index_open(indexRelid, AccessShareLock);
154 if (!IS_BRIN(indexRel))
155 ereport(ERROR,
156 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
157 errmsg("\"%s\" is not a %s index",
158 RelationGetRelationName(indexRel), "BRIN")));
160 bdesc = brin_build_desc(indexRel);
162 /* minimally verify the page we got */
163 page = verify_brin_page(raw_page, BRIN_PAGETYPE_REGULAR, "regular");
165 if (PageIsNew(page))
167 brin_free_desc(bdesc);
168 index_close(indexRel, AccessShareLock);
169 PG_RETURN_NULL();
173 * Initialize output functions for all indexed datatypes; simplifies
174 * calling them later.
176 columns = palloc(sizeof(brin_column_state *) * RelationGetDescr(indexRel)->natts);
177 for (attno = 1; attno <= bdesc->bd_tupdesc->natts; attno++)
179 Oid output;
180 bool isVarlena;
181 BrinOpcInfo *opcinfo;
182 int i;
183 brin_column_state *column;
185 opcinfo = bdesc->bd_info[attno - 1];
186 column = palloc(offsetof(brin_column_state, outputFn) +
187 sizeof(FmgrInfo) * opcinfo->oi_nstored);
189 column->nstored = opcinfo->oi_nstored;
190 for (i = 0; i < opcinfo->oi_nstored; i++)
192 getTypeOutputInfo(opcinfo->oi_typcache[i]->type_id, &output, &isVarlena);
193 fmgr_info(output, &column->outputFn[i]);
196 columns[attno - 1] = column;
199 offset = FirstOffsetNumber;
200 unusedItem = false;
201 dtup = NULL;
202 for (;;)
204 Datum values[8];
205 bool nulls[8] = {0};
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.
213 if (dtup == NULL)
215 ItemId itemId;
217 /* verify item status: if there's no data, we can't decode */
218 itemId = PageGetItemId(page, offset);
219 if (ItemIdIsUsed(itemId))
221 dtup = brin_deform_tuple(bdesc,
222 (BrinTuple *) PageGetItem(page, itemId),
223 NULL);
224 attno = 1;
225 unusedItem = false;
227 else
228 unusedItem = true;
230 else
231 attno++;
233 if (unusedItem)
235 values[0] = UInt16GetDatum(offset);
236 nulls[1] = true;
237 nulls[2] = true;
238 nulls[3] = true;
239 nulls[4] = true;
240 nulls[5] = true;
241 nulls[6] = true;
242 nulls[7] = true;
244 else
246 int att = attno - 1;
248 values[0] = UInt16GetDatum(offset);
249 switch (TupleDescAttr(rsinfo->setDesc, 1)->atttypid)
251 case INT8OID:
252 values[1] = Int64GetDatum((int64) dtup->bt_blkno);
253 break;
254 case INT4OID:
255 /* support for old extension version */
256 values[1] = UInt32GetDatum(dtup->bt_blkno);
257 break;
258 default:
259 elog(ERROR, "incorrect output types");
261 values[2] = UInt16GetDatum(attno);
262 values[3] = BoolGetDatum(dtup->bt_columns[att].bv_allnulls);
263 values[4] = BoolGetDatum(dtup->bt_columns[att].bv_hasnulls);
264 values[5] = BoolGetDatum(dtup->bt_placeholder);
265 values[6] = BoolGetDatum(dtup->bt_empty_range);
266 if (!dtup->bt_columns[att].bv_allnulls)
268 BrinValues *bvalues = &dtup->bt_columns[att];
269 StringInfoData s;
270 bool first;
271 int i;
273 initStringInfo(&s);
274 appendStringInfoChar(&s, '{');
276 first = true;
277 for (i = 0; i < columns[att]->nstored; i++)
279 char *val;
281 if (!first)
282 appendStringInfoString(&s, " .. ");
283 first = false;
284 val = OutputFunctionCall(&columns[att]->outputFn[i],
285 bvalues->bv_values[i]);
286 appendStringInfoString(&s, val);
287 pfree(val);
289 appendStringInfoChar(&s, '}');
291 values[7] = CStringGetTextDatum(s.data);
292 pfree(s.data);
294 else
296 nulls[7] = true;
300 tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
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.
307 if (unusedItem)
308 offset = OffsetNumberNext(offset);
309 else if (attno >= bdesc->bd_tupdesc->natts)
311 pfree(dtup);
312 dtup = NULL;
313 offset = OffsetNumberNext(offset);
317 * If we're beyond the end of the page, we're done.
319 if (offset > PageGetMaxOffsetNumber(page))
320 break;
323 brin_free_desc(bdesc);
324 index_close(indexRel, AccessShareLock);
326 return (Datum) 0;
329 Datum
330 brin_metapage_info(PG_FUNCTION_ARGS)
332 bytea *raw_page = PG_GETARG_BYTEA_P(0);
333 Page page;
334 BrinMetaPageData *meta;
335 TupleDesc tupdesc;
336 Datum values[4];
337 bool nulls[4] = {0};
338 HeapTuple htup;
340 if (!superuser())
341 ereport(ERROR,
342 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
343 errmsg("must be superuser to use raw page functions")));
345 page = verify_brin_page(raw_page, BRIN_PAGETYPE_META, "metapage");
347 if (PageIsNew(page))
348 PG_RETURN_NULL();
350 /* Build a tuple descriptor for our result type */
351 if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
352 elog(ERROR, "return type must be a row type");
353 tupdesc = BlessTupleDesc(tupdesc);
355 /* Extract values from the metapage */
356 meta = (BrinMetaPageData *) PageGetContents(page);
357 values[0] = CStringGetTextDatum(psprintf("0x%08X", meta->brinMagic));
358 values[1] = Int32GetDatum(meta->brinVersion);
359 values[2] = Int32GetDatum(meta->pagesPerRange);
360 values[3] = Int64GetDatum(meta->lastRevmapPage);
362 htup = heap_form_tuple(tupdesc, values, nulls);
364 PG_RETURN_DATUM(HeapTupleGetDatum(htup));
368 * Return the TID array stored in a BRIN revmap page
370 Datum
371 brin_revmap_data(PG_FUNCTION_ARGS)
373 struct
375 ItemPointerData *tids;
376 int idx;
377 } *state;
378 FuncCallContext *fctx;
380 if (!superuser())
381 ereport(ERROR,
382 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
383 errmsg("must be superuser to use raw page functions")));
385 if (SRF_IS_FIRSTCALL())
387 bytea *raw_page = PG_GETARG_BYTEA_P(0);
388 MemoryContext mctx;
389 Page page;
391 /* create a function context for cross-call persistence */
392 fctx = SRF_FIRSTCALL_INIT();
394 /* switch to memory context appropriate for multiple function calls */
395 mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
397 /* minimally verify the page we got */
398 page = verify_brin_page(raw_page, BRIN_PAGETYPE_REVMAP, "revmap");
400 if (PageIsNew(page))
402 MemoryContextSwitchTo(mctx);
403 PG_RETURN_NULL();
406 state = palloc(sizeof(*state));
407 state->tids = ((RevmapContents *) PageGetContents(page))->rm_tids;
408 state->idx = 0;
410 fctx->user_fctx = state;
412 MemoryContextSwitchTo(mctx);
415 fctx = SRF_PERCALL_SETUP();
416 state = fctx->user_fctx;
418 if (state->idx < REVMAP_PAGE_MAXITEMS)
419 SRF_RETURN_NEXT(fctx, PointerGetDatum(&state->tids[state->idx++]));
421 SRF_RETURN_DONE(fctx);