Age Owner Branch data TLA Line data Source code
1 : : /*
2 : : * ginfuncs.c
3 : : * Functions to investigate the content of GIN indexes
4 : : *
5 : : * Copyright (c) 2014-2025, PostgreSQL Global Development Group
6 : : *
7 : : * IDENTIFICATION
8 : : * contrib/pageinspect/ginfuncs.c
9 : : */
10 : : #include "postgres.h"
11 : :
12 : : #include "access/gin_private.h"
13 : : #include "access/htup_details.h"
14 : : #include "catalog/pg_type.h"
15 : : #include "funcapi.h"
16 : : #include "miscadmin.h"
17 : : #include "pageinspect.h"
18 : : #include "utils/array.h"
19 : : #include "utils/builtins.h"
20 : :
21 : :
3942 heikki.linnakangas@i 22 :CBC 7 : PG_FUNCTION_INFO_V1(gin_metapage_info);
23 : 7 : PG_FUNCTION_INFO_V1(gin_page_opaque_info);
24 : 7 : PG_FUNCTION_INFO_V1(gin_leafpage_items);
25 : :
26 : :
27 : : Datum
28 : 5 : gin_metapage_info(PG_FUNCTION_ARGS)
29 : : {
30 : 5 : bytea *raw_page = PG_GETARG_BYTEA_P(0);
31 : : TupleDesc tupdesc;
32 : : Page page;
33 : : GinPageOpaque opaq;
34 : : GinMetaPageData *metadata;
35 : : HeapTuple resultTuple;
36 : : Datum values[10];
37 : : bool nulls[10];
38 : :
39 [ - + ]: 5 : if (!superuser())
3942 heikki.linnakangas@i 40 [ # # ]:UBC 0 : ereport(ERROR,
41 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
42 : : errmsg("must be superuser to use raw page functions")));
43 : :
3228 peter_e@gmx.net 44 :CBC 5 : page = get_page_from_raw(raw_page);
45 : :
1241 michael@paquier.xyz 46 [ + + ]: 4 : if (PageIsNew(page))
47 : 1 : PG_RETURN_NULL();
48 : :
1259 49 [ + + ]: 3 : if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData)))
50 [ + - ]: 1 : ereport(ERROR,
51 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
52 : : errmsg("input page is not a valid GIN metapage"),
53 : : errdetail("Expected special size %d, got %d.",
54 : : (int) MAXALIGN(sizeof(GinPageOpaqueData)),
55 : : (int) PageGetSpecialSize(page))));
56 : :
1253 57 : 2 : opaq = GinPageGetOpaque(page);
58 : :
3942 heikki.linnakangas@i 59 [ + + ]: 2 : if (opaq->flags != GIN_META)
60 [ + - ]: 1 : ereport(ERROR,
61 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
62 : : errmsg("input page is not a GIN metapage"),
63 : : errdetail("Flags %04X, expected %04X",
64 : : opaq->flags, GIN_META)));
65 : :
66 : : /* Build a tuple descriptor for our result type */
67 [ - + ]: 1 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
3942 heikki.linnakangas@i 68 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
69 : :
3942 heikki.linnakangas@i 70 :CBC 1 : metadata = GinPageGetMeta(page);
71 : :
72 : 1 : memset(nulls, 0, sizeof(nulls));
73 : :
74 : 1 : values[0] = Int64GetDatum(metadata->head);
75 : 1 : values[1] = Int64GetDatum(metadata->tail);
76 : 1 : values[2] = Int32GetDatum(metadata->tailFreeSize);
77 : 1 : values[3] = Int64GetDatum(metadata->nPendingPages);
78 : 1 : values[4] = Int64GetDatum(metadata->nPendingHeapTuples);
79 : :
80 : : /* statistics, updated by VACUUM */
81 : 1 : values[5] = Int64GetDatum(metadata->nTotalPages);
82 : 1 : values[6] = Int64GetDatum(metadata->nEntryPages);
83 : 1 : values[7] = Int64GetDatum(metadata->nDataPages);
84 : 1 : values[8] = Int64GetDatum(metadata->nEntries);
85 : :
86 : 1 : values[9] = Int32GetDatum(metadata->ginVersion);
87 : :
88 : : /* Build and return the result tuple. */
89 : 1 : resultTuple = heap_form_tuple(tupdesc, values, nulls);
90 : :
91 : 1 : return HeapTupleGetDatum(resultTuple);
92 : : }
93 : :
94 : :
95 : : Datum
96 : 4 : gin_page_opaque_info(PG_FUNCTION_ARGS)
97 : : {
98 : 4 : bytea *raw_page = PG_GETARG_BYTEA_P(0);
99 : : TupleDesc tupdesc;
100 : : Page page;
101 : : GinPageOpaque opaq;
102 : : HeapTuple resultTuple;
103 : : Datum values[3];
104 : : bool nulls[3];
105 : : Datum flags[16];
106 : 4 : int nflags = 0;
107 : : uint16 flagbits;
108 : :
109 [ - + ]: 4 : if (!superuser())
3942 heikki.linnakangas@i 110 [ # # ]:UBC 0 : ereport(ERROR,
111 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
112 : : errmsg("must be superuser to use raw page functions")));
113 : :
3228 peter_e@gmx.net 114 :CBC 4 : page = get_page_from_raw(raw_page);
115 : :
1241 michael@paquier.xyz 116 [ + + ]: 3 : if (PageIsNew(page))
117 : 1 : PG_RETURN_NULL();
118 : :
1259 119 [ + + ]: 2 : if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData)))
120 [ + - ]: 1 : ereport(ERROR,
121 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
122 : : errmsg("input page is not a valid GIN data leaf page"),
123 : : errdetail("Expected special size %d, got %d.",
124 : : (int) MAXALIGN(sizeof(GinPageOpaqueData)),
125 : : (int) PageGetSpecialSize(page))));
126 : :
1253 127 : 1 : opaq = GinPageGetOpaque(page);
128 : :
129 : : /* Build a tuple descriptor for our result type */
3942 heikki.linnakangas@i 130 [ - + ]: 1 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
3942 heikki.linnakangas@i 131 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
132 : :
133 : : /* Convert the flags bitmask to an array of human-readable names */
3942 heikki.linnakangas@i 134 :CBC 1 : flagbits = opaq->flags;
135 [ - + ]: 1 : if (flagbits & GIN_DATA)
3942 heikki.linnakangas@i 136 :UBC 0 : flags[nflags++] = CStringGetTextDatum("data");
3942 heikki.linnakangas@i 137 [ + - ]:CBC 1 : if (flagbits & GIN_LEAF)
138 : 1 : flags[nflags++] = CStringGetTextDatum("leaf");
139 [ - + ]: 1 : if (flagbits & GIN_DELETED)
3942 heikki.linnakangas@i 140 :UBC 0 : flags[nflags++] = CStringGetTextDatum("deleted");
3942 heikki.linnakangas@i 141 [ - + ]:CBC 1 : if (flagbits & GIN_META)
3942 heikki.linnakangas@i 142 :UBC 0 : flags[nflags++] = CStringGetTextDatum("meta");
3942 heikki.linnakangas@i 143 [ - + ]:CBC 1 : if (flagbits & GIN_LIST)
3942 heikki.linnakangas@i 144 :UBC 0 : flags[nflags++] = CStringGetTextDatum("list");
3942 heikki.linnakangas@i 145 [ - + ]:CBC 1 : if (flagbits & GIN_LIST_FULLROW)
3942 heikki.linnakangas@i 146 :UBC 0 : flags[nflags++] = CStringGetTextDatum("list_fullrow");
3942 heikki.linnakangas@i 147 [ - + ]:CBC 1 : if (flagbits & GIN_INCOMPLETE_SPLIT)
3942 heikki.linnakangas@i 148 :UBC 0 : flags[nflags++] = CStringGetTextDatum("incomplete_split");
3942 heikki.linnakangas@i 149 [ - + ]:CBC 1 : if (flagbits & GIN_COMPRESSED)
3942 heikki.linnakangas@i 150 :UBC 0 : flags[nflags++] = CStringGetTextDatum("compressed");
3942 heikki.linnakangas@i 151 :CBC 1 : flagbits &= ~(GIN_DATA | GIN_LEAF | GIN_DELETED | GIN_META | GIN_LIST |
152 : : GIN_LIST_FULLROW | GIN_INCOMPLETE_SPLIT | GIN_COMPRESSED);
153 [ - + ]: 1 : if (flagbits)
154 : : {
155 : : /* any flags we don't recognize are printed in hex */
3942 heikki.linnakangas@i 156 :UBC 0 : flags[nflags++] = DirectFunctionCall1(to_hex32, Int32GetDatum(flagbits));
157 : : }
158 : :
3942 heikki.linnakangas@i 159 :CBC 1 : memset(nulls, 0, sizeof(nulls));
160 : :
161 : 1 : values[0] = Int64GetDatum(opaq->rightlink);
3230 tgl@sss.pgh.pa.us 162 : 1 : values[1] = Int32GetDatum(opaq->maxoff);
1163 peter@eisentraut.org 163 : 1 : values[2] = PointerGetDatum(construct_array_builtin(flags, nflags, TEXTOID));
164 : :
165 : : /* Build and return the result tuple. */
3942 heikki.linnakangas@i 166 : 1 : resultTuple = heap_form_tuple(tupdesc, values, nulls);
167 : :
168 : 1 : return HeapTupleGetDatum(resultTuple);
169 : : }
170 : :
171 : : typedef struct gin_leafpage_items_state
172 : : {
173 : : TupleDesc tupd;
174 : : GinPostingList *seg;
175 : : GinPostingList *lastseg;
176 : : } gin_leafpage_items_state;
177 : :
178 : : Datum
179 : 26 : gin_leafpage_items(PG_FUNCTION_ARGS)
180 : : {
181 : 26 : bytea *raw_page = PG_GETARG_BYTEA_P(0);
182 : : FuncCallContext *fctx;
183 : : gin_leafpage_items_state *inter_call_data;
184 : :
185 [ - + ]: 26 : if (!superuser())
3942 heikki.linnakangas@i 186 [ # # ]:UBC 0 : ereport(ERROR,
187 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
188 : : errmsg("must be superuser to use raw page functions")));
189 : :
3942 heikki.linnakangas@i 190 [ + + ]:CBC 26 : if (SRF_IS_FIRSTCALL())
191 : : {
192 : : TupleDesc tupdesc;
193 : : MemoryContext mctx;
194 : : Page page;
195 : : GinPageOpaque opaq;
196 : :
3228 tgl@sss.pgh.pa.us 197 : 5 : fctx = SRF_FIRSTCALL_INIT();
198 : 5 : mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
199 : :
peter_e@gmx.net 200 : 5 : page = get_page_from_raw(raw_page);
201 : :
1241 michael@paquier.xyz 202 [ + + ]: 4 : if (PageIsNew(page))
203 : : {
204 : 1 : MemoryContextSwitchTo(mctx);
205 : 1 : PG_RETURN_NULL();
206 : : }
207 : :
3942 heikki.linnakangas@i 208 [ + + ]: 3 : if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData)))
209 [ + - ]: 1 : ereport(ERROR,
210 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
211 : : errmsg("input page is not a valid GIN data leaf page"),
212 : : errdetail("Expected special size %d, got %d.",
213 : : (int) MAXALIGN(sizeof(GinPageOpaqueData)),
214 : : (int) PageGetSpecialSize(page))));
215 : :
1253 michael@paquier.xyz 216 : 2 : opaq = GinPageGetOpaque(page);
3942 heikki.linnakangas@i 217 [ + + ]: 2 : if (opaq->flags != (GIN_DATA | GIN_LEAF | GIN_COMPRESSED))
218 [ + - ]: 1 : ereport(ERROR,
219 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
220 : : errmsg("input page is not a compressed GIN data leaf page"),
221 : : errdetail("Flags %04X, expected %04X",
222 : : opaq->flags,
223 : : (GIN_DATA | GIN_LEAF | GIN_COMPRESSED))));
224 : :
225 : 1 : inter_call_data = palloc(sizeof(gin_leafpage_items_state));
226 : :
227 : : /* Build a tuple descriptor for our result type */
228 [ - + ]: 1 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
3942 heikki.linnakangas@i 229 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
230 : :
3942 heikki.linnakangas@i 231 :CBC 1 : inter_call_data->tupd = tupdesc;
232 : :
233 : 1 : inter_call_data->seg = GinDataLeafPageGetPostingList(page);
234 : 1 : inter_call_data->lastseg = (GinPostingList *)
235 : 1 : (((char *) inter_call_data->seg) +
236 : 1 : GinDataLeafPageGetPostingListSize(page));
237 : :
238 : 1 : fctx->user_fctx = inter_call_data;
239 : :
240 : 1 : MemoryContextSwitchTo(mctx);
241 : : }
242 : :
243 : 22 : fctx = SRF_PERCALL_SETUP();
244 : 22 : inter_call_data = fctx->user_fctx;
245 : :
246 [ + + ]: 22 : if (inter_call_data->seg != inter_call_data->lastseg)
247 : : {
248 : 21 : GinPostingList *cur = inter_call_data->seg;
249 : : HeapTuple resultTuple;
250 : : Datum result;
251 : : Datum values[3];
252 : : bool nulls[3];
253 : : int ndecoded,
254 : : i;
255 : : ItemPointer tids;
256 : : Datum *tids_datum;
257 : :
258 : 21 : memset(nulls, 0, sizeof(nulls));
259 : :
260 : 21 : values[0] = ItemPointerGetDatum(&cur->first);
261 : 21 : values[1] = UInt16GetDatum(cur->nbytes);
262 : :
263 : : /* build an array of decoded item pointers */
264 : 21 : tids = ginPostingListDecode(cur, &ndecoded);
265 : 21 : tids_datum = (Datum *) palloc(ndecoded * sizeof(Datum));
266 [ + + ]: 6082 : for (i = 0; i < ndecoded; i++)
267 : 6061 : tids_datum[i] = ItemPointerGetDatum(&tids[i]);
1163 peter@eisentraut.org 268 : 21 : values[2] = PointerGetDatum(construct_array_builtin(tids_datum, ndecoded, TIDOID));
3942 heikki.linnakangas@i 269 : 21 : pfree(tids_datum);
270 : 21 : pfree(tids);
271 : :
272 : : /* Build and return the result tuple. */
273 : 21 : resultTuple = heap_form_tuple(inter_call_data->tupd, values, nulls);
274 : 21 : result = HeapTupleGetDatum(resultTuple);
275 : :
276 : 21 : inter_call_data->seg = GinNextPostingListSegment(cur);
277 : :
278 : 21 : SRF_RETURN_NEXT(fctx, result);
279 : : }
280 : :
2000 tgl@sss.pgh.pa.us 281 : 1 : SRF_RETURN_DONE(fctx);
282 : : }
|