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 : #include "lib/qunique.h"
32 :
33 :
34 : /*
35 : * GIN handler function: return IndexAmRoutine with access method parameters
36 : * and callbacks.
37 : */
38 : Datum
39 2211 : ginhandler(PG_FUNCTION_ARGS)
40 : {
41 : static const IndexAmRoutine amroutine = {
42 : .type = T_IndexAmRoutine,
43 : .amstrategies = 0,
44 : .amsupport = GINNProcs,
45 : .amoptsprocnum = GIN_OPTIONS_PROC,
46 : .amcanorder = false,
47 : .amcanorderbyop = false,
48 : .amcanhash = false,
49 : .amconsistentequality = false,
50 : .amconsistentordering = false,
51 : .amcanbackward = false,
52 : .amcanunique = false,
53 : .amcanmulticol = true,
54 : .amoptionalkey = true,
55 : .amsearcharray = false,
56 : .amsearchnulls = false,
57 : .amstorage = true,
58 : .amclusterable = false,
59 : .ampredlocks = true,
60 : .amcanparallel = false,
61 : .amcanbuildparallel = true,
62 : .amcaninclude = false,
63 : .amusemaintenanceworkmem = true,
64 : .amsummarizing = false,
65 : .amparallelvacuumoptions =
66 : VACUUM_OPTION_PARALLEL_BULKDEL | VACUUM_OPTION_PARALLEL_CLEANUP,
67 : .amkeytype = InvalidOid,
68 :
69 : .ambuild = ginbuild,
70 : .ambuildempty = ginbuildempty,
71 : .aminsert = gininsert,
72 : .aminsertcleanup = NULL,
73 : .ambulkdelete = ginbulkdelete,
74 : .amvacuumcleanup = ginvacuumcleanup,
75 : .amcanreturn = NULL,
76 : .amcostestimate = gincostestimate,
77 : .amgettreeheight = NULL,
78 : .amoptions = ginoptions,
79 : .amproperty = NULL,
80 : .ambuildphasename = ginbuildphasename,
81 : .amvalidate = ginvalidate,
82 : .amadjustmembers = ginadjustmembers,
83 : .ambeginscan = ginbeginscan,
84 : .amrescan = ginrescan,
85 : .amgettuple = NULL,
86 : .amgetbitmap = gingetbitmap,
87 : .amendscan = ginendscan,
88 : .ammarkpos = NULL,
89 : .amrestrpos = NULL,
90 : .amestimateparallelscan = NULL,
91 : .aminitparallelscan = NULL,
92 : .amparallelrescan = NULL,
93 : };
94 :
95 2211 : PG_RETURN_POINTER(&amroutine);
96 : }
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 2971 : initGinState(GinState *state, Relation index)
105 : {
106 2971 : TupleDesc origTupdesc = RelationGetDescr(index);
107 : int i;
108 :
109 2971 : MemSet(state, 0, sizeof(GinState));
110 :
111 2971 : state->index = index;
112 2971 : state->oneCol = (origTupdesc->natts == 1);
113 2971 : state->origTupdesc = origTupdesc;
114 :
115 6133 : for (i = 0; i < origTupdesc->natts; i++)
116 : {
117 3162 : Form_pg_attribute attr = TupleDescAttr(origTupdesc, i);
118 :
119 3162 : if (state->oneCol)
120 2780 : state->tupdesc[i] = state->origTupdesc;
121 : else
122 : {
123 382 : state->tupdesc[i] = CreateTemplateTupleDesc(2);
124 :
125 382 : TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 1, NULL,
126 : INT2OID, -1, 0);
127 382 : TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 2, NULL,
128 : attr->atttypid,
129 : attr->atttypmod,
130 382 : attr->attndims);
131 382 : TupleDescInitEntryCollation(state->tupdesc[i], (AttrNumber) 2,
132 : attr->attcollation);
133 382 : TupleDescFinalize(state->tupdesc[i]);
134 : }
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 3162 : if (index_getprocid(index, i + 1, GIN_COMPARE_PROC) != InvalidOid)
141 : {
142 1498 : fmgr_info_copy(&(state->compareFn[i]),
143 1498 : index_getprocinfo(index, i + 1, GIN_COMPARE_PROC),
144 : CurrentMemoryContext);
145 : }
146 : else
147 : {
148 : TypeCacheEntry *typentry;
149 :
150 1664 : typentry = lookup_type_cache(attr->atttypid,
151 : TYPECACHE_CMP_PROC_FINFO);
152 1664 : if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))
153 0 : ereport(ERROR,
154 : (errcode(ERRCODE_UNDEFINED_FUNCTION),
155 : errmsg("could not identify a comparison function for type %s",
156 : format_type_be(attr->atttypid))));
157 1664 : fmgr_info_copy(&(state->compareFn[i]),
158 : &(typentry->cmp_proc_finfo),
159 : CurrentMemoryContext);
160 : }
161 :
162 : /* Opclass must always provide extract procs */
163 3162 : fmgr_info_copy(&(state->extractValueFn[i]),
164 3162 : index_getprocinfo(index, i + 1, GIN_EXTRACTVALUE_PROC),
165 : CurrentMemoryContext);
166 3162 : fmgr_info_copy(&(state->extractQueryFn[i]),
167 3162 : index_getprocinfo(index, i + 1, GIN_EXTRACTQUERY_PROC),
168 : CurrentMemoryContext);
169 :
170 : /*
171 : * Check opclass capability to do tri-state or binary logic consistent
172 : * check.
173 : */
174 3162 : if (index_getprocid(index, i + 1, GIN_TRICONSISTENT_PROC) != InvalidOid)
175 : {
176 2775 : fmgr_info_copy(&(state->triConsistentFn[i]),
177 2775 : index_getprocinfo(index, i + 1, GIN_TRICONSISTENT_PROC),
178 : CurrentMemoryContext);
179 : }
180 :
181 3162 : if (index_getprocid(index, i + 1, GIN_CONSISTENT_PROC) != InvalidOid)
182 : {
183 3162 : fmgr_info_copy(&(state->consistentFn[i]),
184 3162 : index_getprocinfo(index, i + 1, GIN_CONSISTENT_PROC),
185 : CurrentMemoryContext);
186 : }
187 :
188 3162 : if (state->consistentFn[i].fn_oid == InvalidOid &&
189 0 : state->triConsistentFn[i].fn_oid == InvalidOid)
190 : {
191 0 : elog(ERROR, "missing GIN support function (%d or %d) for attribute %d of index \"%s\"",
192 : GIN_CONSISTENT_PROC, GIN_TRICONSISTENT_PROC,
193 : i + 1, RelationGetRelationName(index));
194 : }
195 :
196 : /*
197 : * Check opclass capability to do partial match.
198 : */
199 3162 : if (index_getprocid(index, i + 1, GIN_COMPARE_PARTIAL_PROC) != InvalidOid)
200 : {
201 537 : fmgr_info_copy(&(state->comparePartialFn[i]),
202 537 : index_getprocinfo(index, i + 1, GIN_COMPARE_PARTIAL_PROC),
203 : CurrentMemoryContext);
204 537 : state->canPartialMatch[i] = true;
205 : }
206 : else
207 : {
208 2625 : state->canPartialMatch[i] = false;
209 : }
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 3162 : if (OidIsValid(index->rd_indcollation[i]))
224 225 : state->supportCollation[i] = index->rd_indcollation[i];
225 : else
226 2937 : state->supportCollation[i] = DEFAULT_COLLATION_OID;
227 : }
228 2971 : }
229 :
230 : /*
231 : * Extract attribute (column) number of stored entry from GIN tuple
232 : */
233 : OffsetNumber
234 10646352 : gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple)
235 : {
236 : OffsetNumber colN;
237 :
238 10646352 : if (ginstate->oneCol)
239 : {
240 : /* column number is not stored explicitly */
241 4873692 : colN = FirstOffsetNumber;
242 : }
243 : else
244 : {
245 : Datum res;
246 : 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 5772660 : res = index_getattr(tuple, FirstOffsetNumber, ginstate->tupdesc[0],
253 : &isnull);
254 : Assert(!isnull);
255 :
256 5772660 : colN = DatumGetUInt16(res);
257 : Assert(colN >= FirstOffsetNumber && colN <= ginstate->origTupdesc->natts);
258 : }
259 :
260 10646352 : return colN;
261 : }
262 :
263 : /*
264 : * Extract stored datum (and possible null category) from GIN tuple
265 : */
266 : Datum
267 7760986 : gintuple_get_key(GinState *ginstate, IndexTuple tuple,
268 : GinNullCategory *category)
269 : {
270 : Datum res;
271 : bool isnull;
272 :
273 7760986 : if (ginstate->oneCol)
274 : {
275 : /*
276 : * Single column index doesn't store attribute numbers in tuples
277 : */
278 4875139 : res = index_getattr(tuple, FirstOffsetNumber, ginstate->origTupdesc,
279 : &isnull);
280 : }
281 : else
282 : {
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 2885847 : OffsetNumber colN = gintuple_get_attrnum(ginstate, tuple);
288 :
289 2885847 : res = index_getattr(tuple, OffsetNumberNext(FirstOffsetNumber),
290 2885847 : ginstate->tupdesc[colN - 1],
291 : &isnull);
292 : }
293 :
294 7760986 : if (isnull)
295 1195 : *category = GinGetNullCategory(tuple, ginstate);
296 : else
297 7759791 : *category = GIN_CAT_NORM_KEY;
298 :
299 7760986 : return res;
300 : }
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 5725 : GinNewBuffer(Relation index)
309 : {
310 : Buffer buffer;
311 :
312 : /* First, try to get a page from FSM */
313 : for (;;)
314 0 : {
315 5725 : BlockNumber blkno = GetFreeIndexPage(index);
316 :
317 5725 : if (blkno == InvalidBlockNumber)
318 5655 : break;
319 :
320 70 : 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 70 : if (ConditionalLockBuffer(buffer))
327 : {
328 70 : if (GinPageIsRecyclable(BufferGetPage(buffer)))
329 70 : return buffer; /* OK to use */
330 :
331 0 : LockBuffer(buffer, GIN_UNLOCK);
332 : }
333 :
334 : /* Can't use it, so release buffer and try again */
335 0 : ReleaseBuffer(buffer);
336 : }
337 :
338 : /* Must extend the file */
339 5655 : buffer = ExtendBufferedRel(BMR_REL(index), MAIN_FORKNUM, NULL,
340 : EB_LOCK_FIRST);
341 :
342 5655 : return buffer;
343 : }
344 :
345 : void
346 33173 : GinInitPage(Page page, uint32 f, Size pageSize)
347 : {
348 : GinPageOpaque opaque;
349 :
350 33173 : PageInit(page, pageSize, sizeof(GinPageOpaqueData));
351 :
352 33173 : opaque = GinPageGetOpaque(page);
353 33173 : opaque->flags = f;
354 33173 : opaque->rightlink = InvalidBlockNumber;
355 33173 : }
356 :
357 : void
358 2542 : GinInitBuffer(Buffer b, uint32 f)
359 : {
360 2542 : GinInitPage(BufferGetPage(b), f, BufferGetPageSize(b));
361 2542 : }
362 :
363 : void
364 24180 : GinInitMetabuffer(Buffer b)
365 : {
366 : GinMetaPageData *metadata;
367 24180 : Page page = BufferGetPage(b);
368 :
369 24180 : GinInitPage(page, GIN_META, BufferGetPageSize(b));
370 :
371 24180 : metadata = GinPageGetMeta(page);
372 :
373 24180 : metadata->head = metadata->tail = InvalidBlockNumber;
374 24180 : metadata->tailFreeSize = 0;
375 24180 : metadata->nPendingPages = 0;
376 24180 : metadata->nPendingHeapTuples = 0;
377 24180 : metadata->nTotalPages = 0;
378 24180 : metadata->nEntryPages = 0;
379 24180 : metadata->nDataPages = 0;
380 24180 : metadata->nEntries = 0;
381 24180 : 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 24180 : ((PageHeader) page)->pd_lower =
389 24180 : ((char *) metadata + sizeof(GinMetaPageData)) - (char *) page;
390 24180 : }
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 2380425 : cmpEntries(const void *a, const void *b, void *arg)
405 : {
406 2380425 : const Datum *aa = (const Datum *) a;
407 2380425 : const Datum *bb = (const Datum *) b;
408 2380425 : cmpEntriesArg *data = (cmpEntriesArg *) arg;
409 : int res;
410 :
411 2380425 : res = DatumGetInt32(FunctionCall2Coll(data->cmpDatumFunc,
412 : data->collation,
413 : *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 2380425 : if (res == 0)
421 45546 : data->haveDups = true;
422 :
423 2380425 : return res;
424 : }
425 :
426 : #define ST_SORT qsort_arg_entries
427 : #define ST_ELEMENT_TYPE Datum
428 : #define ST_COMPARE_ARG_TYPE cmpEntriesArg
429 : #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 772384 : ginExtractEntries(GinState *ginstate, OffsetNumber attnum,
443 : Datum value, bool isNull,
444 : int32 *nentries_p, GinNullCategory **categories_p)
445 : {
446 : Datum *entries;
447 : bool *nullFlags;
448 : GinNullCategory *categories;
449 : bool hasNull;
450 : int32 nentries;
451 :
452 : /*
453 : * We don't call the extractValueFn on a null item. Instead generate a
454 : * placeholder.
455 : */
456 772384 : if (isNull)
457 : {
458 4333 : *nentries_p = 1;
459 4333 : entries = palloc_object(Datum);
460 4333 : entries[0] = (Datum) 0;
461 4333 : *categories_p = palloc_object(GinNullCategory);
462 4333 : (*categories_p)[0] = GIN_CAT_NULL_ITEM;
463 4333 : return entries;
464 : }
465 :
466 : /* OK, call the opclass's extractValueFn */
467 768051 : nullFlags = NULL; /* in case extractValue doesn't set it */
468 768051 : nentries = 0;
469 : entries = (Datum *)
470 768051 : DatumGetPointer(FunctionCall3Coll(&ginstate->extractValueFn[attnum - 1],
471 768051 : ginstate->supportCollation[attnum - 1],
472 : value,
473 : PointerGetDatum(&nentries),
474 : PointerGetDatum(&nullFlags)));
475 :
476 : /*
477 : * Generate a placeholder if the item contained no keys.
478 : */
479 768051 : if (entries == NULL || nentries <= 0)
480 : {
481 1142 : *nentries_p = 1;
482 1142 : entries = palloc_object(Datum);
483 1142 : entries[0] = (Datum) 0;
484 1142 : *categories_p = palloc_object(GinNullCategory);
485 1142 : (*categories_p)[0] = GIN_CAT_EMPTY_ITEM;
486 1142 : return entries;
487 : }
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 766909 : hasNull = false;
496 766909 : if (nullFlags)
497 : {
498 646736 : int32 numNonNulls = 0;
499 :
500 2317549 : for (int32 i = 0; i < nentries; i++)
501 : {
502 1670813 : if (nullFlags[i])
503 20 : hasNull = true;
504 : else
505 : {
506 1670793 : entries[numNonNulls] = entries[i];
507 1670793 : numNonNulls++;
508 : }
509 : }
510 646736 : nentries = numNonNulls;
511 : }
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 766909 : if (nentries > 1)
521 : {
522 : cmpEntriesArg arg;
523 :
524 376333 : arg.cmpDatumFunc = &ginstate->compareFn[attnum - 1];
525 376333 : arg.collation = ginstate->supportCollation[attnum - 1];
526 376333 : arg.haveDups = false;
527 :
528 376333 : qsort_arg_entries(entries, nentries, &arg);
529 :
530 376333 : if (arg.haveDups)
531 17428 : nentries = qunique_arg(entries, nentries, sizeof(Datum), cmpEntries, &arg);
532 : }
533 :
534 : /*
535 : * Create GinNullCategory representation.
536 : */
537 : {
538 : /* palloc0 sets all entries to GIN_CAT_NORM_KEY */
539 : StaticAssertDecl(GIN_CAT_NORM_KEY == 0, "Assuming GIN_CAT_NORM_KEY == 0");
540 766909 : categories = palloc0_array(GinNullCategory, nentries + (hasNull ? 1 : 0));
541 : }
542 :
543 : /* Put back a NULL entry, if there were any */
544 766909 : if (hasNull)
545 : {
546 20 : entries[nentries] = (Datum) 0;
547 20 : categories[nentries] = GIN_CAT_NULL_KEY;
548 20 : nentries++;
549 : }
550 :
551 766909 : *nentries_p = nentries;
552 766909 : *categories_p = categories;
553 766909 : return entries;
554 : }
555 :
556 : bytea *
557 321 : ginoptions(Datum reloptions, bool validate)
558 : {
559 : static const relopt_parse_elt tab[] = {
560 : {"fastupdate", RELOPT_TYPE_BOOL, offsetof(GinOptions, useFastUpdate)},
561 : {"gin_pending_list_limit", RELOPT_TYPE_INT, offsetof(GinOptions,
562 : pendingListCleanupSize)}
563 : };
564 :
565 321 : return (bytea *) build_reloptions(reloptions, validate,
566 : RELOPT_KIND_GIN,
567 : sizeof(GinOptions),
568 : tab, lengthof(tab));
569 : }
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 1760 : ginGetStats(Relation index, GinStatsData *stats)
579 : {
580 : Buffer metabuffer;
581 : Page metapage;
582 : GinMetaPageData *metadata;
583 :
584 1760 : metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
585 1760 : LockBuffer(metabuffer, GIN_SHARE);
586 1760 : metapage = BufferGetPage(metabuffer);
587 1760 : metadata = GinPageGetMeta(metapage);
588 :
589 1760 : stats->nPendingPages = metadata->nPendingPages;
590 1760 : stats->nTotalPages = metadata->nTotalPages;
591 1760 : stats->nEntryPages = metadata->nEntryPages;
592 1760 : stats->nDataPages = metadata->nDataPages;
593 1760 : stats->nEntries = metadata->nEntries;
594 1760 : stats->ginVersion = metadata->ginVersion;
595 :
596 1760 : UnlockReleaseBuffer(metabuffer);
597 1760 : }
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 257 : ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build)
606 : {
607 : Buffer metabuffer;
608 : Page metapage;
609 : GinMetaPageData *metadata;
610 :
611 257 : metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
612 257 : LockBuffer(metabuffer, GIN_EXCLUSIVE);
613 257 : metapage = BufferGetPage(metabuffer);
614 257 : metadata = GinPageGetMeta(metapage);
615 :
616 257 : START_CRIT_SECTION();
617 :
618 257 : metadata->nTotalPages = stats->nTotalPages;
619 257 : metadata->nEntryPages = stats->nEntryPages;
620 257 : metadata->nDataPages = stats->nDataPages;
621 257 : 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 257 : ((PageHeader) metapage)->pd_lower =
631 257 : ((char *) metadata + sizeof(GinMetaPageData)) - (char *) metapage;
632 :
633 257 : MarkBufferDirty(metabuffer);
634 :
635 257 : if (RelationNeedsWAL(index) && !is_build)
636 : {
637 : XLogRecPtr recptr;
638 : ginxlogUpdateMeta data;
639 :
640 33 : data.locator = index->rd_locator;
641 33 : data.ntuples = 0;
642 33 : data.newRightlink = data.prevTail = InvalidBlockNumber;
643 33 : memcpy(&data.metadata, metadata, sizeof(GinMetaPageData));
644 :
645 33 : XLogBeginInsert();
646 33 : XLogRegisterData(&data, sizeof(ginxlogUpdateMeta));
647 33 : XLogRegisterBuffer(0, metabuffer, REGBUF_WILL_INIT | REGBUF_STANDARD);
648 :
649 33 : recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_UPDATE_META_PAGE);
650 33 : PageSetLSN(metapage, recptr);
651 : }
652 :
653 257 : END_CRIT_SECTION();
654 :
655 257 : UnlockReleaseBuffer(metabuffer);
656 257 : }
657 :
658 : /*
659 : * ginbuildphasename() -- Return name of index build phase.
660 : */
661 : char *
662 0 : ginbuildphasename(int64 phasenum)
663 : {
664 0 : switch (phasenum)
665 : {
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 0 : return NULL;
680 : }
681 : }
|