Age Owner Branch data TLA 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
3761 tgl@sss.pgh.pa.us 39 :CBC 2097 : 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 : :
126 tgl@sss.pgh.pa.us 95 :GNC 2097 : 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
7153 bruce@momjian.us 104 :CBC 2937 : initGinState(GinState *state, Relation index)
105 : : {
5597 tgl@sss.pgh.pa.us 106 : 2937 : TupleDesc origTupdesc = RelationGetDescr(index);
107 : : int i;
108 : :
109 [ + - + - : 2937 : MemSet(state, 0, sizeof(GinState));
+ - - + -
- ]
110 : :
111 : 2937 : state->index = index;
1700 michael@paquier.xyz 112 : 2937 : state->oneCol = (origTupdesc->natts == 1);
5597 tgl@sss.pgh.pa.us 113 : 2937 : state->origTupdesc = origTupdesc;
114 : :
115 [ + + ]: 6067 : for (i = 0; i < origTupdesc->natts; i++)
116 : : {
3180 andres@anarazel.de 117 : 3130 : Form_pg_attribute attr = TupleDescAttr(origTupdesc, i);
118 : :
5597 tgl@sss.pgh.pa.us 119 [ + + ]: 3130 : if (state->oneCol)
120 : 2744 : state->tupdesc[i] = state->origTupdesc;
121 : : else
122 : : {
2723 andres@anarazel.de 123 : 386 : state->tupdesc[i] = CreateTemplateTupleDesc(2);
124 : :
5597 tgl@sss.pgh.pa.us 125 : 386 : TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 1, NULL,
126 : : INT2OID, -1, 0);
127 : 386 : TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 2, NULL,
128 : : attr->atttypid,
129 : : attr->atttypmod,
3180 andres@anarazel.de 130 : 386 : attr->attndims);
5519 tgl@sss.pgh.pa.us 131 : 386 : TupleDescInitEntryCollation(state->tupdesc[i], (AttrNumber) 2,
132 : : attr->attcollation);
50 drowley@postgresql.o 133 :GNC 386 : 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 : : */
3508 tgl@sss.pgh.pa.us 140 [ + + ]:CBC 3130 : if (index_getprocid(index, i + 1, GIN_COMPARE_PROC) != InvalidOid)
141 : : {
142 : 1460 : fmgr_info_copy(&(state->compareFn[i]),
143 : 1460 : index_getprocinfo(index, i + 1, GIN_COMPARE_PROC),
144 : : CurrentMemoryContext);
145 : : }
146 : : else
147 : : {
148 : : TypeCacheEntry *typentry;
149 : :
3180 andres@anarazel.de 150 : 1670 : typentry = lookup_type_cache(attr->atttypid,
151 : : TYPECACHE_CMP_PROC_FINFO);
3508 tgl@sss.pgh.pa.us 152 [ - + ]: 1670 : if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))
3508 tgl@sss.pgh.pa.us 153 [ # # ]:UBC 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))));
3508 tgl@sss.pgh.pa.us 157 :CBC 1670 : fmgr_info_copy(&(state->compareFn[i]),
158 : : &(typentry->cmp_proc_finfo),
159 : : CurrentMemoryContext);
160 : : }
161 : :
162 : : /* Opclass must always provide extract procs */
6507 163 : 3130 : fmgr_info_copy(&(state->extractValueFn[i]),
6172 bruce@momjian.us 164 : 3130 : index_getprocinfo(index, i + 1, GIN_EXTRACTVALUE_PROC),
165 : : CurrentMemoryContext);
6507 tgl@sss.pgh.pa.us 166 : 3130 : fmgr_info_copy(&(state->extractQueryFn[i]),
6172 bruce@momjian.us 167 : 3130 : 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 : : */
4437 heikki.linnakangas@i 174 [ + + ]: 3130 : if (index_getprocid(index, i + 1, GIN_TRICONSISTENT_PROC) != InvalidOid)
175 : : {
176 : 2743 : fmgr_info_copy(&(state->triConsistentFn[i]),
3240 tgl@sss.pgh.pa.us 177 : 2743 : index_getprocinfo(index, i + 1, GIN_TRICONSISTENT_PROC),
178 : : CurrentMemoryContext);
179 : : }
180 : :
4437 heikki.linnakangas@i 181 [ + - ]: 3130 : if (index_getprocid(index, i + 1, GIN_CONSISTENT_PROC) != InvalidOid)
182 : : {
183 : 3130 : fmgr_info_copy(&(state->consistentFn[i]),
3240 tgl@sss.pgh.pa.us 184 : 3130 : index_getprocinfo(index, i + 1, GIN_CONSISTENT_PROC),
185 : : CurrentMemoryContext);
186 : : }
187 : :
4437 heikki.linnakangas@i 188 [ - + ]: 3130 : if (state->consistentFn[i].fn_oid == InvalidOid &&
4437 heikki.linnakangas@i 189 [ # # ]:UBC 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 : : */
6172 bruce@momjian.us 199 [ + + ]:CBC 3130 : if (index_getprocid(index, i + 1, GIN_COMPARE_PARTIAL_PROC) != InvalidOid)
200 : : {
6507 tgl@sss.pgh.pa.us 201 : 540 : fmgr_info_copy(&(state->comparePartialFn[i]),
3240 202 : 540 : index_getprocinfo(index, i + 1, GIN_COMPARE_PARTIAL_PROC),
203 : : CurrentMemoryContext);
6507 204 : 540 : state->canPartialMatch[i] = true;
205 : : }
206 : : else
207 : : {
208 : 2590 : 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 : : */
5502 223 [ + + ]: 3130 : if (OidIsValid(index->rd_indcollation[i]))
5492 224 : 227 : state->supportCollation[i] = index->rd_indcollation[i];
225 : : else
226 : 2903 : state->supportCollation[i] = DEFAULT_COLLATION_OID;
227 : : }
6507 228 : 2937 : }
229 : :
230 : : /*
231 : : * Extract attribute (column) number of stored entry from GIN tuple
232 : : */
233 : : OffsetNumber
234 : 10646966 : gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple)
235 : : {
236 : : OffsetNumber colN;
237 : :
5597 238 [ + + ]: 10646966 : if (ginstate->oneCol)
239 : : {
240 : : /* column number is not stored explicitly */
241 : 4874306 : 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 : : */
6507 252 : 5772660 : res = index_getattr(tuple, FirstOffsetNumber, ginstate->tupdesc[0],
253 : : &isnull);
254 [ - + ]: 5772660 : Assert(!isnull);
255 : :
256 : 5772660 : colN = DatumGetUInt16(res);
6172 bruce@momjian.us 257 [ + - - + ]: 5772660 : Assert(colN >= FirstOffsetNumber && colN <= ginstate->origTupdesc->natts);
258 : : }
259 : :
6507 tgl@sss.pgh.pa.us 260 : 10646966 : return colN;
261 : : }
262 : :
263 : : /*
264 : : * Extract stored datum (and possible null category) from GIN tuple
265 : : */
266 : : Datum
5597 267 : 7761447 : gintuple_get_key(GinState *ginstate, IndexTuple tuple,
268 : : GinNullCategory *category)
269 : : {
270 : : Datum res;
271 : : bool isnull;
272 : :
6172 bruce@momjian.us 273 [ + + ]: 7761447 : if (ginstate->oneCol)
274 : : {
275 : : /*
276 : : * Single column index doesn't store attribute numbers in tuples
277 : : */
6507 tgl@sss.pgh.pa.us 278 : 4875600 : 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 : :
5597 294 [ + + ]: 7761447 : if (isnull)
295 [ + + ]: 1204 : *category = GinGetNullCategory(tuple, ginstate);
296 : : else
297 : 7760243 : *category = GIN_CAT_NORM_KEY;
298 : :
6507 299 : 7761447 : 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
7153 bruce@momjian.us 308 : 5684 : GinNewBuffer(Relation index)
309 : : {
310 : : Buffer buffer;
311 : :
312 : : /* First, try to get a page from FSM */
313 : : for (;;)
7153 bruce@momjian.us 314 :UBC 0 : {
6426 heikki.linnakangas@i 315 :CBC 5684 : BlockNumber blkno = GetFreeIndexPage(index);
316 : :
7308 teodor@sigaev.ru 317 [ + + ]: 5684 : if (blkno == InvalidBlockNumber)
318 : 5613 : break;
319 : :
320 : 71 : 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 : : */
7153 bruce@momjian.us 326 [ + - ]: 71 : if (ConditionalLockBuffer(buffer))
327 : : {
2700 akorotkov@postgresql 328 [ + - ]: 71 : if (GinPageIsRecyclable(BufferGetPage(buffer)))
7153 bruce@momjian.us 329 : 71 : return buffer; /* OK to use */
330 : :
7308 teodor@sigaev.ru 331 :UBC 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 */
986 tmunro@postgresql.or 339 :CBC 5613 : buffer = ExtendBufferedRel(BMR_REL(index), MAIN_FORKNUM, NULL,
340 : : EB_LOCK_FIRST);
341 : :
7308 teodor@sigaev.ru 342 : 5613 : return buffer;
343 : : }
344 : :
345 : : void
7153 bruce@momjian.us 346 : 33135 : GinInitPage(Page page, uint32 f, Size pageSize)
347 : : {
348 : : GinPageOpaque opaque;
349 : :
7308 teodor@sigaev.ru 350 : 33135 : PageInit(page, pageSize, sizeof(GinPageOpaqueData));
351 : :
352 : 33135 : opaque = GinPageGetOpaque(page);
7153 bruce@momjian.us 353 : 33135 : opaque->flags = f;
7308 teodor@sigaev.ru 354 : 33135 : opaque->rightlink = InvalidBlockNumber;
355 : 33135 : }
356 : :
357 : : void
7153 bruce@momjian.us 358 : 2514 : GinInitBuffer(Buffer b, uint32 f)
359 : : {
3667 kgrittn@postgresql.o 360 : 2514 : GinInitPage(BufferGetPage(b), f, BufferGetPageSize(b));
7308 teodor@sigaev.ru 361 : 2514 : }
362 : :
363 : : void
6251 tgl@sss.pgh.pa.us 364 : 24165 : GinInitMetabuffer(Buffer b)
365 : : {
366 : : GinMetaPageData *metadata;
3667 kgrittn@postgresql.o 367 : 24165 : Page page = BufferGetPage(b);
368 : :
6251 tgl@sss.pgh.pa.us 369 : 24165 : GinInitPage(page, GIN_META, BufferGetPageSize(b));
370 : :
371 : 24165 : metadata = GinPageGetMeta(page);
372 : :
373 : 24165 : metadata->head = metadata->tail = InvalidBlockNumber;
374 : 24165 : metadata->tailFreeSize = 0;
375 : 24165 : metadata->nPendingPages = 0;
376 : 24165 : metadata->nPendingHeapTuples = 0;
5679 377 : 24165 : metadata->nTotalPages = 0;
378 : 24165 : metadata->nEntryPages = 0;
379 : 24165 : metadata->nDataPages = 0;
380 : 24165 : metadata->nEntries = 0;
5597 381 : 24165 : 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 : : */
3106 388 : 24165 : ((PageHeader) page)->pd_lower =
389 : 24165 : ((char *) metadata + sizeof(GinMetaPageData)) - (char *) page;
6251 390 : 24165 : }
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
5597 404 : 2318745 : cmpEntries(const void *a, const void *b, void *arg)
405 : : {
28 heikki.linnakangas@i 406 :GNC 2318745 : const Datum *aa = (const Datum *) a;
407 : 2318745 : const Datum *bb = (const Datum *) b;
5597 tgl@sss.pgh.pa.us 408 :CBC 2318745 : cmpEntriesArg *data = (cmpEntriesArg *) arg;
409 : : int res;
410 : :
28 heikki.linnakangas@i 411 :GNC 2318745 : 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 : : */
7153 bruce@momjian.us 420 [ + + ]:CBC 2318745 : if (res == 0)
5597 tgl@sss.pgh.pa.us 421 : 38567 : data->haveDups = true;
422 : :
7308 teodor@sigaev.ru 423 : 2318745 : 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 *
5597 tgl@sss.pgh.pa.us 442 : 769016 : 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 [ + + ]: 769016 : if (isNull)
457 : : {
28 heikki.linnakangas@i 458 :GNC 4333 : *nentries_p = 1;
146 michael@paquier.xyz 459 : 4333 : entries = palloc_object(Datum);
5597 tgl@sss.pgh.pa.us 460 :CBC 4333 : entries[0] = (Datum) 0;
28 heikki.linnakangas@i 461 :GNC 4333 : *categories_p = palloc_object(GinNullCategory);
462 : 4333 : (*categories_p)[0] = GIN_CAT_NULL_ITEM;
5597 tgl@sss.pgh.pa.us 463 :CBC 4333 : return entries;
464 : : }
465 : :
466 : : /* OK, call the opclass's extractValueFn */
467 : 764683 : nullFlags = NULL; /* in case extractValue doesn't set it */
28 heikki.linnakangas@i 468 :GNC 764683 : nentries = 0;
469 : : entries = (Datum *)
5492 tgl@sss.pgh.pa.us 470 :CBC 764683 : DatumGetPointer(FunctionCall3Coll(&ginstate->extractValueFn[attnum - 1],
3240 471 : 764683 : 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 : : */
28 heikki.linnakangas@i 479 [ + + + + ]:GNC 764683 : if (entries == NULL || nentries <= 0)
480 : : {
481 : 1142 : *nentries_p = 1;
146 michael@paquier.xyz 482 : 1142 : entries = palloc_object(Datum);
5597 tgl@sss.pgh.pa.us 483 :CBC 1142 : entries[0] = (Datum) 0;
28 heikki.linnakangas@i 484 :GNC 1142 : *categories_p = palloc_object(GinNullCategory);
485 : 1142 : (*categories_p)[0] = GIN_CAT_EMPTY_ITEM;
5597 tgl@sss.pgh.pa.us 486 :CBC 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 : : */
28 heikki.linnakangas@i 495 :GNC 763541 : hasNull = false;
496 [ + + ]: 763541 : if (nullFlags)
497 : : {
498 : 646776 : int32 numNonNulls = 0;
499 : :
500 [ + + ]: 2317709 : for (int32 i = 0; i < nentries; i++)
501 : : {
502 [ + + ]: 1670933 : if (nullFlags[i])
503 : 20 : hasNull = true;
504 : : else
505 : : {
506 : 1670913 : entries[numNonNulls] = entries[i];
507 : 1670913 : numNonNulls++;
508 : : }
509 : : }
510 : 646776 : 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 [ + + ]: 763541 : if (nentries > 1)
521 : : {
522 : : cmpEntriesArg arg;
523 : :
5597 tgl@sss.pgh.pa.us 524 :CBC 373297 : arg.cmpDatumFunc = &ginstate->compareFn[attnum - 1];
5492 525 : 373297 : arg.collation = ginstate->supportCollation[attnum - 1];
5597 526 : 373297 : arg.haveDups = false;
527 : :
28 heikki.linnakangas@i 528 :GNC 373297 : qsort_arg_entries(entries, nentries, &arg);
529 : :
530 [ + + ]: 373297 : if (arg.haveDups)
531 : 15633 : 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");
21 540 : 763541 : categories = palloc0_array(GinNullCategory, nentries + (hasNull ? 1 : 0));
541 : : }
542 : :
543 : : /* Put back a NULL entry, if there were any */
28 544 [ + + ]: 763541 : if (hasNull)
545 : : {
546 : 20 : entries[nentries] = (Datum) 0;
547 : 20 : categories[nentries] = GIN_CAT_NULL_KEY;
548 : 20 : nentries++;
549 : : }
550 : :
551 : 763541 : *nentries_p = nentries;
552 : 763541 : *categories_p = categories;
7308 teodor@sigaev.ru 553 :CBC 763541 : return entries;
554 : : }
555 : :
556 : : bytea *
3761 tgl@sss.pgh.pa.us 557 : 336 : 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 : :
2373 michael@paquier.xyz 565 : 336 : 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
5679 tgl@sss.pgh.pa.us 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);
3667 kgrittn@postgresql.o 586 : 1760 : metapage = BufferGetPage(metabuffer);
5679 tgl@sss.pgh.pa.us 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;
5597 594 : 1760 : stats->ginVersion = metadata->ginVersion;
595 : :
5679 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
2589 heikki.linnakangas@i 605 : 241 : ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build)
606 : : {
607 : : Buffer metabuffer;
608 : : Page metapage;
609 : : GinMetaPageData *metadata;
610 : :
5679 tgl@sss.pgh.pa.us 611 : 241 : metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
612 : 241 : LockBuffer(metabuffer, GIN_EXCLUSIVE);
3667 kgrittn@postgresql.o 613 : 241 : metapage = BufferGetPage(metabuffer);
5679 tgl@sss.pgh.pa.us 614 : 241 : metadata = GinPageGetMeta(metapage);
615 : :
616 : 241 : START_CRIT_SECTION();
617 : :
618 : 241 : metadata->nTotalPages = stats->nTotalPages;
619 : 241 : metadata->nEntryPages = stats->nEntryPages;
620 : 241 : metadata->nDataPages = stats->nDataPages;
621 : 241 : 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 : : */
3106 630 : 241 : ((PageHeader) metapage)->pd_lower =
631 : 241 : ((char *) metadata + sizeof(GinMetaPageData)) - (char *) metapage;
632 : :
5679 633 : 241 : MarkBufferDirty(metabuffer);
634 : :
2589 heikki.linnakangas@i 635 [ + + + + : 241 : if (RelationNeedsWAL(index) && !is_build)
+ + + + +
+ ]
636 : : {
637 : : XLogRecPtr recptr;
638 : : ginxlogUpdateMeta data;
639 : :
1399 rhaas@postgresql.org 640 : 33 : data.locator = index->rd_locator;
5679 tgl@sss.pgh.pa.us 641 : 33 : data.ntuples = 0;
642 : 33 : data.newRightlink = data.prevTail = InvalidBlockNumber;
643 : 33 : memcpy(&data.metadata, metadata, sizeof(GinMetaPageData));
644 : :
4184 heikki.linnakangas@i 645 : 33 : XLogBeginInsert();
448 peter@eisentraut.org 646 : 33 : XLogRegisterData(&data, sizeof(ginxlogUpdateMeta));
3106 tgl@sss.pgh.pa.us 647 : 33 : XLogRegisterBuffer(0, metabuffer, REGBUF_WILL_INIT | REGBUF_STANDARD);
648 : :
4184 heikki.linnakangas@i 649 : 33 : recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_UPDATE_META_PAGE);
5679 tgl@sss.pgh.pa.us 650 : 33 : PageSetLSN(metapage, recptr);
651 : : }
652 : :
653 [ - + ]: 241 : END_CRIT_SECTION();
654 : :
75 michael@paquier.xyz 655 :GNC 241 : UnlockReleaseBuffer(metabuffer);
5679 tgl@sss.pgh.pa.us 656 :CBC 241 : }
657 : :
658 : : /*
659 : : * ginbuildphasename() -- Return name of index build phase.
660 : : */
661 : : char *
428 tomas.vondra@postgre 662 :UBC 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 : : }
|