1 /*-------------------------------------------------------------------------
4 * insert routines for the postgres inverted index access method.
7 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
8 * Portions Copyright (c) 1994, Regents of the University of California
11 * src/backend/access/gin/gininsert.c
12 *-------------------------------------------------------------------------
17 #include "access/gin_private.h"
18 #include "access/ginxlog.h"
19 #include "access/tableam.h"
20 #include "access/xloginsert.h"
21 #include "catalog/index.h"
22 #include "miscadmin.h"
23 #include "storage/bufmgr.h"
24 #include "storage/indexfsm.h"
25 #include "storage/predicate.h"
26 #include "storage/smgr.h"
27 #include "utils/memutils.h"
28 #include "utils/rel.h"
34 GinStatsData buildStats
;
36 MemoryContext funcCtx
;
37 BuildAccumulator accum
;
42 * Adds array of item pointers to tuple's posting list, or
43 * creates posting tree and tuple pointing to tree in case
44 * of not enough space. Max size of tuple is defined in
45 * GinFormTuple(). Returns a new, modified index tuple.
46 * items[] must be in sorted order with no duplicates.
49 addItemPointersToLeafTuple(GinState
*ginstate
,
51 ItemPointerData
*items
, uint32 nitem
,
52 GinStatsData
*buildStats
, Buffer buffer
)
56 GinNullCategory category
;
58 ItemPointerData
*newItems
,
62 GinPostingList
*compressedList
;
64 Assert(!GinIsPostingTree(old
));
66 attnum
= gintuple_get_attrnum(ginstate
, old
);
67 key
= gintuple_get_key(ginstate
, old
, &category
);
69 /* merge the old and new posting lists */
70 oldItems
= ginReadTuple(ginstate
, attnum
, old
, &oldNPosting
);
72 newItems
= ginMergeItemPointers(items
, nitem
,
73 oldItems
, oldNPosting
,
76 /* Compress the posting list, and try to a build tuple with room for it */
78 compressedList
= ginCompressPostingList(newItems
, newNPosting
, GinMaxItemSize
,
83 res
= GinFormTuple(ginstate
, attnum
, key
, category
,
84 (char *) compressedList
,
85 SizeOfGinPostingList(compressedList
),
88 pfree(compressedList
);
92 /* posting list would be too big, convert to posting tree */
93 BlockNumber postingRoot
;
96 * Initialize posting tree with the old tuple's posting list. It's
97 * surely small enough to fit on one posting-tree page, and should
98 * already be in order with no duplicates.
100 postingRoot
= createPostingTree(ginstate
->index
,
106 /* Now insert the TIDs-to-be-added into the posting tree */
107 ginInsertItemPointers(ginstate
->index
, postingRoot
,
111 /* And build a new posting-tree-only result tuple */
112 res
= GinFormTuple(ginstate
, attnum
, key
, category
, NULL
, 0, 0, true);
113 GinSetPostingTree(res
, postingRoot
);
121 * Build a fresh leaf tuple, either posting-list or posting-tree format
122 * depending on whether the given items list will fit.
123 * items[] must be in sorted order with no duplicates.
125 * This is basically the same logic as in addItemPointersToLeafTuple,
126 * but working from slightly different input.
129 buildFreshLeafTuple(GinState
*ginstate
,
130 OffsetNumber attnum
, Datum key
, GinNullCategory category
,
131 ItemPointerData
*items
, uint32 nitem
,
132 GinStatsData
*buildStats
, Buffer buffer
)
134 IndexTuple res
= NULL
;
135 GinPostingList
*compressedList
;
137 /* try to build a posting list tuple with all the items */
138 compressedList
= ginCompressPostingList(items
, nitem
, GinMaxItemSize
, NULL
);
141 res
= GinFormTuple(ginstate
, attnum
, key
, category
,
142 (char *) compressedList
,
143 SizeOfGinPostingList(compressedList
),
145 pfree(compressedList
);
149 /* posting list would be too big, build posting tree */
150 BlockNumber postingRoot
;
153 * Build posting-tree-only result tuple. We do this first so as to
154 * fail quickly if the key is too big.
156 res
= GinFormTuple(ginstate
, attnum
, key
, category
, NULL
, 0, 0, true);
159 * Initialize a new posting tree with the TIDs.
161 postingRoot
= createPostingTree(ginstate
->index
, items
, nitem
,
164 /* And save the root link in the result tuple */
165 GinSetPostingTree(res
, postingRoot
);
172 * Insert one or more heap TIDs associated with the given key value.
173 * This will either add a single key entry, or enlarge a pre-existing entry.
175 * During an index build, buildStats is non-null and the counters
176 * it contains should be incremented as needed.
179 ginEntryInsert(GinState
*ginstate
,
180 OffsetNumber attnum
, Datum key
, GinNullCategory category
,
181 ItemPointerData
*items
, uint32 nitem
,
182 GinStatsData
*buildStats
)
185 GinBtreeEntryInsertData insertdata
;
186 GinBtreeStack
*stack
;
190 insertdata
.isDelete
= false;
192 ginPrepareEntryScan(&btree
, attnum
, key
, category
, ginstate
);
193 btree
.isBuild
= (buildStats
!= NULL
);
195 stack
= ginFindLeafPage(&btree
, false, false, NULL
);
196 page
= BufferGetPage(stack
->buffer
);
198 if (btree
.findItem(&btree
, stack
))
200 /* found pre-existing entry */
201 itup
= (IndexTuple
) PageGetItem(page
, PageGetItemId(page
, stack
->off
));
203 if (GinIsPostingTree(itup
))
205 /* add entries to existing posting tree */
206 BlockNumber rootPostingTree
= GinGetPostingTree(itup
);
208 /* release all stack */
209 LockBuffer(stack
->buffer
, GIN_UNLOCK
);
210 freeGinBtreeStack(stack
);
212 /* insert into posting tree */
213 ginInsertItemPointers(ginstate
->index
, rootPostingTree
,
219 CheckForSerializableConflictIn(ginstate
->index
, NULL
,
220 BufferGetBlockNumber(stack
->buffer
));
221 /* modify an existing leaf entry */
222 itup
= addItemPointersToLeafTuple(ginstate
, itup
,
223 items
, nitem
, buildStats
, stack
->buffer
);
225 insertdata
.isDelete
= true;
229 CheckForSerializableConflictIn(ginstate
->index
, NULL
,
230 BufferGetBlockNumber(stack
->buffer
));
231 /* no match, so construct a new leaf entry */
232 itup
= buildFreshLeafTuple(ginstate
, attnum
, key
, category
,
233 items
, nitem
, buildStats
, stack
->buffer
);
236 * nEntries counts leaf tuples, so increment it only when we make a
240 buildStats
->nEntries
++;
243 /* Insert the new or modified leaf tuple */
244 insertdata
.entry
= itup
;
245 ginInsertValue(&btree
, stack
, &insertdata
, buildStats
);
250 * Extract index entries for a single indexable item, and add them to the
251 * BuildAccumulator's state.
253 * This function is used only during initial index creation.
256 ginHeapTupleBulkInsert(GinBuildState
*buildstate
, OffsetNumber attnum
,
257 Datum value
, bool isNull
,
261 GinNullCategory
*categories
;
263 MemoryContext oldCtx
;
265 oldCtx
= MemoryContextSwitchTo(buildstate
->funcCtx
);
266 entries
= ginExtractEntries(buildstate
->accum
.ginstate
, attnum
,
268 &nentries
, &categories
);
269 MemoryContextSwitchTo(oldCtx
);
271 ginInsertBAEntries(&buildstate
->accum
, heapptr
, attnum
,
272 entries
, categories
, nentries
);
274 buildstate
->indtuples
+= nentries
;
276 MemoryContextReset(buildstate
->funcCtx
);
280 ginBuildCallback(Relation index
, ItemPointer tid
, Datum
*values
,
281 bool *isnull
, bool tupleIsAlive
, void *state
)
283 GinBuildState
*buildstate
= (GinBuildState
*) state
;
284 MemoryContext oldCtx
;
287 oldCtx
= MemoryContextSwitchTo(buildstate
->tmpCtx
);
289 for (i
= 0; i
< buildstate
->ginstate
.origTupdesc
->natts
; i
++)
290 ginHeapTupleBulkInsert(buildstate
, (OffsetNumber
) (i
+ 1),
291 values
[i
], isnull
[i
], tid
);
293 /* If we've maxed out our available memory, dump everything to the index */
294 if (buildstate
->accum
.allocatedMemory
>= (Size
) maintenance_work_mem
* 1024L)
296 ItemPointerData
*list
;
298 GinNullCategory category
;
302 ginBeginBAScan(&buildstate
->accum
);
303 while ((list
= ginGetBAEntry(&buildstate
->accum
,
304 &attnum
, &key
, &category
, &nlist
)) != NULL
)
306 /* there could be many entries, so be willing to abort here */
307 CHECK_FOR_INTERRUPTS();
308 ginEntryInsert(&buildstate
->ginstate
, attnum
, key
, category
,
309 list
, nlist
, &buildstate
->buildStats
);
312 MemoryContextReset(buildstate
->tmpCtx
);
313 ginInitBA(&buildstate
->accum
);
316 MemoryContextSwitchTo(oldCtx
);
320 ginbuild(Relation heap
, Relation index
, IndexInfo
*indexInfo
)
322 IndexBuildResult
*result
;
324 GinBuildState buildstate
;
327 ItemPointerData
*list
;
329 GinNullCategory category
;
331 MemoryContext oldCtx
;
334 if (RelationGetNumberOfBlocks(index
) != 0)
335 elog(ERROR
, "index \"%s\" already contains data",
336 RelationGetRelationName(index
));
338 initGinState(&buildstate
.ginstate
, index
);
339 buildstate
.indtuples
= 0;
340 memset(&buildstate
.buildStats
, 0, sizeof(GinStatsData
));
342 /* initialize the meta page */
343 MetaBuffer
= GinNewBuffer(index
);
345 /* initialize the root page */
346 RootBuffer
= GinNewBuffer(index
);
348 START_CRIT_SECTION();
349 GinInitMetabuffer(MetaBuffer
);
350 MarkBufferDirty(MetaBuffer
);
351 GinInitBuffer(RootBuffer
, GIN_LEAF
);
352 MarkBufferDirty(RootBuffer
);
355 UnlockReleaseBuffer(MetaBuffer
);
356 UnlockReleaseBuffer(RootBuffer
);
359 /* count the root as first entry page */
360 buildstate
.buildStats
.nEntryPages
++;
363 * create a temporary memory context that is used to hold data not yet
364 * dumped out to the index
366 buildstate
.tmpCtx
= AllocSetContextCreate(CurrentMemoryContext
,
367 "Gin build temporary context",
368 ALLOCSET_DEFAULT_SIZES
);
371 * create a temporary memory context that is used for calling
372 * ginExtractEntries(), and can be reset after each tuple
374 buildstate
.funcCtx
= AllocSetContextCreate(CurrentMemoryContext
,
375 "Gin build temporary context for user-defined function",
376 ALLOCSET_DEFAULT_SIZES
);
378 buildstate
.accum
.ginstate
= &buildstate
.ginstate
;
379 ginInitBA(&buildstate
.accum
);
382 * Do the heap scan. We disallow sync scan here because dataPlaceToPage
383 * prefers to receive tuples in TID order.
385 reltuples
= table_index_build_scan(heap
, index
, indexInfo
, false, true,
386 ginBuildCallback
, (void *) &buildstate
,
389 /* dump remaining entries to the index */
390 oldCtx
= MemoryContextSwitchTo(buildstate
.tmpCtx
);
391 ginBeginBAScan(&buildstate
.accum
);
392 while ((list
= ginGetBAEntry(&buildstate
.accum
,
393 &attnum
, &key
, &category
, &nlist
)) != NULL
)
395 /* there could be many entries, so be willing to abort here */
396 CHECK_FOR_INTERRUPTS();
397 ginEntryInsert(&buildstate
.ginstate
, attnum
, key
, category
,
398 list
, nlist
, &buildstate
.buildStats
);
400 MemoryContextSwitchTo(oldCtx
);
402 MemoryContextDelete(buildstate
.funcCtx
);
403 MemoryContextDelete(buildstate
.tmpCtx
);
406 * Update metapage stats
408 buildstate
.buildStats
.nTotalPages
= RelationGetNumberOfBlocks(index
);
409 ginUpdateStats(index
, &buildstate
.buildStats
, true);
412 * We didn't write WAL records as we built the index, so if WAL-logging is
413 * required, write all pages to the WAL now.
415 if (RelationNeedsWAL(index
))
417 log_newpage_range(index
, MAIN_FORKNUM
,
418 0, RelationGetNumberOfBlocks(index
),
425 result
= (IndexBuildResult
*) palloc(sizeof(IndexBuildResult
));
427 result
->heap_tuples
= reltuples
;
428 result
->index_tuples
= buildstate
.indtuples
;
434 * ginbuildempty() -- build an empty gin index in the initialization fork
437 ginbuildempty(Relation index
)
442 /* An empty GIN index has two pages. */
444 ReadBufferExtended(index
, INIT_FORKNUM
, P_NEW
, RBM_NORMAL
, NULL
);
445 LockBuffer(MetaBuffer
, BUFFER_LOCK_EXCLUSIVE
);
447 ReadBufferExtended(index
, INIT_FORKNUM
, P_NEW
, RBM_NORMAL
, NULL
);
448 LockBuffer(RootBuffer
, BUFFER_LOCK_EXCLUSIVE
);
450 /* Initialize and xlog metabuffer and root buffer. */
451 START_CRIT_SECTION();
452 GinInitMetabuffer(MetaBuffer
);
453 MarkBufferDirty(MetaBuffer
);
454 log_newpage_buffer(MetaBuffer
, true);
455 GinInitBuffer(RootBuffer
, GIN_LEAF
);
456 MarkBufferDirty(RootBuffer
);
457 log_newpage_buffer(RootBuffer
, false);
460 /* Unlock and release the buffers. */
461 UnlockReleaseBuffer(MetaBuffer
);
462 UnlockReleaseBuffer(RootBuffer
);
466 * Insert index entries for a single indexable item during "normal"
467 * (non-fast-update) insertion
470 ginHeapTupleInsert(GinState
*ginstate
, OffsetNumber attnum
,
471 Datum value
, bool isNull
,
475 GinNullCategory
*categories
;
479 entries
= ginExtractEntries(ginstate
, attnum
, value
, isNull
,
480 &nentries
, &categories
);
482 for (i
= 0; i
< nentries
; i
++)
483 ginEntryInsert(ginstate
, attnum
, entries
[i
], categories
[i
],
488 gininsert(Relation index
, Datum
*values
, bool *isnull
,
489 ItemPointer ht_ctid
, Relation heapRel
,
490 IndexUniqueCheck checkUnique
,
492 IndexInfo
*indexInfo
)
494 GinState
*ginstate
= (GinState
*) indexInfo
->ii_AmCache
;
495 MemoryContext oldCtx
;
496 MemoryContext insertCtx
;
499 /* Initialize GinState cache if first call in this statement */
500 if (ginstate
== NULL
)
502 oldCtx
= MemoryContextSwitchTo(indexInfo
->ii_Context
);
503 ginstate
= (GinState
*) palloc(sizeof(GinState
));
504 initGinState(ginstate
, index
);
505 indexInfo
->ii_AmCache
= (void *) ginstate
;
506 MemoryContextSwitchTo(oldCtx
);
509 insertCtx
= AllocSetContextCreate(CurrentMemoryContext
,
510 "Gin insert temporary context",
511 ALLOCSET_DEFAULT_SIZES
);
513 oldCtx
= MemoryContextSwitchTo(insertCtx
);
515 if (GinGetUseFastUpdate(index
))
517 GinTupleCollector collector
;
519 memset(&collector
, 0, sizeof(GinTupleCollector
));
521 for (i
= 0; i
< ginstate
->origTupdesc
->natts
; i
++)
522 ginHeapTupleFastCollect(ginstate
, &collector
,
523 (OffsetNumber
) (i
+ 1),
524 values
[i
], isnull
[i
],
527 ginHeapTupleFastInsert(ginstate
, &collector
);
531 for (i
= 0; i
< ginstate
->origTupdesc
->natts
; i
++)
532 ginHeapTupleInsert(ginstate
, (OffsetNumber
) (i
+ 1),
533 values
[i
], isnull
[i
],
537 MemoryContextSwitchTo(oldCtx
);
538 MemoryContextDelete(insertCtx
);