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