1 /*-------------------------------------------------------------------------
4 * insert routines for the postgres inverted index access method.
7 * Portions Copyright (c) 1996-2016, 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/xloginsert.h"
19 #include "catalog/index.h"
20 #include "miscadmin.h"
21 #include "storage/bufmgr.h"
22 #include "storage/smgr.h"
23 #include "storage/indexfsm.h"
24 #include "utils/memutils.h"
25 #include "utils/rel.h"
32 GinStatsData buildStats;
34 MemoryContext funcCtx;
35 BuildAccumulator accum;
40 * Adds array of item pointers to tuple's posting list, or
41 * creates posting tree and tuple pointing to tree in case
42 * of not enough space. Max size of tuple is defined in
43 * GinFormTuple(). Returns a new, modified index tuple.
44 * items[] must be in sorted order with no duplicates.
47 addItemPointersToLeafTuple(GinState *ginstate,
49 ItemPointerData *items, uint32 nitem,
50 GinStatsData *buildStats)
54 GinNullCategory category;
56 ItemPointerData *newItems,
60 GinPostingList *compressedList;
62 Assert(!GinIsPostingTree(old));
64 attnum = gintuple_get_attrnum(ginstate, old);
65 key = gintuple_get_key(ginstate, old, &category);
67 /* merge the old and new posting lists */
68 oldItems = ginReadTuple(ginstate, attnum, old, &oldNPosting);
70 newItems = ginMergeItemPointers(items, nitem,
71 oldItems, oldNPosting,
74 /* Compress the posting list, and try to a build tuple with room for it */
76 compressedList = ginCompressPostingList(newItems, newNPosting, GinMaxItemSize,
81 res = GinFormTuple(ginstate, attnum, key, category,
82 (char *) compressedList,
83 SizeOfGinPostingList(compressedList),
86 pfree(compressedList);
90 /* posting list would be too big, convert to posting tree */
91 BlockNumber postingRoot;
94 * Initialize posting tree with the old tuple's posting list. It's
95 * surely small enough to fit on one posting-tree page, and should
96 * already be in order with no duplicates.
98 postingRoot = createPostingTree(ginstate->index,
103 /* Now insert the TIDs-to-be-added into the posting tree */
104 ginInsertItemPointers(ginstate->index, postingRoot,
108 /* And build a new posting-tree-only result tuple */
109 res = GinFormTuple(ginstate, attnum, key, category, NULL, 0, 0, true);
110 GinSetPostingTree(res, postingRoot);
118 * Build a fresh leaf tuple, either posting-list or posting-tree format
119 * depending on whether the given items list will fit.
120 * items[] must be in sorted order with no duplicates.
122 * This is basically the same logic as in addItemPointersToLeafTuple,
123 * but working from slightly different input.
126 buildFreshLeafTuple(GinState *ginstate,
127 OffsetNumber attnum, Datum key, GinNullCategory category,
128 ItemPointerData *items, uint32 nitem,
129 GinStatsData *buildStats)
131 IndexTuple res = NULL;
132 GinPostingList *compressedList;
134 /* try to build a posting list tuple with all the items */
135 compressedList = ginCompressPostingList(items, nitem, GinMaxItemSize, NULL);
138 res = GinFormTuple(ginstate, attnum, key, category,
139 (char *) compressedList,
140 SizeOfGinPostingList(compressedList),
142 pfree(compressedList);
146 /* posting list would be too big, build posting tree */
147 BlockNumber postingRoot;
150 * Build posting-tree-only result tuple. We do this first so as to
151 * fail quickly if the key is too big.
153 res = GinFormTuple(ginstate, attnum, key, category, NULL, 0, 0, true);
156 * Initialize a new posting tree with the TIDs.
158 postingRoot = createPostingTree(ginstate->index, items, nitem,
161 /* And save the root link in the result tuple */
162 GinSetPostingTree(res, postingRoot);
169 * Insert one or more heap TIDs associated with the given key value.
170 * This will either add a single key entry, or enlarge a pre-existing entry.
172 * During an index build, buildStats is non-null and the counters
173 * it contains should be incremented as needed.
176 ginEntryInsert(GinState *ginstate,
177 OffsetNumber attnum, Datum key, GinNullCategory category,
178 ItemPointerData *items, uint32 nitem,
179 GinStatsData *buildStats)
182 GinBtreeEntryInsertData insertdata;
183 GinBtreeStack *stack;
187 insertdata.isDelete = FALSE;
189 /* During index build, count the to-be-inserted entry */
191 buildStats->nEntries++;
193 ginPrepareEntryScan(&btree, attnum, key, category, ginstate);
195 stack = ginFindLeafPage(&btree, 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 /* modify an existing leaf entry */
220 itup = addItemPointersToLeafTuple(ginstate, itup,
221 items, nitem, buildStats);
223 insertdata.isDelete = TRUE;
227 /* no match, so construct a new leaf entry */
228 itup = buildFreshLeafTuple(ginstate, attnum, key, category,
229 items, nitem, buildStats);
232 /* Insert the new or modified leaf tuple */
233 insertdata.entry = itup;
234 ginInsertValue(&btree, stack, &insertdata, buildStats);
239 * Extract index entries for a single indexable item, and add them to the
240 * BuildAccumulator's state.
242 * This function is used only during initial index creation.
245 ginHeapTupleBulkInsert(GinBuildState *buildstate, OffsetNumber attnum,
246 Datum value, bool isNull,
250 GinNullCategory *categories;
252 MemoryContext oldCtx;
254 oldCtx = MemoryContextSwitchTo(buildstate->funcCtx);
255 entries = ginExtractEntries(buildstate->accum.ginstate, attnum,
257 &nentries, &categories);
258 MemoryContextSwitchTo(oldCtx);
260 ginInsertBAEntries(&buildstate->accum, heapptr, attnum,
261 entries, categories, nentries);
263 buildstate->indtuples += nentries;
265 MemoryContextReset(buildstate->funcCtx);
269 ginBuildCallback(Relation index, HeapTuple htup, Datum *values,
270 bool *isnull, bool tupleIsAlive, void *state)
272 GinBuildState *buildstate = (GinBuildState *) state;
273 MemoryContext oldCtx;
276 oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
278 for (i = 0; i < buildstate->ginstate.origTupdesc->natts; i++)
279 ginHeapTupleBulkInsert(buildstate, (OffsetNumber) (i + 1),
280 values[i], isnull[i],
283 /* If we've maxed out our available memory, dump everything to the index */
284 if (buildstate->accum.allocatedMemory >= (Size)maintenance_work_mem * 1024L)
286 ItemPointerData *list;
288 GinNullCategory category;
292 ginBeginBAScan(&buildstate->accum);
293 while ((list = ginGetBAEntry(&buildstate->accum,
294 &attnum, &key, &category, &nlist)) != NULL)
296 /* there could be many entries, so be willing to abort here */
297 CHECK_FOR_INTERRUPTS();
298 ginEntryInsert(&buildstate->ginstate, attnum, key, category,
299 list, nlist, &buildstate->buildStats);
302 MemoryContextReset(buildstate->tmpCtx);
303 ginInitBA(&buildstate->accum);
306 MemoryContextSwitchTo(oldCtx);
310 ginbuild(Relation heap, Relation index, IndexInfo *indexInfo)
312 IndexBuildResult *result;
314 GinBuildState buildstate;
317 ItemPointerData *list;
319 GinNullCategory category;
321 MemoryContext oldCtx;
324 if (RelationGetNumberOfBlocks(index) != 0)
325 elog(ERROR, "index \"%s\" already contains data",
326 RelationGetRelationName(index));
328 initGinState(&buildstate.ginstate, index);
329 buildstate.indtuples = 0;
330 memset(&buildstate.buildStats, 0, sizeof(GinStatsData));
332 /* initialize the meta page */
333 MetaBuffer = GinNewBuffer(index);
335 /* initialize the root page */
336 RootBuffer = GinNewBuffer(index);
338 START_CRIT_SECTION();
339 GinInitMetabuffer(MetaBuffer);
340 MarkBufferDirty(MetaBuffer);
341 GinInitBuffer(RootBuffer, GIN_LEAF);
342 MarkBufferDirty(RootBuffer);
344 if (RelationNeedsWAL(index))
350 XLogRegisterBuffer(0, MetaBuffer, REGBUF_WILL_INIT);
351 XLogRegisterBuffer(1, RootBuffer, REGBUF_WILL_INIT);
353 recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_CREATE_INDEX);
355 page = BufferGetPage(RootBuffer);
356 PageSetLSN(page, recptr);
358 page = BufferGetPage(MetaBuffer);
359 PageSetLSN(page, recptr);
362 UnlockReleaseBuffer(MetaBuffer);
363 UnlockReleaseBuffer(RootBuffer);
366 /* count the root as first entry page */
367 buildstate.buildStats.nEntryPages++;
370 * create a temporary memory context that is used to hold data not yet
371 * dumped out to the index
373 buildstate.tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
374 "Gin build temporary context",
375 ALLOCSET_DEFAULT_MINSIZE,
376 ALLOCSET_DEFAULT_INITSIZE,
377 ALLOCSET_DEFAULT_MAXSIZE);
380 * create a temporary memory context that is used for calling
381 * ginExtractEntries(), and can be reset after each tuple
383 buildstate.funcCtx = AllocSetContextCreate(CurrentMemoryContext,
384 "Gin build temporary context for user-defined function",
385 ALLOCSET_DEFAULT_MINSIZE,
386 ALLOCSET_DEFAULT_INITSIZE,
387 ALLOCSET_DEFAULT_MAXSIZE);
389 buildstate.accum.ginstate = &buildstate.ginstate;
390 ginInitBA(&buildstate.accum);
393 * Do the heap scan. We disallow sync scan here because dataPlaceToPage
394 * prefers to receive tuples in TID order.
396 reltuples = IndexBuildHeapScan(heap, index, indexInfo, false,
397 ginBuildCallback, (void *) &buildstate);
399 /* dump remaining entries to the index */
400 oldCtx = MemoryContextSwitchTo(buildstate.tmpCtx);
401 ginBeginBAScan(&buildstate.accum);
402 while ((list = ginGetBAEntry(&buildstate.accum,
403 &attnum, &key, &category, &nlist)) != NULL)
405 /* there could be many entries, so be willing to abort here */
406 CHECK_FOR_INTERRUPTS();
407 ginEntryInsert(&buildstate.ginstate, attnum, key, category,
408 list, nlist, &buildstate.buildStats);
410 MemoryContextSwitchTo(oldCtx);
412 MemoryContextDelete(buildstate.funcCtx);
413 MemoryContextDelete(buildstate.tmpCtx);
416 * Update metapage stats
418 buildstate.buildStats.nTotalPages = RelationGetNumberOfBlocks(index);
419 ginUpdateStats(index, &buildstate.buildStats);
424 result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult));
426 result->heap_tuples = reltuples;
427 result->index_tuples = buildstate.indtuples;
433 * ginbuildempty() -- build an empty gin index in the initialization fork
436 ginbuildempty(Relation index)
441 /* An empty GIN index has two pages. */
443 ReadBufferExtended(index, INIT_FORKNUM, P_NEW, RBM_NORMAL, NULL);
444 LockBuffer(MetaBuffer, BUFFER_LOCK_EXCLUSIVE);
446 ReadBufferExtended(index, INIT_FORKNUM, P_NEW, RBM_NORMAL, NULL);
447 LockBuffer(RootBuffer, BUFFER_LOCK_EXCLUSIVE);
449 /* Initialize and xlog metabuffer and root buffer. */
450 START_CRIT_SECTION();
451 GinInitMetabuffer(MetaBuffer);
452 MarkBufferDirty(MetaBuffer);
453 log_newpage_buffer(MetaBuffer, false);
454 GinInitBuffer(RootBuffer, GIN_LEAF);
455 MarkBufferDirty(RootBuffer);
456 log_newpage_buffer(RootBuffer, false);
459 /* Unlock and release the buffers. */
460 UnlockReleaseBuffer(MetaBuffer);
461 UnlockReleaseBuffer(RootBuffer);
465 * Insert index entries for a single indexable item during "normal"
466 * (non-fast-update) insertion
469 ginHeapTupleInsert(GinState *ginstate, OffsetNumber attnum,
470 Datum value, bool isNull,
474 GinNullCategory *categories;
478 entries = ginExtractEntries(ginstate, attnum, value, isNull,
479 &nentries, &categories);
481 for (i = 0; i < nentries; i++)
482 ginEntryInsert(ginstate, attnum, entries[i], categories[i],
487 gininsert(Relation index, Datum *values, bool *isnull,
488 ItemPointer ht_ctid, Relation heapRel,
489 IndexUniqueCheck checkUnique)
492 MemoryContext oldCtx;
493 MemoryContext insertCtx;
496 insertCtx = AllocSetContextCreate(CurrentMemoryContext,
497 "Gin insert temporary context",
498 ALLOCSET_DEFAULT_MINSIZE,
499 ALLOCSET_DEFAULT_INITSIZE,
500 ALLOCSET_DEFAULT_MAXSIZE);
502 oldCtx = MemoryContextSwitchTo(insertCtx);
504 initGinState(&ginstate, index);
506 if (GinGetUseFastUpdate(index))
508 GinTupleCollector collector;
510 memset(&collector, 0, sizeof(GinTupleCollector));
512 for (i = 0; i < ginstate.origTupdesc->natts; i++)
513 ginHeapTupleFastCollect(&ginstate, &collector,
514 (OffsetNumber) (i + 1),
515 values[i], isnull[i],
518 ginHeapTupleFastInsert(&ginstate, &collector);
522 for (i = 0; i < ginstate.origTupdesc->natts; i++)
523 ginHeapTupleInsert(&ginstate, (OffsetNumber) (i + 1),
524 values[i], isnull[i],
528 MemoryContextSwitchTo(oldCtx);
529 MemoryContextDelete(insertCtx);