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