1 | /*------------------------------------------------------------------------- |
2 | * |
3 | * ginutil.c |
4 | * Utility routines for the Postgres inverted index access method. |
5 | * |
6 | * |
7 | * Portions Copyright (c) 1996-2019, 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 "storage/predicate.h" |
27 | #include "utils/builtins.h" |
28 | #include "utils/index_selfuncs.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 | ginhandler(PG_FUNCTION_ARGS) |
38 | { |
39 | IndexAmRoutine *amroutine = makeNode(IndexAmRoutine); |
40 | |
41 | amroutine->amstrategies = 0; |
42 | amroutine->amsupport = GINNProcs; |
43 | amroutine->amcanorder = false; |
44 | amroutine->amcanorderbyop = false; |
45 | amroutine->amcanbackward = false; |
46 | amroutine->amcanunique = false; |
47 | amroutine->amcanmulticol = true; |
48 | amroutine->amoptionalkey = true; |
49 | amroutine->amsearcharray = false; |
50 | amroutine->amsearchnulls = false; |
51 | amroutine->amstorage = true; |
52 | amroutine->amclusterable = false; |
53 | amroutine->ampredlocks = true; |
54 | amroutine->amcanparallel = false; |
55 | amroutine->amcaninclude = false; |
56 | amroutine->amkeytype = InvalidOid; |
57 | |
58 | amroutine->ambuild = ginbuild; |
59 | amroutine->ambuildempty = ginbuildempty; |
60 | amroutine->aminsert = gininsert; |
61 | amroutine->ambulkdelete = ginbulkdelete; |
62 | amroutine->amvacuumcleanup = ginvacuumcleanup; |
63 | amroutine->amcanreturn = NULL; |
64 | amroutine->amcostestimate = gincostestimate; |
65 | amroutine->amoptions = ginoptions; |
66 | amroutine->amproperty = NULL; |
67 | amroutine->ambuildphasename = NULL; |
68 | amroutine->amvalidate = ginvalidate; |
69 | amroutine->ambeginscan = ginbeginscan; |
70 | amroutine->amrescan = ginrescan; |
71 | amroutine->amgettuple = NULL; |
72 | amroutine->amgetbitmap = gingetbitmap; |
73 | amroutine->amendscan = ginendscan; |
74 | amroutine->ammarkpos = NULL; |
75 | amroutine->amrestrpos = NULL; |
76 | amroutine->amestimateparallelscan = NULL; |
77 | amroutine->aminitparallelscan = NULL; |
78 | amroutine->amparallelrescan = NULL; |
79 | |
80 | PG_RETURN_POINTER(amroutine); |
81 | } |
82 | |
83 | /* |
84 | * initGinState: fill in an empty GinState struct to describe the index |
85 | * |
86 | * Note: assorted subsidiary data is allocated in the CurrentMemoryContext. |
87 | */ |
88 | void |
89 | initGinState(GinState *state, Relation index) |
90 | { |
91 | TupleDesc origTupdesc = RelationGetDescr(index); |
92 | int i; |
93 | |
94 | MemSet(state, 0, sizeof(GinState)); |
95 | |
96 | state->index = index; |
97 | state->oneCol = (origTupdesc->natts == 1) ? true : false; |
98 | state->origTupdesc = origTupdesc; |
99 | |
100 | for (i = 0; i < origTupdesc->natts; i++) |
101 | { |
102 | Form_pg_attribute attr = TupleDescAttr(origTupdesc, i); |
103 | |
104 | if (state->oneCol) |
105 | state->tupdesc[i] = state->origTupdesc; |
106 | else |
107 | { |
108 | state->tupdesc[i] = CreateTemplateTupleDesc(2); |
109 | |
110 | TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 1, NULL, |
111 | INT2OID, -1, 0); |
112 | TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 2, NULL, |
113 | attr->atttypid, |
114 | attr->atttypmod, |
115 | attr->attndims); |
116 | TupleDescInitEntryCollation(state->tupdesc[i], (AttrNumber) 2, |
117 | attr->attcollation); |
118 | } |
119 | |
120 | /* |
121 | * If the compare proc isn't specified in the opclass definition, look |
122 | * up the index key type's default btree comparator. |
123 | */ |
124 | if (index_getprocid(index, i + 1, GIN_COMPARE_PROC) != InvalidOid) |
125 | { |
126 | fmgr_info_copy(&(state->compareFn[i]), |
127 | index_getprocinfo(index, i + 1, GIN_COMPARE_PROC), |
128 | CurrentMemoryContext); |
129 | } |
130 | else |
131 | { |
132 | TypeCacheEntry *typentry; |
133 | |
134 | typentry = lookup_type_cache(attr->atttypid, |
135 | TYPECACHE_CMP_PROC_FINFO); |
136 | if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid)) |
137 | ereport(ERROR, |
138 | (errcode(ERRCODE_UNDEFINED_FUNCTION), |
139 | errmsg("could not identify a comparison function for type %s" , |
140 | format_type_be(attr->atttypid)))); |
141 | fmgr_info_copy(&(state->compareFn[i]), |
142 | &(typentry->cmp_proc_finfo), |
143 | CurrentMemoryContext); |
144 | } |
145 | |
146 | /* Opclass must always provide extract procs */ |
147 | fmgr_info_copy(&(state->extractValueFn[i]), |
148 | index_getprocinfo(index, i + 1, GIN_EXTRACTVALUE_PROC), |
149 | CurrentMemoryContext); |
150 | fmgr_info_copy(&(state->extractQueryFn[i]), |
151 | index_getprocinfo(index, i + 1, GIN_EXTRACTQUERY_PROC), |
152 | CurrentMemoryContext); |
153 | |
154 | /* |
155 | * Check opclass capability to do tri-state or binary logic consistent |
156 | * check. |
157 | */ |
158 | if (index_getprocid(index, i + 1, GIN_TRICONSISTENT_PROC) != InvalidOid) |
159 | { |
160 | fmgr_info_copy(&(state->triConsistentFn[i]), |
161 | index_getprocinfo(index, i + 1, GIN_TRICONSISTENT_PROC), |
162 | CurrentMemoryContext); |
163 | } |
164 | |
165 | if (index_getprocid(index, i + 1, GIN_CONSISTENT_PROC) != InvalidOid) |
166 | { |
167 | fmgr_info_copy(&(state->consistentFn[i]), |
168 | index_getprocinfo(index, i + 1, GIN_CONSISTENT_PROC), |
169 | CurrentMemoryContext); |
170 | } |
171 | |
172 | if (state->consistentFn[i].fn_oid == InvalidOid && |
173 | state->triConsistentFn[i].fn_oid == InvalidOid) |
174 | { |
175 | elog(ERROR, "missing GIN support function (%d or %d) for attribute %d of index \"%s\"" , |
176 | GIN_CONSISTENT_PROC, GIN_TRICONSISTENT_PROC, |
177 | i + 1, RelationGetRelationName(index)); |
178 | } |
179 | |
180 | /* |
181 | * Check opclass capability to do partial match. |
182 | */ |
183 | if (index_getprocid(index, i + 1, GIN_COMPARE_PARTIAL_PROC) != InvalidOid) |
184 | { |
185 | fmgr_info_copy(&(state->comparePartialFn[i]), |
186 | index_getprocinfo(index, i + 1, GIN_COMPARE_PARTIAL_PROC), |
187 | CurrentMemoryContext); |
188 | state->canPartialMatch[i] = true; |
189 | } |
190 | else |
191 | { |
192 | state->canPartialMatch[i] = false; |
193 | } |
194 | |
195 | /* |
196 | * If the index column has a specified collation, we should honor that |
197 | * while doing comparisons. However, we may have a collatable storage |
198 | * type for a noncollatable indexed data type (for instance, hstore |
199 | * uses text index entries). If there's no index collation then |
200 | * specify default collation in case the support functions need |
201 | * collation. This is harmless if the support functions don't care |
202 | * about collation, so we just do it unconditionally. (We could |
203 | * alternatively call get_typcollation, but that seems like expensive |
204 | * overkill --- there aren't going to be any cases where a GIN storage |
205 | * type has a nondefault collation.) |
206 | */ |
207 | if (OidIsValid(index->rd_indcollation[i])) |
208 | state->supportCollation[i] = index->rd_indcollation[i]; |
209 | else |
210 | state->supportCollation[i] = DEFAULT_COLLATION_OID; |
211 | } |
212 | } |
213 | |
214 | /* |
215 | * Extract attribute (column) number of stored entry from GIN tuple |
216 | */ |
217 | OffsetNumber |
218 | gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple) |
219 | { |
220 | OffsetNumber colN; |
221 | |
222 | if (ginstate->oneCol) |
223 | { |
224 | /* column number is not stored explicitly */ |
225 | colN = FirstOffsetNumber; |
226 | } |
227 | else |
228 | { |
229 | Datum res; |
230 | bool isnull; |
231 | |
232 | /* |
233 | * First attribute is always int16, so we can safely use any tuple |
234 | * descriptor to obtain first attribute of tuple |
235 | */ |
236 | res = index_getattr(tuple, FirstOffsetNumber, ginstate->tupdesc[0], |
237 | &isnull); |
238 | Assert(!isnull); |
239 | |
240 | colN = DatumGetUInt16(res); |
241 | Assert(colN >= FirstOffsetNumber && colN <= ginstate->origTupdesc->natts); |
242 | } |
243 | |
244 | return colN; |
245 | } |
246 | |
247 | /* |
248 | * Extract stored datum (and possible null category) from GIN tuple |
249 | */ |
250 | Datum |
251 | gintuple_get_key(GinState *ginstate, IndexTuple tuple, |
252 | GinNullCategory *category) |
253 | { |
254 | Datum res; |
255 | bool isnull; |
256 | |
257 | if (ginstate->oneCol) |
258 | { |
259 | /* |
260 | * Single column index doesn't store attribute numbers in tuples |
261 | */ |
262 | res = index_getattr(tuple, FirstOffsetNumber, ginstate->origTupdesc, |
263 | &isnull); |
264 | } |
265 | else |
266 | { |
267 | /* |
268 | * Since the datum type depends on which index column it's from, we |
269 | * must be careful to use the right tuple descriptor here. |
270 | */ |
271 | OffsetNumber colN = gintuple_get_attrnum(ginstate, tuple); |
272 | |
273 | res = index_getattr(tuple, OffsetNumberNext(FirstOffsetNumber), |
274 | ginstate->tupdesc[colN - 1], |
275 | &isnull); |
276 | } |
277 | |
278 | if (isnull) |
279 | *category = GinGetNullCategory(tuple, ginstate); |
280 | else |
281 | *category = GIN_CAT_NORM_KEY; |
282 | |
283 | return res; |
284 | } |
285 | |
286 | /* |
287 | * Allocate a new page (either by recycling, or by extending the index file) |
288 | * The returned buffer is already pinned and exclusive-locked |
289 | * Caller is responsible for initializing the page by calling GinInitBuffer |
290 | */ |
291 | Buffer |
292 | GinNewBuffer(Relation index) |
293 | { |
294 | Buffer buffer; |
295 | bool needLock; |
296 | |
297 | /* First, try to get a page from FSM */ |
298 | for (;;) |
299 | { |
300 | BlockNumber blkno = GetFreeIndexPage(index); |
301 | |
302 | if (blkno == InvalidBlockNumber) |
303 | break; |
304 | |
305 | buffer = ReadBuffer(index, blkno); |
306 | |
307 | /* |
308 | * We have to guard against the possibility that someone else already |
309 | * recycled this page; the buffer may be locked if so. |
310 | */ |
311 | if (ConditionalLockBuffer(buffer)) |
312 | { |
313 | if (GinPageIsRecyclable(BufferGetPage(buffer))) |
314 | return buffer; /* OK to use */ |
315 | |
316 | LockBuffer(buffer, GIN_UNLOCK); |
317 | } |
318 | |
319 | /* Can't use it, so release buffer and try again */ |
320 | ReleaseBuffer(buffer); |
321 | } |
322 | |
323 | /* Must extend the file */ |
324 | needLock = !RELATION_IS_LOCAL(index); |
325 | if (needLock) |
326 | LockRelationForExtension(index, ExclusiveLock); |
327 | |
328 | buffer = ReadBuffer(index, P_NEW); |
329 | LockBuffer(buffer, GIN_EXCLUSIVE); |
330 | |
331 | if (needLock) |
332 | UnlockRelationForExtension(index, ExclusiveLock); |
333 | |
334 | return buffer; |
335 | } |
336 | |
337 | void |
338 | GinInitPage(Page page, uint32 f, Size pageSize) |
339 | { |
340 | GinPageOpaque opaque; |
341 | |
342 | PageInit(page, pageSize, sizeof(GinPageOpaqueData)); |
343 | |
344 | opaque = GinPageGetOpaque(page); |
345 | memset(opaque, 0, sizeof(GinPageOpaqueData)); |
346 | opaque->flags = f; |
347 | opaque->rightlink = InvalidBlockNumber; |
348 | } |
349 | |
350 | void |
351 | GinInitBuffer(Buffer b, uint32 f) |
352 | { |
353 | GinInitPage(BufferGetPage(b), f, BufferGetPageSize(b)); |
354 | } |
355 | |
356 | void |
357 | GinInitMetabuffer(Buffer b) |
358 | { |
359 | GinMetaPageData *metadata; |
360 | Page page = BufferGetPage(b); |
361 | |
362 | GinInitPage(page, GIN_META, BufferGetPageSize(b)); |
363 | |
364 | metadata = GinPageGetMeta(page); |
365 | |
366 | metadata->head = metadata->tail = InvalidBlockNumber; |
367 | metadata->tailFreeSize = 0; |
368 | metadata->nPendingPages = 0; |
369 | metadata->nPendingHeapTuples = 0; |
370 | metadata->nTotalPages = 0; |
371 | metadata->nEntryPages = 0; |
372 | metadata->nDataPages = 0; |
373 | metadata->nEntries = 0; |
374 | 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 | ((PageHeader) page)->pd_lower = |
382 | ((char *) metadata + sizeof(GinMetaPageData)) - (char *) page; |
383 | } |
384 | |
385 | /* |
386 | * Compare two keys of the same index column |
387 | */ |
388 | int |
389 | 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 | if (categorya != categoryb) |
395 | return (categorya < categoryb) ? -1 : 1; |
396 | |
397 | /* all null items in same category are equal */ |
398 | if (categorya != GIN_CAT_NORM_KEY) |
399 | return 0; |
400 | |
401 | /* both not null, so safe to call the compareFn */ |
402 | return DatumGetInt32(FunctionCall2Coll(&ginstate->compareFn[attnum - 1], |
403 | ginstate->supportCollation[attnum - 1], |
404 | a, b)); |
405 | } |
406 | |
407 | /* |
408 | * Compare two keys of possibly different index columns |
409 | */ |
410 | int |
411 | 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 | if (attnuma != attnumb) |
417 | return (attnuma < attnumb) ? -1 : 1; |
418 | |
419 | 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 | cmpEntries(const void *a, const void *b, void *arg) |
445 | { |
446 | const keyEntryData *aa = (const keyEntryData *) a; |
447 | const keyEntryData *bb = (const keyEntryData *) b; |
448 | cmpEntriesArg *data = (cmpEntriesArg *) arg; |
449 | int res; |
450 | |
451 | if (aa->isnull) |
452 | { |
453 | if (bb->isnull) |
454 | res = 0; /* NULL "=" NULL */ |
455 | else |
456 | res = 1; /* NULL ">" not-NULL */ |
457 | } |
458 | else if (bb->isnull) |
459 | res = -1; /* not-NULL "<" NULL */ |
460 | else |
461 | 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 | if (res == 0) |
471 | data->haveDups = true; |
472 | |
473 | 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 | (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 | if (isNull) |
497 | { |
498 | *nentries = 1; |
499 | entries = (Datum *) palloc(sizeof(Datum)); |
500 | entries[0] = (Datum) 0; |
501 | *categories = (GinNullCategory *) palloc(sizeof(GinNullCategory)); |
502 | (*categories)[0] = GIN_CAT_NULL_ITEM; |
503 | return entries; |
504 | } |
505 | |
506 | /* OK, call the opclass's extractValueFn */ |
507 | nullFlags = NULL; /* in case extractValue doesn't set it */ |
508 | entries = (Datum *) |
509 | DatumGetPointer(FunctionCall3Coll(&ginstate->extractValueFn[attnum - 1], |
510 | 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 | if (entries == NULL || *nentries <= 0) |
519 | { |
520 | *nentries = 1; |
521 | entries = (Datum *) palloc(sizeof(Datum)); |
522 | entries[0] = (Datum) 0; |
523 | *categories = (GinNullCategory *) palloc(sizeof(GinNullCategory)); |
524 | (*categories)[0] = GIN_CAT_EMPTY_ITEM; |
525 | 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 | if (nullFlags == NULL) |
533 | 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 | if (*nentries > 1) |
543 | { |
544 | keyEntryData *keydata; |
545 | cmpEntriesArg arg; |
546 | |
547 | keydata = (keyEntryData *) palloc(*nentries * sizeof(keyEntryData)); |
548 | for (i = 0; i < *nentries; i++) |
549 | { |
550 | keydata[i].datum = entries[i]; |
551 | keydata[i].isnull = nullFlags[i]; |
552 | } |
553 | |
554 | arg.cmpDatumFunc = &ginstate->compareFn[attnum - 1]; |
555 | arg.collation = ginstate->supportCollation[attnum - 1]; |
556 | arg.haveDups = false; |
557 | qsort_arg(keydata, *nentries, sizeof(keyEntryData), |
558 | cmpEntries, (void *) &arg); |
559 | |
560 | if (arg.haveDups) |
561 | { |
562 | /* there are duplicates, must get rid of 'em */ |
563 | int32 j; |
564 | |
565 | entries[0] = keydata[0].datum; |
566 | nullFlags[0] = keydata[0].isnull; |
567 | j = 1; |
568 | for (i = 1; i < *nentries; i++) |
569 | { |
570 | if (cmpEntries(&keydata[i - 1], &keydata[i], &arg) != 0) |
571 | { |
572 | entries[j] = keydata[i].datum; |
573 | nullFlags[j] = keydata[i].isnull; |
574 | j++; |
575 | } |
576 | } |
577 | *nentries = j; |
578 | } |
579 | else |
580 | { |
581 | /* easy, no duplicates */ |
582 | for (i = 0; i < *nentries; i++) |
583 | { |
584 | entries[i] = keydata[i].datum; |
585 | nullFlags[i] = keydata[i].isnull; |
586 | } |
587 | } |
588 | |
589 | pfree(keydata); |
590 | } |
591 | |
592 | /* |
593 | * Create GinNullCategory representation from nullFlags. |
594 | */ |
595 | *categories = (GinNullCategory *) palloc0(*nentries * sizeof(GinNullCategory)); |
596 | for (i = 0; i < *nentries; i++) |
597 | (*categories)[i] = (nullFlags[i] ? GIN_CAT_NULL_KEY : GIN_CAT_NORM_KEY); |
598 | |
599 | return entries; |
600 | } |
601 | |
602 | bytea * |
603 | ginoptions(Datum reloptions, bool validate) |
604 | { |
605 | relopt_value *options; |
606 | GinOptions *rdopts; |
607 | int numoptions; |
608 | static const relopt_parse_elt tab[] = { |
609 | {"fastupdate" , RELOPT_TYPE_BOOL, offsetof(GinOptions, useFastUpdate)}, |
610 | {"gin_pending_list_limit" , RELOPT_TYPE_INT, offsetof(GinOptions, |
611 | pendingListCleanupSize)} |
612 | }; |
613 | |
614 | options = parseRelOptions(reloptions, validate, RELOPT_KIND_GIN, |
615 | &numoptions); |
616 | |
617 | /* if none set, we're done */ |
618 | if (numoptions == 0) |
619 | return NULL; |
620 | |
621 | rdopts = allocateReloptStruct(sizeof(GinOptions), options, numoptions); |
622 | |
623 | fillRelOptions((void *) rdopts, sizeof(GinOptions), options, numoptions, |
624 | validate, tab, lengthof(tab)); |
625 | |
626 | pfree(options); |
627 | |
628 | return (bytea *) rdopts; |
629 | } |
630 | |
631 | /* |
632 | * Fetch index's statistical data into *stats |
633 | * |
634 | * Note: in the result, nPendingPages can be trusted to be up-to-date, |
635 | * as can ginVersion; but the other fields are as of the last VACUUM. |
636 | */ |
637 | void |
638 | ginGetStats(Relation index, GinStatsData *stats) |
639 | { |
640 | Buffer metabuffer; |
641 | Page metapage; |
642 | GinMetaPageData *metadata; |
643 | |
644 | metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO); |
645 | LockBuffer(metabuffer, GIN_SHARE); |
646 | metapage = BufferGetPage(metabuffer); |
647 | metadata = GinPageGetMeta(metapage); |
648 | |
649 | stats->nPendingPages = metadata->nPendingPages; |
650 | stats->nTotalPages = metadata->nTotalPages; |
651 | stats->nEntryPages = metadata->nEntryPages; |
652 | stats->nDataPages = metadata->nDataPages; |
653 | stats->nEntries = metadata->nEntries; |
654 | stats->ginVersion = metadata->ginVersion; |
655 | |
656 | UnlockReleaseBuffer(metabuffer); |
657 | } |
658 | |
659 | /* |
660 | * Write the given statistics to the index's metapage |
661 | * |
662 | * Note: nPendingPages and ginVersion are *not* copied over |
663 | */ |
664 | void |
665 | ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build) |
666 | { |
667 | Buffer metabuffer; |
668 | Page metapage; |
669 | GinMetaPageData *metadata; |
670 | |
671 | metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO); |
672 | LockBuffer(metabuffer, GIN_EXCLUSIVE); |
673 | metapage = BufferGetPage(metabuffer); |
674 | metadata = GinPageGetMeta(metapage); |
675 | |
676 | START_CRIT_SECTION(); |
677 | |
678 | metadata->nTotalPages = stats->nTotalPages; |
679 | metadata->nEntryPages = stats->nEntryPages; |
680 | metadata->nDataPages = stats->nDataPages; |
681 | metadata->nEntries = stats->nEntries; |
682 | |
683 | /* |
684 | * Set pd_lower just past the end of the metadata. This is essential, |
685 | * because without doing so, metadata will be lost if xlog.c compresses |
686 | * the page. (We must do this here because pre-v11 versions of PG did not |
687 | * set the metapage's pd_lower correctly, so a pg_upgraded index might |
688 | * contain the wrong value.) |
689 | */ |
690 | ((PageHeader) metapage)->pd_lower = |
691 | ((char *) metadata + sizeof(GinMetaPageData)) - (char *) metapage; |
692 | |
693 | MarkBufferDirty(metabuffer); |
694 | |
695 | if (RelationNeedsWAL(index) && !is_build) |
696 | { |
697 | XLogRecPtr recptr; |
698 | ginxlogUpdateMeta data; |
699 | |
700 | data.node = index->rd_node; |
701 | data.ntuples = 0; |
702 | data.newRightlink = data.prevTail = InvalidBlockNumber; |
703 | memcpy(&data.metadata, metadata, sizeof(GinMetaPageData)); |
704 | |
705 | XLogBeginInsert(); |
706 | XLogRegisterData((char *) &data, sizeof(ginxlogUpdateMeta)); |
707 | XLogRegisterBuffer(0, metabuffer, REGBUF_WILL_INIT | REGBUF_STANDARD); |
708 | |
709 | recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_UPDATE_META_PAGE); |
710 | PageSetLSN(metapage, recptr); |
711 | } |
712 | |
713 | UnlockReleaseBuffer(metabuffer); |
714 | |
715 | END_CRIT_SECTION(); |
716 | } |
717 | |