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