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-2025, 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 3906 : 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 3906 : 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 5434 : initGinState(GinState *state, Relation index)
104 : {
105 5434 : TupleDesc origTupdesc = RelationGetDescr(index);
106 : int i;
107 :
108 5434 : MemSet(state, 0, sizeof(GinState));
109 :
110 5434 : state->index = index;
111 5434 : state->oneCol = (origTupdesc->natts == 1);
112 5434 : state->origTupdesc = origTupdesc;
113 :
114 11156 : for (i = 0; i < origTupdesc->natts; i++)
115 : {
116 5722 : Form_pg_attribute attr = TupleDescAttr(origTupdesc, i);
117 :
118 5722 : if (state->oneCol)
119 5146 : state->tupdesc[i] = state->origTupdesc;
120 : else
121 : {
122 576 : state->tupdesc[i] = CreateTemplateTupleDesc(2);
123 :
124 576 : TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 1, NULL,
125 : INT2OID, -1, 0);
126 576 : TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 2, NULL,
127 : attr->atttypid,
128 : attr->atttypmod,
129 576 : attr->attndims);
130 576 : TupleDescInitEntryCollation(state->tupdesc[i], (AttrNumber) 2,
131 : 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 5722 : if (index_getprocid(index, i + 1, GIN_COMPARE_PROC) != InvalidOid)
139 : {
140 2688 : fmgr_info_copy(&(state->compareFn[i]),
141 2688 : index_getprocinfo(index, i + 1, GIN_COMPARE_PROC),
142 : CurrentMemoryContext);
143 : }
144 : else
145 : {
146 : TypeCacheEntry *typentry;
147 :
148 3034 : typentry = lookup_type_cache(attr->atttypid,
149 : TYPECACHE_CMP_PROC_FINFO);
150 3034 : 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 3034 : fmgr_info_copy(&(state->compareFn[i]),
156 : &(typentry->cmp_proc_finfo),
157 : CurrentMemoryContext);
158 : }
159 :
160 : /* Opclass must always provide extract procs */
161 5722 : fmgr_info_copy(&(state->extractValueFn[i]),
162 5722 : index_getprocinfo(index, i + 1, GIN_EXTRACTVALUE_PROC),
163 : CurrentMemoryContext);
164 5722 : fmgr_info_copy(&(state->extractQueryFn[i]),
165 5722 : index_getprocinfo(index, i + 1, GIN_EXTRACTQUERY_PROC),
166 : CurrentMemoryContext);
167 :
168 : /*
169 : * Check opclass capability to do tri-state or binary logic consistent
170 : * check.
171 : */
172 5722 : if (index_getprocid(index, i + 1, GIN_TRICONSISTENT_PROC) != InvalidOid)
173 : {
174 4948 : fmgr_info_copy(&(state->triConsistentFn[i]),
175 4948 : index_getprocinfo(index, i + 1, GIN_TRICONSISTENT_PROC),
176 : CurrentMemoryContext);
177 : }
178 :
179 5722 : if (index_getprocid(index, i + 1, GIN_CONSISTENT_PROC) != InvalidOid)
180 : {
181 5722 : fmgr_info_copy(&(state->consistentFn[i]),
182 5722 : index_getprocinfo(index, i + 1, GIN_CONSISTENT_PROC),
183 : CurrentMemoryContext);
184 : }
185 :
186 5722 : 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 : }
193 :
194 : /*
195 : * Check opclass capability to do partial match.
196 : */
197 5722 : if (index_getprocid(index, i + 1, GIN_COMPARE_PARTIAL_PROC) != InvalidOid)
198 : {
199 972 : fmgr_info_copy(&(state->comparePartialFn[i]),
200 972 : index_getprocinfo(index, i + 1, GIN_COMPARE_PARTIAL_PROC),
201 : CurrentMemoryContext);
202 972 : state->canPartialMatch[i] = true;
203 : }
204 : else
205 : {
206 4750 : 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 5722 : if (OidIsValid(index->rd_indcollation[i]))
222 404 : state->supportCollation[i] = index->rd_indcollation[i];
223 : else
224 5318 : state->supportCollation[i] = DEFAULT_COLLATION_OID;
225 : }
226 5434 : }
227 :
228 : /*
229 : * Extract attribute (column) number of stored entry from GIN tuple
230 : */
231 : OffsetNumber
232 17225304 : gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple)
233 : {
234 : OffsetNumber colN;
235 :
236 17225304 : if (ginstate->oneCol)
237 : {
238 : /* column number is not stored explicitly */
239 8566294 : colN = FirstOffsetNumber;
240 : }
241 : else
242 : {
243 : Datum res;
244 : 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 8659010 : res = index_getattr(tuple, FirstOffsetNumber, ginstate->tupdesc[0],
251 : &isnull);
252 : Assert(!isnull);
253 :
254 8659010 : colN = DatumGetUInt16(res);
255 : Assert(colN >= FirstOffsetNumber && colN <= ginstate->origTupdesc->natts);
256 : }
257 :
258 17225304 : return colN;
259 : }
260 :
261 : /*
262 : * Extract stored datum (and possible null category) from GIN tuple
263 : */
264 : Datum
265 12897870 : gintuple_get_key(GinState *ginstate, IndexTuple tuple,
266 : GinNullCategory *category)
267 : {
268 : Datum res;
269 : bool isnull;
270 :
271 12897870 : if (ginstate->oneCol)
272 : {
273 : /*
274 : * Single column index doesn't store attribute numbers in tuples
275 : */
276 8569088 : res = index_getattr(tuple, FirstOffsetNumber, ginstate->origTupdesc,
277 : &isnull);
278 : }
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 4328782 : OffsetNumber colN = gintuple_get_attrnum(ginstate, tuple);
286 :
287 4328782 : res = index_getattr(tuple, OffsetNumberNext(FirstOffsetNumber),
288 4328782 : ginstate->tupdesc[colN - 1],
289 : &isnull);
290 : }
291 :
292 12897870 : if (isnull)
293 1794 : *category = GinGetNullCategory(tuple, ginstate);
294 : else
295 12896076 : *category = GIN_CAT_NORM_KEY;
296 :
297 12897870 : return res;
298 : }
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 9266 : GinNewBuffer(Relation index)
307 : {
308 : Buffer buffer;
309 :
310 : /* First, try to get a page from FSM */
311 : for (;;)
312 0 : {
313 9266 : BlockNumber blkno = GetFreeIndexPage(index);
314 :
315 9266 : if (blkno == InvalidBlockNumber)
316 9160 : break;
317 :
318 106 : 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 106 : if (ConditionalLockBuffer(buffer))
325 : {
326 106 : if (GinPageIsRecyclable(BufferGetPage(buffer)))
327 106 : return buffer; /* OK to use */
328 :
329 0 : LockBuffer(buffer, GIN_UNLOCK);
330 : }
331 :
332 : /* Can't use it, so release buffer and try again */
333 0 : ReleaseBuffer(buffer);
334 : }
335 :
336 : /* Must extend the file */
337 9160 : buffer = ExtendBufferedRel(BMR_REL(index), MAIN_FORKNUM, NULL,
338 : EB_LOCK_FIRST);
339 :
340 9160 : return buffer;
341 : }
342 :
343 : void
344 63074 : GinInitPage(Page page, uint32 f, Size pageSize)
345 : {
346 : GinPageOpaque opaque;
347 :
348 63074 : PageInit(page, pageSize, sizeof(GinPageOpaqueData));
349 :
350 63074 : opaque = GinPageGetOpaque(page);
351 63074 : opaque->flags = f;
352 63074 : opaque->rightlink = InvalidBlockNumber;
353 63074 : }
354 :
355 : void
356 4062 : GinInitBuffer(Buffer b, uint32 f)
357 : {
358 4062 : GinInitPage(BufferGetPage(b), f, BufferGetPageSize(b));
359 4062 : }
360 :
361 : void
362 48316 : GinInitMetabuffer(Buffer b)
363 : {
364 : GinMetaPageData *metadata;
365 48316 : Page page = BufferGetPage(b);
366 :
367 48316 : GinInitPage(page, GIN_META, BufferGetPageSize(b));
368 :
369 48316 : metadata = GinPageGetMeta(page);
370 :
371 48316 : metadata->head = metadata->tail = InvalidBlockNumber;
372 48316 : metadata->tailFreeSize = 0;
373 48316 : metadata->nPendingPages = 0;
374 48316 : metadata->nPendingHeapTuples = 0;
375 48316 : metadata->nTotalPages = 0;
376 48316 : metadata->nEntryPages = 0;
377 48316 : metadata->nDataPages = 0;
378 48316 : metadata->nEntries = 0;
379 48316 : 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 48316 : ((PageHeader) page)->pd_lower =
387 48316 : ((char *) metadata + sizeof(GinMetaPageData)) - (char *) page;
388 48316 : }
389 :
390 : /*
391 : * Compare two keys of the same index column
392 : */
393 : int
394 39316092 : ginCompareEntries(GinState *ginstate, OffsetNumber attnum,
395 : Datum a, GinNullCategory categorya,
396 : Datum b, GinNullCategory categoryb)
397 : {
398 : /* if not of same null category, sort by that first */
399 39316092 : if (categorya != categoryb)
400 623816 : return (categorya < categoryb) ? -1 : 1;
401 :
402 : /* all null items in same category are equal */
403 38692276 : if (categorya != GIN_CAT_NORM_KEY)
404 8296 : return 0;
405 :
406 : /* both not null, so safe to call the compareFn */
407 38683980 : return DatumGetInt32(FunctionCall2Coll(&ginstate->compareFn[attnum - 1],
408 38683980 : ginstate->supportCollation[attnum - 1],
409 : a, b));
410 : }
411 :
412 : /*
413 : * Compare two keys of possibly different index columns
414 : */
415 : int
416 39799708 : ginCompareAttEntries(GinState *ginstate,
417 : OffsetNumber attnuma, Datum a, GinNullCategory categorya,
418 : OffsetNumber attnumb, Datum b, GinNullCategory categoryb)
419 : {
420 : /* attribute number is the first sort key */
421 39799708 : if (attnuma != attnumb)
422 537806 : return (attnuma < attnumb) ? -1 : 1;
423 :
424 39261902 : return ginCompareEntries(ginstate, attnuma, a, categorya, b, categoryb);
425 : }
426 :
427 :
428 : /*
429 : * Support for sorting key datums in ginExtractEntries
430 : *
431 : * Note: we only have to worry about null and not-null keys here;
432 : * ginExtractEntries never generates more than one placeholder null,
433 : * so it doesn't have to sort those.
434 : */
435 : typedef struct
436 : {
437 : Datum datum;
438 : bool isnull;
439 : } keyEntryData;
440 :
441 : typedef struct
442 : {
443 : FmgrInfo *cmpDatumFunc;
444 : Oid collation;
445 : bool haveDups;
446 : } cmpEntriesArg;
447 :
448 : static int
449 4234936 : cmpEntries(const void *a, const void *b, void *arg)
450 : {
451 4234936 : const keyEntryData *aa = (const keyEntryData *) a;
452 4234936 : const keyEntryData *bb = (const keyEntryData *) b;
453 4234936 : cmpEntriesArg *data = (cmpEntriesArg *) arg;
454 : int res;
455 :
456 4234936 : if (aa->isnull)
457 : {
458 0 : if (bb->isnull)
459 0 : res = 0; /* NULL "=" NULL */
460 : else
461 0 : res = 1; /* NULL ">" not-NULL */
462 : }
463 4234936 : else if (bb->isnull)
464 0 : res = -1; /* not-NULL "<" NULL */
465 : else
466 4234936 : res = DatumGetInt32(FunctionCall2Coll(data->cmpDatumFunc,
467 : data->collation,
468 4234936 : aa->datum, bb->datum));
469 :
470 : /*
471 : * Detect if we have any duplicates. If there are equal keys, qsort must
472 : * compare them at some point, else it wouldn't know whether one should go
473 : * before or after the other.
474 : */
475 4234936 : if (res == 0)
476 83546 : data->haveDups = true;
477 :
478 4234936 : return res;
479 : }
480 :
481 :
482 : /*
483 : * Extract the index key values from an indexable item
484 : *
485 : * The resulting key values are sorted, and any duplicates are removed.
486 : * This avoids generating redundant index entries.
487 : */
488 : Datum *
489 1384522 : ginExtractEntries(GinState *ginstate, OffsetNumber attnum,
490 : Datum value, bool isNull,
491 : int32 *nentries, GinNullCategory **categories)
492 : {
493 : Datum *entries;
494 : bool *nullFlags;
495 : int32 i;
496 :
497 : /*
498 : * We don't call the extractValueFn on a null item. Instead generate a
499 : * placeholder.
500 : */
501 1384522 : if (isNull)
502 : {
503 6622 : *nentries = 1;
504 6622 : entries = palloc_object(Datum);
505 6622 : entries[0] = (Datum) 0;
506 6622 : *categories = palloc_object(GinNullCategory);
507 6622 : (*categories)[0] = GIN_CAT_NULL_ITEM;
508 6622 : return entries;
509 : }
510 :
511 : /* OK, call the opclass's extractValueFn */
512 1377900 : nullFlags = NULL; /* in case extractValue doesn't set it */
513 : entries = (Datum *)
514 2755800 : DatumGetPointer(FunctionCall3Coll(&ginstate->extractValueFn[attnum - 1],
515 1377900 : ginstate->supportCollation[attnum - 1],
516 : value,
517 : PointerGetDatum(nentries),
518 : PointerGetDatum(&nullFlags)));
519 :
520 : /*
521 : * Generate a placeholder if the item contained no keys.
522 : */
523 1377900 : if (entries == NULL || *nentries <= 0)
524 : {
525 1776 : *nentries = 1;
526 1776 : entries = palloc_object(Datum);
527 1776 : entries[0] = (Datum) 0;
528 1776 : *categories = palloc_object(GinNullCategory);
529 1776 : (*categories)[0] = GIN_CAT_EMPTY_ITEM;
530 1776 : return entries;
531 : }
532 :
533 : /*
534 : * If the extractValueFn didn't create a nullFlags array, create one,
535 : * assuming that everything's non-null.
536 : */
537 1376124 : if (nullFlags == NULL)
538 237548 : nullFlags = (bool *) palloc0(*nentries * sizeof(bool));
539 :
540 : /*
541 : * If there's more than one key, sort and unique-ify.
542 : *
543 : * XXX Using qsort here is notationally painful, and the overhead is
544 : * pretty bad too. For small numbers of keys it'd likely be better to use
545 : * a simple insertion sort.
546 : */
547 1376124 : if (*nentries > 1)
548 : {
549 : keyEntryData *keydata;
550 : cmpEntriesArg arg;
551 :
552 595388 : keydata = palloc_array(keyEntryData, *nentries);
553 3325504 : for (i = 0; i < *nentries; i++)
554 : {
555 2730116 : keydata[i].datum = entries[i];
556 2730116 : keydata[i].isnull = nullFlags[i];
557 : }
558 :
559 595388 : arg.cmpDatumFunc = &ginstate->compareFn[attnum - 1];
560 595388 : arg.collation = ginstate->supportCollation[attnum - 1];
561 595388 : arg.haveDups = false;
562 595388 : qsort_arg(keydata, *nentries, sizeof(keyEntryData),
563 : cmpEntries, &arg);
564 :
565 595388 : if (arg.haveDups)
566 : {
567 : /* there are duplicates, must get rid of 'em */
568 : int32 j;
569 :
570 30722 : entries[0] = keydata[0].datum;
571 30722 : nullFlags[0] = keydata[0].isnull;
572 30722 : j = 1;
573 236722 : for (i = 1; i < *nentries; i++)
574 : {
575 206000 : if (cmpEntries(&keydata[i - 1], &keydata[i], &arg) != 0)
576 : {
577 165230 : entries[j] = keydata[i].datum;
578 165230 : nullFlags[j] = keydata[i].isnull;
579 165230 : j++;
580 : }
581 : }
582 30722 : *nentries = j;
583 : }
584 : else
585 : {
586 : /* easy, no duplicates */
587 3058060 : for (i = 0; i < *nentries; i++)
588 : {
589 2493394 : entries[i] = keydata[i].datum;
590 2493394 : nullFlags[i] = keydata[i].isnull;
591 : }
592 : }
593 :
594 595388 : pfree(keydata);
595 : }
596 :
597 : /*
598 : * Create GinNullCategory representation from nullFlags.
599 : */
600 1376124 : *categories = (GinNullCategory *) palloc0(*nentries * sizeof(GinNullCategory));
601 4846206 : for (i = 0; i < *nentries; i++)
602 3470082 : (*categories)[i] = (nullFlags[i] ? GIN_CAT_NULL_KEY : GIN_CAT_NORM_KEY);
603 :
604 1376124 : return entries;
605 : }
606 :
607 : bytea *
608 614 : ginoptions(Datum reloptions, bool validate)
609 : {
610 : static const relopt_parse_elt tab[] = {
611 : {"fastupdate", RELOPT_TYPE_BOOL, offsetof(GinOptions, useFastUpdate)},
612 : {"gin_pending_list_limit", RELOPT_TYPE_INT, offsetof(GinOptions,
613 : pendingListCleanupSize)}
614 : };
615 :
616 614 : return (bytea *) build_reloptions(reloptions, validate,
617 : RELOPT_KIND_GIN,
618 : sizeof(GinOptions),
619 : tab, lengthof(tab));
620 : }
621 :
622 : /*
623 : * Fetch index's statistical data into *stats
624 : *
625 : * Note: in the result, nPendingPages can be trusted to be up-to-date,
626 : * as can ginVersion; but the other fields are as of the last VACUUM.
627 : */
628 : void
629 2592 : ginGetStats(Relation index, GinStatsData *stats)
630 : {
631 : Buffer metabuffer;
632 : Page metapage;
633 : GinMetaPageData *metadata;
634 :
635 2592 : metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
636 2592 : LockBuffer(metabuffer, GIN_SHARE);
637 2592 : metapage = BufferGetPage(metabuffer);
638 2592 : metadata = GinPageGetMeta(metapage);
639 :
640 2592 : stats->nPendingPages = metadata->nPendingPages;
641 2592 : stats->nTotalPages = metadata->nTotalPages;
642 2592 : stats->nEntryPages = metadata->nEntryPages;
643 2592 : stats->nDataPages = metadata->nDataPages;
644 2592 : stats->nEntries = metadata->nEntries;
645 2592 : stats->ginVersion = metadata->ginVersion;
646 :
647 2592 : UnlockReleaseBuffer(metabuffer);
648 2592 : }
649 :
650 : /*
651 : * Write the given statistics to the index's metapage
652 : *
653 : * Note: nPendingPages and ginVersion are *not* copied over
654 : */
655 : void
656 454 : ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build)
657 : {
658 : Buffer metabuffer;
659 : Page metapage;
660 : GinMetaPageData *metadata;
661 :
662 454 : metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
663 454 : LockBuffer(metabuffer, GIN_EXCLUSIVE);
664 454 : metapage = BufferGetPage(metabuffer);
665 454 : metadata = GinPageGetMeta(metapage);
666 :
667 454 : START_CRIT_SECTION();
668 :
669 454 : metadata->nTotalPages = stats->nTotalPages;
670 454 : metadata->nEntryPages = stats->nEntryPages;
671 454 : metadata->nDataPages = stats->nDataPages;
672 454 : metadata->nEntries = stats->nEntries;
673 :
674 : /*
675 : * Set pd_lower just past the end of the metadata. This is essential,
676 : * because without doing so, metadata will be lost if xlog.c compresses
677 : * the page. (We must do this here because pre-v11 versions of PG did not
678 : * set the metapage's pd_lower correctly, so a pg_upgraded index might
679 : * contain the wrong value.)
680 : */
681 454 : ((PageHeader) metapage)->pd_lower =
682 454 : ((char *) metadata + sizeof(GinMetaPageData)) - (char *) metapage;
683 :
684 454 : MarkBufferDirty(metabuffer);
685 :
686 454 : if (RelationNeedsWAL(index) && !is_build)
687 : {
688 : XLogRecPtr recptr;
689 : ginxlogUpdateMeta data;
690 :
691 50 : data.locator = index->rd_locator;
692 50 : data.ntuples = 0;
693 50 : data.newRightlink = data.prevTail = InvalidBlockNumber;
694 50 : memcpy(&data.metadata, metadata, sizeof(GinMetaPageData));
695 :
696 50 : XLogBeginInsert();
697 50 : XLogRegisterData(&data, sizeof(ginxlogUpdateMeta));
698 50 : XLogRegisterBuffer(0, metabuffer, REGBUF_WILL_INIT | REGBUF_STANDARD);
699 :
700 50 : recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_UPDATE_META_PAGE);
701 50 : PageSetLSN(metapage, recptr);
702 : }
703 :
704 454 : UnlockReleaseBuffer(metabuffer);
705 :
706 454 : END_CRIT_SECTION();
707 454 : }
708 :
709 : /*
710 : * ginbuildphasename() -- Return name of index build phase.
711 : */
712 : char *
713 0 : ginbuildphasename(int64 phasenum)
714 : {
715 0 : switch (phasenum)
716 : {
717 0 : case PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE:
718 0 : return "initializing";
719 0 : case PROGRESS_GIN_PHASE_INDEXBUILD_TABLESCAN:
720 0 : return "scanning table";
721 0 : case PROGRESS_GIN_PHASE_PERFORMSORT_1:
722 0 : return "sorting tuples (workers)";
723 0 : case PROGRESS_GIN_PHASE_MERGE_1:
724 0 : return "merging tuples (workers)";
725 0 : case PROGRESS_GIN_PHASE_PERFORMSORT_2:
726 0 : return "sorting tuples";
727 0 : case PROGRESS_GIN_PHASE_MERGE_2:
728 0 : return "merging tuples";
729 0 : default:
730 0 : return NULL;
731 : }
732 : }
|