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