Line data Source code
1 : /*
2 : * ginfuncs.c
3 : * Functions to investigate the content of GIN indexes
4 : *
5 : * Copyright (c) 2014-2026, 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 :
22 0 : PG_FUNCTION_INFO_V1(gin_metapage_info);
23 0 : PG_FUNCTION_INFO_V1(gin_page_opaque_info);
24 0 : PG_FUNCTION_INFO_V1(gin_leafpage_items);
25 :
26 :
27 : Datum
28 0 : gin_metapage_info(PG_FUNCTION_ARGS)
29 : {
30 0 : bytea *raw_page = PG_GETARG_BYTEA_P(0);
31 0 : TupleDesc tupdesc;
32 0 : Page page;
33 0 : GinPageOpaque opaq;
34 0 : GinMetaPageData *metadata;
35 0 : HeapTuple resultTuple;
36 0 : Datum values[10];
37 0 : bool nulls[10];
38 :
39 0 : if (!superuser())
40 0 : ereport(ERROR,
41 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
42 : errmsg("must be superuser to use raw page functions")));
43 :
44 0 : page = get_page_from_raw(raw_page);
45 :
46 0 : if (PageIsNew(page))
47 0 : PG_RETURN_NULL();
48 :
49 0 : if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData)))
50 0 : 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 :
57 0 : opaq = GinPageGetOpaque(page);
58 :
59 0 : if (opaq->flags != GIN_META)
60 0 : 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 0 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
68 0 : elog(ERROR, "return type must be a row type");
69 :
70 0 : metadata = GinPageGetMeta(page);
71 :
72 0 : memset(nulls, 0, sizeof(nulls));
73 :
74 0 : values[0] = Int64GetDatum(metadata->head);
75 0 : values[1] = Int64GetDatum(metadata->tail);
76 0 : values[2] = UInt32GetDatum(metadata->tailFreeSize);
77 0 : values[3] = Int64GetDatum(metadata->nPendingPages);
78 0 : values[4] = Int64GetDatum(metadata->nPendingHeapTuples);
79 :
80 : /* statistics, updated by VACUUM */
81 0 : values[5] = Int64GetDatum(metadata->nTotalPages);
82 0 : values[6] = Int64GetDatum(metadata->nEntryPages);
83 0 : values[7] = Int64GetDatum(metadata->nDataPages);
84 0 : values[8] = Int64GetDatum(metadata->nEntries);
85 :
86 0 : values[9] = Int32GetDatum(metadata->ginVersion);
87 :
88 : /* Build and return the result tuple. */
89 0 : resultTuple = heap_form_tuple(tupdesc, values, nulls);
90 :
91 0 : return HeapTupleGetDatum(resultTuple);
92 0 : }
93 :
94 :
95 : Datum
96 0 : gin_page_opaque_info(PG_FUNCTION_ARGS)
97 : {
98 0 : bytea *raw_page = PG_GETARG_BYTEA_P(0);
99 0 : TupleDesc tupdesc;
100 0 : Page page;
101 0 : GinPageOpaque opaq;
102 0 : HeapTuple resultTuple;
103 0 : Datum values[3];
104 0 : bool nulls[3];
105 0 : Datum flags[16];
106 0 : int nflags = 0;
107 0 : uint16 flagbits;
108 :
109 0 : if (!superuser())
110 0 : ereport(ERROR,
111 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
112 : errmsg("must be superuser to use raw page functions")));
113 :
114 0 : page = get_page_from_raw(raw_page);
115 :
116 0 : if (PageIsNew(page))
117 0 : PG_RETURN_NULL();
118 :
119 0 : if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData)))
120 0 : 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 :
127 0 : opaq = GinPageGetOpaque(page);
128 :
129 : /* Build a tuple descriptor for our result type */
130 0 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
131 0 : elog(ERROR, "return type must be a row type");
132 :
133 : /* Convert the flags bitmask to an array of human-readable names */
134 0 : flagbits = opaq->flags;
135 0 : if (flagbits & GIN_DATA)
136 0 : flags[nflags++] = CStringGetTextDatum("data");
137 0 : if (flagbits & GIN_LEAF)
138 0 : flags[nflags++] = CStringGetTextDatum("leaf");
139 0 : if (flagbits & GIN_DELETED)
140 0 : flags[nflags++] = CStringGetTextDatum("deleted");
141 0 : if (flagbits & GIN_META)
142 0 : flags[nflags++] = CStringGetTextDatum("meta");
143 0 : if (flagbits & GIN_LIST)
144 0 : flags[nflags++] = CStringGetTextDatum("list");
145 0 : if (flagbits & GIN_LIST_FULLROW)
146 0 : flags[nflags++] = CStringGetTextDatum("list_fullrow");
147 0 : if (flagbits & GIN_INCOMPLETE_SPLIT)
148 0 : flags[nflags++] = CStringGetTextDatum("incomplete_split");
149 0 : if (flagbits & GIN_COMPRESSED)
150 0 : flags[nflags++] = CStringGetTextDatum("compressed");
151 0 : flagbits &= ~(GIN_DATA | GIN_LEAF | GIN_DELETED | GIN_META | GIN_LIST |
152 : GIN_LIST_FULLROW | GIN_INCOMPLETE_SPLIT | GIN_COMPRESSED);
153 0 : if (flagbits)
154 : {
155 : /* any flags we don't recognize are printed in hex */
156 0 : flags[nflags++] = DirectFunctionCall1(to_hex32, Int32GetDatum(flagbits));
157 0 : }
158 :
159 0 : memset(nulls, 0, sizeof(nulls));
160 :
161 0 : values[0] = Int64GetDatum(opaq->rightlink);
162 0 : values[1] = Int32GetDatum(opaq->maxoff);
163 0 : values[2] = PointerGetDatum(construct_array_builtin(flags, nflags, TEXTOID));
164 :
165 : /* Build and return the result tuple. */
166 0 : resultTuple = heap_form_tuple(tupdesc, values, nulls);
167 :
168 0 : return HeapTupleGetDatum(resultTuple);
169 0 : }
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 0 : gin_leafpage_items(PG_FUNCTION_ARGS)
180 : {
181 0 : bytea *raw_page = PG_GETARG_BYTEA_P(0);
182 0 : FuncCallContext *fctx;
183 0 : gin_leafpage_items_state *inter_call_data;
184 :
185 0 : if (!superuser())
186 0 : ereport(ERROR,
187 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
188 : errmsg("must be superuser to use raw page functions")));
189 :
190 0 : if (SRF_IS_FIRSTCALL())
191 : {
192 0 : TupleDesc tupdesc;
193 0 : MemoryContext mctx;
194 0 : Page page;
195 0 : GinPageOpaque opaq;
196 :
197 0 : fctx = SRF_FIRSTCALL_INIT();
198 0 : mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
199 :
200 0 : page = get_page_from_raw(raw_page);
201 :
202 0 : if (PageIsNew(page))
203 : {
204 0 : MemoryContextSwitchTo(mctx);
205 0 : PG_RETURN_NULL();
206 0 : }
207 :
208 0 : if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData)))
209 0 : 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 :
216 0 : opaq = GinPageGetOpaque(page);
217 0 : if (opaq->flags != (GIN_DATA | GIN_LEAF | GIN_COMPRESSED))
218 0 : 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 0 : inter_call_data = palloc_object(gin_leafpage_items_state);
226 :
227 : /* Build a tuple descriptor for our result type */
228 0 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
229 0 : elog(ERROR, "return type must be a row type");
230 :
231 0 : inter_call_data->tupd = tupdesc;
232 :
233 0 : inter_call_data->seg = GinDataLeafPageGetPostingList(page);
234 0 : inter_call_data->lastseg = (GinPostingList *)
235 0 : (((char *) inter_call_data->seg) +
236 0 : GinDataLeafPageGetPostingListSize(page));
237 :
238 0 : fctx->user_fctx = inter_call_data;
239 :
240 0 : MemoryContextSwitchTo(mctx);
241 0 : }
242 :
243 0 : fctx = SRF_PERCALL_SETUP();
244 0 : inter_call_data = fctx->user_fctx;
245 :
246 0 : if (inter_call_data->seg != inter_call_data->lastseg)
247 : {
248 0 : GinPostingList *cur = inter_call_data->seg;
249 0 : HeapTuple resultTuple;
250 0 : Datum result;
251 0 : Datum values[3];
252 0 : bool nulls[3];
253 0 : int ndecoded,
254 : i;
255 0 : ItemPointer tids;
256 0 : Datum *tids_datum;
257 :
258 0 : memset(nulls, 0, sizeof(nulls));
259 :
260 0 : values[0] = ItemPointerGetDatum(&cur->first);
261 0 : values[1] = UInt16GetDatum(cur->nbytes);
262 :
263 : /* build an array of decoded item pointers */
264 0 : tids = ginPostingListDecode(cur, &ndecoded);
265 0 : tids_datum = (Datum *) palloc(ndecoded * sizeof(Datum));
266 0 : for (i = 0; i < ndecoded; i++)
267 0 : tids_datum[i] = ItemPointerGetDatum(&tids[i]);
268 0 : values[2] = PointerGetDatum(construct_array_builtin(tids_datum, ndecoded, TIDOID));
269 0 : pfree(tids_datum);
270 0 : pfree(tids);
271 :
272 : /* Build and return the result tuple. */
273 0 : resultTuple = heap_form_tuple(inter_call_data->tupd, values, nulls);
274 0 : result = HeapTupleGetDatum(resultTuple);
275 :
276 0 : inter_call_data->seg = GinNextPostingListSegment(cur);
277 :
278 0 : SRF_RETURN_NEXT(fctx, result);
279 0 : }
280 :
281 0 : SRF_RETURN_DONE(fctx);
282 0 : }
|