Coverage Report

Created: 2026-08-13 07:12

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