Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * ginutil.c
4 : : * Utility routines for the Postgres inverted index access method.
5 : : *
6 : : *
7 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
8 : : * Portions Copyright (c) 1994, Regents of the University of California
9 : : *
10 : : * IDENTIFICATION
11 : : * src/backend/access/gin/ginutil.c
12 : : *-------------------------------------------------------------------------
13 : : */
14 : :
15 : : #include "postgres.h"
16 : :
17 : : #include "access/gin_private.h"
18 : : #include "access/ginxlog.h"
19 : : #include "access/reloptions.h"
20 : : #include "access/xloginsert.h"
21 : : #include "catalog/pg_collation.h"
22 : : #include "catalog/pg_type.h"
23 : : #include "commands/progress.h"
24 : : #include "commands/vacuum.h"
25 : : #include "miscadmin.h"
26 : : #include "storage/indexfsm.h"
27 : : #include "utils/builtins.h"
28 : : #include "utils/index_selfuncs.h"
29 : : #include "utils/rel.h"
30 : : #include "utils/typcache.h"
31 : :
32 : :
33 : : /*
34 : : * GIN handler function: return IndexAmRoutine with access method parameters
35 : : * and callbacks.
36 : : */
37 : : Datum
38 : 117 : ginhandler(PG_FUNCTION_ARGS)
39 : : {
40 : : static const IndexAmRoutine amroutine = {
41 : : .type = T_IndexAmRoutine,
42 : : .amstrategies = 0,
43 : : .amsupport = GINNProcs,
44 : : .amoptsprocnum = GIN_OPTIONS_PROC,
45 : : .amcanorder = false,
46 : : .amcanorderbyop = false,
47 : : .amcanhash = false,
48 : : .amconsistentequality = false,
49 : : .amconsistentordering = false,
50 : : .amcanbackward = false,
51 : : .amcanunique = false,
52 : : .amcanmulticol = true,
53 : : .amoptionalkey = true,
54 : : .amsearcharray = false,
55 : : .amsearchnulls = false,
56 : : .amstorage = true,
57 : : .amclusterable = false,
58 : : .ampredlocks = true,
59 : : .amcanparallel = false,
60 : : .amcanbuildparallel = true,
61 : : .amcaninclude = false,
62 : : .amusemaintenanceworkmem = true,
63 : : .amsummarizing = false,
64 : : .amparallelvacuumoptions =
65 : : VACUUM_OPTION_PARALLEL_BULKDEL | VACUUM_OPTION_PARALLEL_CLEANUP,
66 : : .amkeytype = InvalidOid,
67 : :
68 : : .ambuild = ginbuild,
69 : : .ambuildempty = ginbuildempty,
70 : : .aminsert = gininsert,
71 : : .aminsertcleanup = NULL,
72 : : .ambulkdelete = ginbulkdelete,
73 : : .amvacuumcleanup = ginvacuumcleanup,
74 : : .amcanreturn = NULL,
75 : : .amcostestimate = gincostestimate,
76 : : .amgettreeheight = NULL,
77 : : .amoptions = ginoptions,
78 : : .amproperty = NULL,
79 : : .ambuildphasename = ginbuildphasename,
80 : : .amvalidate = ginvalidate,
81 : : .amadjustmembers = ginadjustmembers,
82 : : .ambeginscan = ginbeginscan,
83 : : .amrescan = ginrescan,
84 : : .amgettuple = NULL,
85 : : .amgetbitmap = gingetbitmap,
86 : : .amendscan = ginendscan,
87 : : .ammarkpos = NULL,
88 : : .amrestrpos = NULL,
89 : : .amestimateparallelscan = NULL,
90 : : .aminitparallelscan = NULL,
91 : : .amparallelrescan = NULL,
92 : : };
93 : :
94 : 117 : PG_RETURN_POINTER(&amroutine);
95 : : }
96 : :
97 : : /*
98 : : * initGinState: fill in an empty GinState struct to describe the index
99 : : *
100 : : * Note: assorted subsidiary data is allocated in the CurrentMemoryContext.
101 : : */
102 : : void
103 : 218 : initGinState(GinState *state, Relation index)
104 : : {
105 : 218 : TupleDesc origTupdesc = RelationGetDescr(index);
106 : 218 : int i;
107 : :
108 [ + - + - : 218 : MemSet(state, 0, sizeof(GinState));
+ - + - #
# ]
109 : :
110 : 218 : state->index = index;
111 : 218 : state->oneCol = (origTupdesc->natts == 1);
112 : 218 : state->origTupdesc = origTupdesc;
113 : :
114 [ + + ]: 482 : for (i = 0; i < origTupdesc->natts; i++)
115 : : {
116 : 264 : Form_pg_attribute attr = TupleDescAttr(origTupdesc, i);
117 : :
118 [ + + ]: 264 : if (state->oneCol)
119 : 172 : state->tupdesc[i] = state->origTupdesc;
120 : : else
121 : : {
122 : 92 : state->tupdesc[i] = CreateTemplateTupleDesc(2);
123 : :
124 : 92 : TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 1, NULL,
125 : : INT2OID, -1, 0);
126 : 184 : TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 2, NULL,
127 : 92 : attr->atttypid,
128 : 92 : attr->atttypmod,
129 : 92 : attr->attndims);
130 : 184 : TupleDescInitEntryCollation(state->tupdesc[i], (AttrNumber) 2,
131 : 92 : attr->attcollation);
132 : : }
133 : :
134 : : /*
135 : : * If the compare proc isn't specified in the opclass definition, look
136 : : * up the index key type's default btree comparator.
137 : : */
138 [ + + ]: 264 : if (index_getprocid(index, i + 1, GIN_COMPARE_PROC) != InvalidOid)
139 : : {
140 : 240 : fmgr_info_copy(&(state->compareFn[i]),
141 : 120 : index_getprocinfo(index, i + 1, GIN_COMPARE_PROC),
142 : 120 : CurrentMemoryContext);
143 : 120 : }
144 : : else
145 : : {
146 : 144 : TypeCacheEntry *typentry;
147 : :
148 : 144 : typentry = lookup_type_cache(attr->atttypid,
149 : : TYPECACHE_CMP_PROC_FINFO);
150 [ + - ]: 144 : if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))
151 [ # # # # ]: 0 : ereport(ERROR,
152 : : (errcode(ERRCODE_UNDEFINED_FUNCTION),
153 : : errmsg("could not identify a comparison function for type %s",
154 : : format_type_be(attr->atttypid))));
155 : 288 : fmgr_info_copy(&(state->compareFn[i]),
156 : 144 : &(typentry->cmp_proc_finfo),
157 : 144 : CurrentMemoryContext);
158 : 144 : }
159 : :
160 : : /* Opclass must always provide extract procs */
161 : 528 : fmgr_info_copy(&(state->extractValueFn[i]),
162 : 264 : index_getprocinfo(index, i + 1, GIN_EXTRACTVALUE_PROC),
163 : 264 : CurrentMemoryContext);
164 : 528 : fmgr_info_copy(&(state->extractQueryFn[i]),
165 : 264 : index_getprocinfo(index, i + 1, GIN_EXTRACTQUERY_PROC),
166 : 264 : CurrentMemoryContext);
167 : :
168 : : /*
169 : : * Check opclass capability to do tri-state or binary logic consistent
170 : : * check.
171 : : */
172 [ - + ]: 264 : if (index_getprocid(index, i + 1, GIN_TRICONSISTENT_PROC) != InvalidOid)
173 : : {
174 : 528 : fmgr_info_copy(&(state->triConsistentFn[i]),
175 : 264 : index_getprocinfo(index, i + 1, GIN_TRICONSISTENT_PROC),
176 : 264 : CurrentMemoryContext);
177 : 264 : }
178 : :
179 [ - + ]: 264 : if (index_getprocid(index, i + 1, GIN_CONSISTENT_PROC) != InvalidOid)
180 : : {
181 : 528 : fmgr_info_copy(&(state->consistentFn[i]),
182 : 264 : index_getprocinfo(index, i + 1, GIN_CONSISTENT_PROC),
183 : 264 : CurrentMemoryContext);
184 : 264 : }
185 : :
186 [ - + # # ]: 264 : if (state->consistentFn[i].fn_oid == InvalidOid &&
187 : 0 : state->triConsistentFn[i].fn_oid == InvalidOid)
188 : : {
189 [ # # # # ]: 0 : elog(ERROR, "missing GIN support function (%d or %d) for attribute %d of index \"%s\"",
190 : : GIN_CONSISTENT_PROC, GIN_TRICONSISTENT_PROC,
191 : : i + 1, RelationGetRelationName(index));
192 : 0 : }
193 : :
194 : : /*
195 : : * Check opclass capability to do partial match.
196 : : */
197 [ + + ]: 264 : if (index_getprocid(index, i + 1, GIN_COMPARE_PARTIAL_PROC) != InvalidOid)
198 : : {
199 : 80 : fmgr_info_copy(&(state->comparePartialFn[i]),
200 : 40 : index_getprocinfo(index, i + 1, GIN_COMPARE_PARTIAL_PROC),
201 : 40 : CurrentMemoryContext);
202 : 40 : state->canPartialMatch[i] = true;
203 : 40 : }
204 : : else
205 : : {
206 : 224 : state->canPartialMatch[i] = false;
207 : : }
208 : :
209 : : /*
210 : : * If the index column has a specified collation, we should honor that
211 : : * while doing comparisons. However, we may have a collatable storage
212 : : * type for a noncollatable indexed data type (for instance, hstore
213 : : * uses text index entries). If there's no index collation then
214 : : * specify default collation in case the support functions need
215 : : * collation. This is harmless if the support functions don't care
216 : : * about collation, so we just do it unconditionally. (We could
217 : : * alternatively call get_typcollation, but that seems like expensive
218 : : * overkill --- there aren't going to be any cases where a GIN storage
219 : : * type has a nondefault collation.)
220 : : */
221 [ + + ]: 264 : if (OidIsValid(index->rd_indcollation[i]))
222 : 22 : state->supportCollation[i] = index->rd_indcollation[i];
223 : : else
224 : 242 : state->supportCollation[i] = DEFAULT_COLLATION_OID;
225 : 264 : }
226 : 218 : }
227 : :
228 : : /*
229 : : * Extract attribute (column) number of stored entry from GIN tuple
230 : : */
231 : : OffsetNumber
232 : 2033336 : gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple)
233 : : {
234 : 2033336 : OffsetNumber colN;
235 : :
236 [ + + ]: 2033336 : if (ginstate->oneCol)
237 : : {
238 : : /* column number is not stored explicitly */
239 : 590181 : colN = FirstOffsetNumber;
240 : 590181 : }
241 : : else
242 : : {
243 : 1443155 : Datum res;
244 : 1443155 : bool isnull;
245 : :
246 : : /*
247 : : * First attribute is always int16, so we can safely use any tuple
248 : : * descriptor to obtain first attribute of tuple
249 : : */
250 : 1443155 : res = index_getattr(tuple, FirstOffsetNumber, ginstate->tupdesc[0],
251 : : &isnull);
252 [ + - ]: 1443155 : Assert(!isnull);
253 : :
254 : 1443155 : colN = DatumGetUInt16(res);
255 [ + - ]: 1443155 : Assert(colN >= FirstOffsetNumber && colN <= ginstate->origTupdesc->natts);
256 : 1443155 : }
257 : :
258 : 4066672 : return colN;
259 : 2033336 : }
260 : :
261 : : /*
262 : : * Extract stored datum (and possible null category) from GIN tuple
263 : : */
264 : : Datum
265 : 1311627 : gintuple_get_key(GinState *ginstate, IndexTuple tuple,
266 : : GinNullCategory *category)
267 : : {
268 : 1311627 : Datum res;
269 : 1311627 : bool isnull;
270 : :
271 [ + + ]: 1311627 : if (ginstate->oneCol)
272 : : {
273 : : /*
274 : : * Single column index doesn't store attribute numbers in tuples
275 : : */
276 : 590171 : res = index_getattr(tuple, FirstOffsetNumber, ginstate->origTupdesc,
277 : : &isnull);
278 : 590171 : }
279 : : else
280 : : {
281 : : /*
282 : : * Since the datum type depends on which index column it's from, we
283 : : * must be careful to use the right tuple descriptor here.
284 : : */
285 : 721456 : OffsetNumber colN = gintuple_get_attrnum(ginstate, tuple);
286 : :
287 : 1442912 : res = index_getattr(tuple, OffsetNumberNext(FirstOffsetNumber),
288 : 721456 : ginstate->tupdesc[colN - 1],
289 : : &isnull);
290 : 721456 : }
291 : :
292 [ + + ]: 1311627 : if (isnull)
293 : 295 : *category = GinGetNullCategory(tuple, ginstate);
294 : : else
295 : 1311332 : *category = GIN_CAT_NORM_KEY;
296 : :
297 : 2623254 : return res;
298 : 1311627 : }
299 : :
300 : : /*
301 : : * Allocate a new page (either by recycling, or by extending the index file)
302 : : * The returned buffer is already pinned and exclusive-locked
303 : : * Caller is responsible for initializing the page by calling GinInitBuffer
304 : : */
305 : : Buffer
306 : 1071 : GinNewBuffer(Relation index)
307 : : {
308 : 1071 : Buffer buffer;
309 : :
310 : : /* First, try to get a page from FSM */
311 : 1071 : for (;;)
312 : : {
313 : 1071 : BlockNumber blkno = GetFreeIndexPage(index);
314 : :
315 [ + + ]: 1071 : if (blkno == InvalidBlockNumber)
316 : 1054 : break;
317 : :
318 : 17 : buffer = ReadBuffer(index, blkno);
319 : :
320 : : /*
321 : : * We have to guard against the possibility that someone else already
322 : : * recycled this page; the buffer may be locked if so.
323 : : */
324 [ - + ]: 17 : if (ConditionalLockBuffer(buffer))
325 : : {
326 [ + - ]: 17 : if (GinPageIsRecyclable(BufferGetPage(buffer)))
327 : 17 : return buffer; /* OK to use */
328 : :
329 : 0 : LockBuffer(buffer, GIN_UNLOCK);
330 : 0 : }
331 : :
332 : : /* Can't use it, so release buffer and try again */
333 : 0 : ReleaseBuffer(buffer);
334 [ + - + ]: 1071 : }
335 : :
336 : : /* Must extend the file */
337 : 1054 : buffer = ExtendBufferedRel(BMR_REL(index), MAIN_FORKNUM, NULL,
338 : : EB_LOCK_FIRST);
339 : :
340 : 1054 : return buffer;
341 : 1071 : }
342 : :
343 : : void
344 : 1615 : GinInitPage(Page page, uint32 f, Size pageSize)
345 : : {
346 : 1615 : GinPageOpaque opaque;
347 : :
348 : 1615 : PageInit(page, pageSize, sizeof(GinPageOpaqueData));
349 : :
350 : 1615 : opaque = GinPageGetOpaque(page);
351 : 1615 : opaque->flags = f;
352 : 1615 : opaque->rightlink = InvalidBlockNumber;
353 : 1615 : }
354 : :
355 : : void
356 : 494 : GinInitBuffer(Buffer b, uint32 f)
357 : : {
358 : 494 : GinInitPage(BufferGetPage(b), f, BufferGetPageSize(b));
359 : 494 : }
360 : :
361 : : void
362 : 18 : GinInitMetabuffer(Buffer b)
363 : : {
364 : 18 : GinMetaPageData *metadata;
365 : 18 : Page page = BufferGetPage(b);
366 : :
367 : 18 : GinInitPage(page, GIN_META, BufferGetPageSize(b));
368 : :
369 : 18 : metadata = GinPageGetMeta(page);
370 : :
371 : 18 : metadata->head = metadata->tail = InvalidBlockNumber;
372 : 18 : metadata->tailFreeSize = 0;
373 : 18 : metadata->nPendingPages = 0;
374 : 18 : metadata->nPendingHeapTuples = 0;
375 : 18 : metadata->nTotalPages = 0;
376 : 18 : metadata->nEntryPages = 0;
377 : 18 : metadata->nDataPages = 0;
378 : 18 : metadata->nEntries = 0;
379 : 18 : metadata->ginVersion = GIN_CURRENT_VERSION;
380 : :
381 : : /*
382 : : * Set pd_lower just past the end of the metadata. This is essential,
383 : : * because without doing so, metadata will be lost if xlog.c compresses
384 : : * the page.
385 : : */
386 : 18 : ((PageHeader) page)->pd_lower =
387 : 18 : ((char *) metadata + sizeof(GinMetaPageData)) - (char *) page;
388 : 18 : }
389 : :
390 : : /*
391 : : * Support for sorting key datums in ginExtractEntries
392 : : *
393 : : * Note: we only have to worry about null and not-null keys here;
394 : : * ginExtractEntries never generates more than one placeholder null,
395 : : * so it doesn't have to sort those.
396 : : */
397 : : typedef struct
398 : : {
399 : : Datum datum;
400 : : bool isnull;
401 : : } keyEntryData;
402 : :
403 : : typedef struct
404 : : {
405 : : FmgrInfo *cmpDatumFunc;
406 : : Oid collation;
407 : : bool haveDups;
408 : : } cmpEntriesArg;
409 : :
410 : : static int
411 : 273829 : cmpEntries(const void *a, const void *b, void *arg)
412 : : {
413 : 273829 : const keyEntryData *aa = (const keyEntryData *) a;
414 : 273829 : const keyEntryData *bb = (const keyEntryData *) b;
415 : 273829 : cmpEntriesArg *data = (cmpEntriesArg *) arg;
416 : 273829 : int res;
417 : :
418 [ - + ]: 273829 : if (aa->isnull)
419 : : {
420 [ # # ]: 0 : if (bb->isnull)
421 : 0 : res = 0; /* NULL "=" NULL */
422 : : else
423 : 0 : res = 1; /* NULL ">" not-NULL */
424 : 0 : }
425 [ - + ]: 273829 : else if (bb->isnull)
426 : 0 : res = -1; /* not-NULL "<" NULL */
427 : : else
428 : 547658 : res = DatumGetInt32(FunctionCall2Coll(data->cmpDatumFunc,
429 : 273829 : data->collation,
430 : 273829 : aa->datum, bb->datum));
431 : :
432 : : /*
433 : : * Detect if we have any duplicates. If there are equal keys, qsort must
434 : : * compare them at some point, else it wouldn't know whether one should go
435 : : * before or after the other.
436 : : */
437 [ + + ]: 273829 : if (res == 0)
438 : 4733 : data->haveDups = true;
439 : :
440 : 547658 : return res;
441 : 273829 : }
442 : :
443 : :
444 : : /*
445 : : * Extract the index key values from an indexable item
446 : : *
447 : : * The resulting key values are sorted, and any duplicates are removed.
448 : : * This avoids generating redundant index entries.
449 : : */
450 : : Datum *
451 : 81027 : ginExtractEntries(GinState *ginstate, OffsetNumber attnum,
452 : : Datum value, bool isNull,
453 : : int32 *nentries, GinNullCategory **categories)
454 : : {
455 : 81027 : Datum *entries;
456 : 81027 : bool *nullFlags;
457 : 81027 : int32 i;
458 : :
459 : : /*
460 : : * We don't call the extractValueFn on a null item. Instead generate a
461 : : * placeholder.
462 : : */
463 [ + + ]: 81027 : if (isNull)
464 : : {
465 : 1022 : *nentries = 1;
466 : 1022 : entries = palloc_object(Datum);
467 : 1022 : entries[0] = (Datum) 0;
468 : 1022 : *categories = palloc_object(GinNullCategory);
469 : 1022 : (*categories)[0] = GIN_CAT_NULL_ITEM;
470 : 1022 : return entries;
471 : : }
472 : :
473 : : /* OK, call the opclass's extractValueFn */
474 : 80005 : nullFlags = NULL; /* in case extractValue doesn't set it */
475 : 80005 : entries = (Datum *)
476 : 160010 : DatumGetPointer(FunctionCall3Coll(&ginstate->extractValueFn[attnum - 1],
477 : 80005 : ginstate->supportCollation[attnum - 1],
478 : 80005 : value,
479 : 80005 : PointerGetDatum(nentries),
480 : 80005 : PointerGetDatum(&nullFlags)));
481 : :
482 : : /*
483 : : * Generate a placeholder if the item contained no keys.
484 : : */
485 [ + + + + ]: 80005 : if (entries == NULL || *nentries <= 0)
486 : : {
487 : 254 : *nentries = 1;
488 : 254 : entries = palloc_object(Datum);
489 : 254 : entries[0] = (Datum) 0;
490 : 254 : *categories = palloc_object(GinNullCategory);
491 : 254 : (*categories)[0] = GIN_CAT_EMPTY_ITEM;
492 : 254 : return entries;
493 : : }
494 : :
495 : : /*
496 : : * If the extractValueFn didn't create a nullFlags array, create one,
497 : : * assuming that everything's non-null.
498 : : */
499 [ + + ]: 79751 : if (nullFlags == NULL)
500 : 2303 : nullFlags = (bool *) palloc0(*nentries * sizeof(bool));
501 : :
502 : : /*
503 : : * If there's more than one key, sort and unique-ify.
504 : : *
505 : : * XXX Using qsort here is notationally painful, and the overhead is
506 : : * pretty bad too. For small numbers of keys it'd likely be better to use
507 : : * a simple insertion sort.
508 : : */
509 [ + + ]: 79751 : if (*nentries > 1)
510 : : {
511 : 79691 : keyEntryData *keydata;
512 : 79691 : cmpEntriesArg arg;
513 : :
514 : 79691 : keydata = palloc_array(keyEntryData, *nentries);
515 [ + + ]: 356377 : for (i = 0; i < *nentries; i++)
516 : : {
517 : 276686 : keydata[i].datum = entries[i];
518 : 276686 : keydata[i].isnull = nullFlags[i];
519 : 276686 : }
520 : :
521 : 79691 : arg.cmpDatumFunc = &ginstate->compareFn[attnum - 1];
522 : 79691 : arg.collation = ginstate->supportCollation[attnum - 1];
523 : 79691 : arg.haveDups = false;
524 : 79691 : qsort_arg(keydata, *nentries, sizeof(keyEntryData),
525 : : cmpEntries, &arg);
526 : :
527 [ + + ]: 79691 : if (arg.haveDups)
528 : : {
529 : : /* there are duplicates, must get rid of 'em */
530 : 2307 : int32 j;
531 : :
532 : 2307 : entries[0] = keydata[0].datum;
533 : 2307 : nullFlags[0] = keydata[0].isnull;
534 : 2307 : j = 1;
535 [ + + ]: 9652 : for (i = 1; i < *nentries; i++)
536 : : {
537 [ + + ]: 7345 : if (cmpEntries(&keydata[i - 1], &keydata[i], &arg) != 0)
538 : : {
539 : 4991 : entries[j] = keydata[i].datum;
540 : 4991 : nullFlags[j] = keydata[i].isnull;
541 : 4991 : j++;
542 : 4991 : }
543 : 7345 : }
544 : 2307 : *nentries = j;
545 : 2307 : }
546 : : else
547 : : {
548 : : /* easy, no duplicates */
549 [ + + ]: 344418 : for (i = 0; i < *nentries; i++)
550 : : {
551 : 267034 : entries[i] = keydata[i].datum;
552 : 267034 : nullFlags[i] = keydata[i].isnull;
553 : 267034 : }
554 : : }
555 : :
556 : 79691 : pfree(keydata);
557 : 79691 : }
558 : :
559 : : /*
560 : : * Create GinNullCategory representation from nullFlags.
561 : : */
562 : 79751 : *categories = (GinNullCategory *) palloc0(*nentries * sizeof(GinNullCategory));
563 [ + + ]: 354143 : for (i = 0; i < *nentries; i++)
564 : 274392 : (*categories)[i] = (nullFlags[i] ? GIN_CAT_NULL_KEY : GIN_CAT_NORM_KEY);
565 : :
566 : 79751 : return entries;
567 : 81027 : }
568 : :
569 : : bytea *
570 : 20 : ginoptions(Datum reloptions, bool validate)
571 : : {
572 : : static const relopt_parse_elt tab[] = {
573 : : {"fastupdate", RELOPT_TYPE_BOOL, offsetof(GinOptions, useFastUpdate)},
574 : : {"gin_pending_list_limit", RELOPT_TYPE_INT, offsetof(GinOptions,
575 : : pendingListCleanupSize)}
576 : : };
577 : :
578 : 20 : return (bytea *) build_reloptions(reloptions, validate,
579 : : RELOPT_KIND_GIN,
580 : : sizeof(GinOptions),
581 : : tab, lengthof(tab));
582 : : }
583 : :
584 : : /*
585 : : * Fetch index's statistical data into *stats
586 : : *
587 : : * Note: in the result, nPendingPages can be trusted to be up-to-date,
588 : : * as can ginVersion; but the other fields are as of the last VACUUM.
589 : : */
590 : : void
591 : 257 : ginGetStats(Relation index, GinStatsData *stats)
592 : : {
593 : 257 : Buffer metabuffer;
594 : 257 : Page metapage;
595 : 257 : GinMetaPageData *metadata;
596 : :
597 : 257 : metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
598 : 257 : LockBuffer(metabuffer, GIN_SHARE);
599 : 257 : metapage = BufferGetPage(metabuffer);
600 : 257 : metadata = GinPageGetMeta(metapage);
601 : :
602 : 257 : stats->nPendingPages = metadata->nPendingPages;
603 : 257 : stats->nTotalPages = metadata->nTotalPages;
604 : 257 : stats->nEntryPages = metadata->nEntryPages;
605 : 257 : stats->nDataPages = metadata->nDataPages;
606 : 257 : stats->nEntries = metadata->nEntries;
607 : 257 : stats->ginVersion = metadata->ginVersion;
608 : :
609 : 257 : UnlockReleaseBuffer(metabuffer);
610 : 257 : }
611 : :
612 : : /*
613 : : * Write the given statistics to the index's metapage
614 : : *
615 : : * Note: nPendingPages and ginVersion are *not* copied over
616 : : */
617 : : void
618 : 30 : ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build)
619 : : {
620 : 30 : Buffer metabuffer;
621 : 30 : Page metapage;
622 : 30 : GinMetaPageData *metadata;
623 : :
624 : 30 : metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
625 : 30 : LockBuffer(metabuffer, GIN_EXCLUSIVE);
626 : 30 : metapage = BufferGetPage(metabuffer);
627 : 30 : metadata = GinPageGetMeta(metapage);
628 : :
629 : 30 : START_CRIT_SECTION();
630 : :
631 : 30 : metadata->nTotalPages = stats->nTotalPages;
632 : 30 : metadata->nEntryPages = stats->nEntryPages;
633 : 30 : metadata->nDataPages = stats->nDataPages;
634 : 30 : metadata->nEntries = stats->nEntries;
635 : :
636 : : /*
637 : : * Set pd_lower just past the end of the metadata. This is essential,
638 : : * because without doing so, metadata will be lost if xlog.c compresses
639 : : * the page. (We must do this here because pre-v11 versions of PG did not
640 : : * set the metapage's pd_lower correctly, so a pg_upgraded index might
641 : : * contain the wrong value.)
642 : : */
643 : 30 : ((PageHeader) metapage)->pd_lower =
644 : 30 : ((char *) metadata + sizeof(GinMetaPageData)) - (char *) metapage;
645 : :
646 : 30 : MarkBufferDirty(metabuffer);
647 : :
648 [ + + + - : 30 : if (RelationNeedsWAL(index) && !is_build)
+ + + + ]
649 : : {
650 : 8 : XLogRecPtr recptr;
651 : 8 : ginxlogUpdateMeta data;
652 : :
653 : 8 : data.locator = index->rd_locator;
654 : 8 : data.ntuples = 0;
655 : 8 : data.newRightlink = data.prevTail = InvalidBlockNumber;
656 : 8 : memcpy(&data.metadata, metadata, sizeof(GinMetaPageData));
657 : :
658 : 8 : XLogBeginInsert();
659 : 8 : XLogRegisterData(&data, sizeof(ginxlogUpdateMeta));
660 : 8 : XLogRegisterBuffer(0, metabuffer, REGBUF_WILL_INIT | REGBUF_STANDARD);
661 : :
662 : 8 : recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_UPDATE_META_PAGE);
663 : 8 : PageSetLSN(metapage, recptr);
664 : 8 : }
665 : :
666 : 46 : UnlockReleaseBuffer(metabuffer);
667 : :
668 [ + - ]: 46 : END_CRIT_SECTION();
669 : 26 : }
670 : :
671 : : /*
672 : : * ginbuildphasename() -- Return name of index build phase.
673 : : */
674 : : char *
675 : 0 : ginbuildphasename(int64 phasenum)
676 : : {
677 [ # # # # : 0 : switch (phasenum)
# # # ]
678 : : {
679 : : case PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE:
680 : 0 : return "initializing";
681 : : case PROGRESS_GIN_PHASE_INDEXBUILD_TABLESCAN:
682 : 0 : return "scanning table";
683 : : case PROGRESS_GIN_PHASE_PERFORMSORT_1:
684 : 0 : return "sorting tuples (workers)";
685 : : case PROGRESS_GIN_PHASE_MERGE_1:
686 : 0 : return "merging tuples (workers)";
687 : : case PROGRESS_GIN_PHASE_PERFORMSORT_2:
688 : 0 : return "sorting tuples";
689 : : case PROGRESS_GIN_PHASE_MERGE_2:
690 : 0 : return "merging tuples";
691 : : default:
692 : 0 : return NULL;
693 : : }
694 : 0 : }
|