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