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