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