1
/*-------------------------------------------------------------------------
4
* insert routines for the postgres inverted index access method.
7
* Portions Copyright (c) 1996-2018, 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/xloginsert.h"
20
#include "catalog/index.h"
21
#include "miscadmin.h"
22
#include "storage/bufmgr.h"
23
#include "storage/smgr.h"
24
#include "storage/indexfsm.h"
25
#include "storage/predicate.h"
26
#include "utils/memutils.h"
27
#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
/* During index build, count the to-be-inserted entry */
194
buildStats->nEntries++;
196
ginPrepareEntryScan(&btree, attnum, key, category, ginstate);
198
stack = ginFindLeafPage(&btree, false, NULL);
199
page = BufferGetPage(stack->buffer);
201
if (btree.findItem(&btree, stack))
203
/* found pre-existing entry */
204
itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, stack->off));
206
if (GinIsPostingTree(itup))
208
/* add entries to existing posting tree */
209
BlockNumber rootPostingTree = GinGetPostingTree(itup);
211
/* release all stack */
212
LockBuffer(stack->buffer, GIN_UNLOCK);
213
freeGinBtreeStack(stack);
215
/* insert into posting tree */
216
ginInsertItemPointers(ginstate->index, rootPostingTree,
222
CheckForSerializableConflictIn(ginstate->index, NULL, stack->buffer);
223
/* modify an existing leaf entry */
224
itup = addItemPointersToLeafTuple(ginstate, itup,
225
items, nitem, buildStats, stack->buffer);
227
insertdata.isDelete = true;
231
CheckForSerializableConflictIn(ginstate->index, NULL, stack->buffer);
232
/* no match, so construct a new leaf entry */
233
itup = buildFreshLeafTuple(ginstate, attnum, key, category,
234
items, nitem, buildStats, stack->buffer);
237
/* Insert the new or modified leaf tuple */
238
insertdata.entry = itup;
239
ginInsertValue(&btree, stack, &insertdata, buildStats);
244
* Extract index entries for a single indexable item, and add them to the
245
* BuildAccumulator's state.
247
* This function is used only during initial index creation.
250
ginHeapTupleBulkInsert(GinBuildState *buildstate, OffsetNumber attnum,
251
Datum value, bool isNull,
255
GinNullCategory *categories;
257
MemoryContext oldCtx;
259
oldCtx = MemoryContextSwitchTo(buildstate->funcCtx);
260
entries = ginExtractEntries(buildstate->accum.ginstate, attnum,
262
&nentries, &categories);
263
MemoryContextSwitchTo(oldCtx);
265
ginInsertBAEntries(&buildstate->accum, heapptr, attnum,
266
entries, categories, nentries);
268
buildstate->indtuples += nentries;
270
MemoryContextReset(buildstate->funcCtx);
274
ginBuildCallback(Relation index, HeapTuple htup, Datum *values,
275
bool *isnull, bool tupleIsAlive, void *state)
277
GinBuildState *buildstate = (GinBuildState *) state;
278
MemoryContext oldCtx;
281
oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
283
for (i = 0; i < buildstate->ginstate.origTupdesc->natts; i++)
284
ginHeapTupleBulkInsert(buildstate, (OffsetNumber) (i + 1),
285
values[i], isnull[i],
288
/* If we've maxed out our available memory, dump everything to the index */
289
if (buildstate->accum.allocatedMemory >= (Size) maintenance_work_mem * 1024L)
291
ItemPointerData *list;
293
GinNullCategory category;
297
ginBeginBAScan(&buildstate->accum);
298
while ((list = ginGetBAEntry(&buildstate->accum,
299
&attnum, &key, &category, &nlist)) != NULL)
301
/* there could be many entries, so be willing to abort here */
302
CHECK_FOR_INTERRUPTS();
303
ginEntryInsert(&buildstate->ginstate, attnum, key, category,
304
list, nlist, &buildstate->buildStats);
307
MemoryContextReset(buildstate->tmpCtx);
308
ginInitBA(&buildstate->accum);
311
MemoryContextSwitchTo(oldCtx);
315
ginbuild(Relation heap, Relation index, IndexInfo *indexInfo)
317
IndexBuildResult *result;
319
GinBuildState buildstate;
322
ItemPointerData *list;
324
GinNullCategory category;
326
MemoryContext oldCtx;
329
if (RelationGetNumberOfBlocks(index) != 0)
330
elog(ERROR, "index \"%s\" already contains data",
331
RelationGetRelationName(index));
333
initGinState(&buildstate.ginstate, index);
334
buildstate.indtuples = 0;
335
memset(&buildstate.buildStats, 0, sizeof(GinStatsData));
337
/* initialize the meta page */
338
MetaBuffer = GinNewBuffer(index);
340
/* initialize the root page */
341
RootBuffer = GinNewBuffer(index);
343
START_CRIT_SECTION();
344
GinInitMetabuffer(MetaBuffer);
345
MarkBufferDirty(MetaBuffer);
346
GinInitBuffer(RootBuffer, GIN_LEAF);
347
MarkBufferDirty(RootBuffer);
349
if (RelationNeedsWAL(index))
355
XLogRegisterBuffer(0, MetaBuffer, REGBUF_WILL_INIT | REGBUF_STANDARD);
356
XLogRegisterBuffer(1, RootBuffer, REGBUF_WILL_INIT);
358
recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_CREATE_INDEX);
360
page = BufferGetPage(RootBuffer);
361
PageSetLSN(page, recptr);
363
page = BufferGetPage(MetaBuffer);
364
PageSetLSN(page, recptr);
367
UnlockReleaseBuffer(MetaBuffer);
368
UnlockReleaseBuffer(RootBuffer);
371
/* count the root as first entry page */
372
buildstate.buildStats.nEntryPages++;
375
* create a temporary memory context that is used to hold data not yet
376
* dumped out to the index
378
buildstate.tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
379
"Gin build temporary context",
380
ALLOCSET_DEFAULT_SIZES);
383
* create a temporary memory context that is used for calling
384
* ginExtractEntries(), and can be reset after each tuple
386
buildstate.funcCtx = AllocSetContextCreate(CurrentMemoryContext,
387
"Gin build temporary context for user-defined function",
388
ALLOCSET_DEFAULT_SIZES);
390
buildstate.accum.ginstate = &buildstate.ginstate;
391
ginInitBA(&buildstate.accum);
394
* Do the heap scan. We disallow sync scan here because dataPlaceToPage
395
* prefers to receive tuples in TID order.
397
reltuples = IndexBuildHeapScan(heap, index, indexInfo, false,
398
ginBuildCallback, (void *) &buildstate, NULL);
400
/* dump remaining entries to the index */
401
oldCtx = MemoryContextSwitchTo(buildstate.tmpCtx);
402
ginBeginBAScan(&buildstate.accum);
403
while ((list = ginGetBAEntry(&buildstate.accum,
404
&attnum, &key, &category, &nlist)) != NULL)
406
/* there could be many entries, so be willing to abort here */
407
CHECK_FOR_INTERRUPTS();
408
ginEntryInsert(&buildstate.ginstate, attnum, key, category,
409
list, nlist, &buildstate.buildStats);
411
MemoryContextSwitchTo(oldCtx);
413
MemoryContextDelete(buildstate.funcCtx);
414
MemoryContextDelete(buildstate.tmpCtx);
417
* Update metapage stats
419
buildstate.buildStats.nTotalPages = RelationGetNumberOfBlocks(index);
420
ginUpdateStats(index, &buildstate.buildStats);
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,
491
IndexInfo *indexInfo)
493
GinState *ginstate = (GinState *) indexInfo->ii_AmCache;
494
MemoryContext oldCtx;
495
MemoryContext insertCtx;
498
/* Initialize GinState cache if first call in this statement */
499
if (ginstate == NULL)
501
oldCtx = MemoryContextSwitchTo(indexInfo->ii_Context);
502
ginstate = (GinState *) palloc(sizeof(GinState));
503
initGinState(ginstate, index);
504
indexInfo->ii_AmCache = (void *) ginstate;
505
MemoryContextSwitchTo(oldCtx);
508
insertCtx = AllocSetContextCreate(CurrentMemoryContext,
509
"Gin insert temporary context",
510
ALLOCSET_DEFAULT_SIZES);
512
oldCtx = MemoryContextSwitchTo(insertCtx);
514
if (GinGetUseFastUpdate(index))
516
GinTupleCollector collector;
518
memset(&collector, 0, sizeof(GinTupleCollector));
520
for (i = 0; i < ginstate->origTupdesc->natts; i++)
521
ginHeapTupleFastCollect(ginstate, &collector,
522
(OffsetNumber) (i + 1),
523
values[i], isnull[i],
526
ginHeapTupleFastInsert(ginstate, &collector);
530
for (i = 0; i < ginstate->origTupdesc->natts; i++)
531
ginHeapTupleInsert(ginstate, (OffsetNumber) (i + 1),
532
values[i], isnull[i],
536
MemoryContextSwitchTo(oldCtx);
537
MemoryContextDelete(insertCtx);