3 * Functions to investigate BRIN indexes
5 * Copyright (c) 2014-2024, PostgreSQL Global Development Group
8 * contrib/pageinspect/brinfuncs.c
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"
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
40 FmgrInfo outputFn
[FLEXIBLE_ARRAY_MEMBER
];
44 static Page
verify_brin_page(bytea
*raw_page
, uint16 type
,
48 brin_page_type(PG_FUNCTION_ARGS
)
50 bytea
*raw_page
= PG_GETARG_BYTEA_P(0);
56 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE
),
57 errmsg("must be superuser to use raw page functions")));
59 page
= get_page_from_raw(raw_page
);
64 /* verify the special space has the expected size */
65 if (PageGetSpecialSize(page
) != MAXALIGN(sizeof(BrinSpecialSpace
)))
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
:
78 case BRIN_PAGETYPE_REVMAP
:
81 case BRIN_PAGETYPE_REGULAR
:
85 type
= psprintf("unknown (%02x)", BrinPageType(page
));
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.
97 verify_brin_page(bytea
*raw_page
, uint16 type
, const char *strtype
)
99 Page page
= get_page_from_raw(raw_page
);
104 /* verify the special space has the expected size */
105 if (PageGetSpecialSize(page
) != MAXALIGN(sizeof(BrinSpecialSpace
)))
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
)
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
))));
126 * Extract all item values from a BRIN index page
128 * Usage: SELECT * FROM brin_page_items(get_raw_page('idx', 1), 'idx'::regclass);
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
;
137 brin_column_state
**columns
;
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
))
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");
167 brin_free_desc(bdesc
);
168 index_close(indexRel
, AccessShareLock
);
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
++)
181 BrinOpcInfo
*opcinfo
;
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
;
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.
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
),
235 values
[0] = UInt16GetDatum(offset
);
248 values
[0] = UInt16GetDatum(offset
);
249 switch (TupleDescAttr(rsinfo
->setDesc
, 1)->atttypid
)
252 values
[1] = Int64GetDatum((int64
) dtup
->bt_blkno
);
255 /* support for old extension version */
256 values
[1] = UInt32GetDatum(dtup
->bt_blkno
);
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
];
274 appendStringInfoChar(&s
, '{');
277 for (i
= 0; i
< columns
[att
]->nstored
; i
++)
282 appendStringInfoString(&s
, " .. ");
284 val
= OutputFunctionCall(&columns
[att
]->outputFn
[i
],
285 bvalues
->bv_values
[i
]);
286 appendStringInfoString(&s
, val
);
289 appendStringInfoChar(&s
, '}');
291 values
[7] = CStringGetTextDatum(s
.data
);
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.
308 offset
= OffsetNumberNext(offset
);
309 else if (attno
>= bdesc
->bd_tupdesc
->natts
)
313 offset
= OffsetNumberNext(offset
);
317 * If we're beyond the end of the page, we're done.
319 if (offset
> PageGetMaxOffsetNumber(page
))
323 brin_free_desc(bdesc
);
324 index_close(indexRel
, AccessShareLock
);
330 brin_metapage_info(PG_FUNCTION_ARGS
)
332 bytea
*raw_page
= PG_GETARG_BYTEA_P(0);
334 BrinMetaPageData
*meta
;
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");
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
371 brin_revmap_data(PG_FUNCTION_ARGS
)
375 ItemPointerData
*tids
;
378 FuncCallContext
*fctx
;
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);
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");
402 MemoryContextSwitchTo(mctx
);
406 state
= palloc(sizeof(*state
));
407 state
->tids
= ((RevmapContents
*) PageGetContents(page
))->rm_tids
;
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
);