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