1 : /*-------------------------------------------------------------------------
2 : *
3 : * gininsert.c
4 : * insert routines for the postgres inverted index access method.
5 : *
6 : *
7 : * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
8 : * Portions Copyright (c) 1994, Regents of the University of California
9 : *
10 : * IDENTIFICATION
11 : * $PostgreSQL: pgsql/src/backend/access/gin/gininsert.c,v 1.10 2007/11/16 21:55:59 tgl Exp $
12 : *-------------------------------------------------------------------------
13 : */
14 :
15 : #include "postgres.h"
16 :
17 : #include "access/genam.h"
18 : #include "access/gin.h"
19 : #include "catalog/index.h"
20 : #include "miscadmin.h"
21 : #include "utils/memutils.h"
22 :
23 :
24 : typedef struct
25 : {
26 : GinState ginstate;
27 : double indtuples;
28 : MemoryContext tmpCtx;
29 : MemoryContext funcCtx;
30 : BuildAccumulator accum;
31 : } GinBuildState;
32 :
33 : /*
34 : * Creates posting tree with one page. Function
35 : * suppose that items[] fits to page
36 : */
37 : static BlockNumber
38 : createPostingTree(Relation index, ItemPointerData *items, uint32 nitems)
39 19 : {
40 : BlockNumber blkno;
41 19 : Buffer buffer = GinNewBuffer(index);
42 : Page page;
43 :
44 19 : START_CRIT_SECTION();
45 :
46 19 : GinInitBuffer(buffer, GIN_DATA | GIN_LEAF);
47 19 : page = BufferGetPage(buffer);
48 19 : blkno = BufferGetBlockNumber(buffer);
49 :
50 19 : memcpy(GinDataPageGetData(page), items, sizeof(ItemPointerData) * nitems);
51 19 : GinPageGetOpaque(page)->maxoff = nitems;
52 :
53 19 : MarkBufferDirty(buffer);
54 :
55 19 : if (!index->rd_istemp)
56 : {
57 : XLogRecPtr recptr;
58 : XLogRecData rdata[2];
59 : ginxlogCreatePostingTree data;
60 :
61 19 : data.node = index->rd_node;
62 19 : data.blkno = blkno;
63 19 : data.nitem = nitems;
64 :
65 19 : rdata[0].buffer = InvalidBuffer;
66 19 : rdata[0].data = (char *) &data;
67 19 : rdata[0].len = sizeof(ginxlogCreatePostingTree);
68 19 : rdata[0].next = &rdata[1];
69 :
70 19 : rdata[1].buffer = InvalidBuffer;
71 19 : rdata[1].data = (char *) items;
72 19 : rdata[1].len = sizeof(ItemPointerData) * nitems;
73 19 : rdata[1].next = NULL;
74 :
75 :
76 :
77 19 : recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_CREATE_PTREE, rdata);
78 19 : PageSetLSN(page, recptr);
79 19 : PageSetTLI(page, ThisTimeLineID);
80 :
81 : }
82 :
83 19 : UnlockReleaseBuffer(buffer);
84 :
85 19 : END_CRIT_SECTION();
86 :
87 19 : return blkno;
88 : }
89 :
90 :
91 : /*
92 : * Adds array of item pointers to tuple's posting list or
93 : * creates posting tree and tuple pointed to tree in a case
94 : * of not enough space. Max size of tuple is defined in
95 : * GinFormTuple().
96 : */
97 : static IndexTuple
98 : addItemPointersToTuple(Relation index, GinState *ginstate, GinBtreeStack *stack,
99 : IndexTuple old, ItemPointerData *items, uint32 nitem, bool isBuild)
100 1312 : {
101 : bool isnull;
102 1312 : Datum key = index_getattr(old, FirstOffsetNumber, ginstate->tupdesc, &isnull);
103 1312 : IndexTuple res = GinFormTuple(ginstate, key, NULL, nitem + GinGetNPosting(old));
104 :
105 1312 : if (res)
106 : {
107 : /* good, small enough */
108 1293 : MergeItemPointers(GinGetPosting(res),
109 : GinGetPosting(old), GinGetNPosting(old),
110 : items, nitem
111 : );
112 :
113 1293 : GinSetNPosting(res, nitem + GinGetNPosting(old));
114 : }
115 : else
116 : {
117 : BlockNumber postingRoot;
118 : GinPostingTreeScan *gdi;
119 :
120 : /* posting list becomes big, so we need to make posting's tree */
121 19 : res = GinFormTuple(ginstate, key, NULL, 0);
122 19 : postingRoot = createPostingTree(index, GinGetPosting(old), GinGetNPosting(old));
123 19 : GinSetPostingTree(res, postingRoot);
124 :
125 19 : gdi = prepareScanPostingTree(index, postingRoot, FALSE);
126 19 : gdi->btree.isBuild = isBuild;
127 :
128 19 : insertItemPointer(gdi, items, nitem);
129 :
130 19 : pfree(gdi);
131 : }
132 :
133 1312 : return res;
134 : }
135 :
136 : /*
137 : * Inserts only one entry to the index, but it can add more than 1 ItemPointer.
138 : */
139 : static void
140 : ginEntryInsert(Relation index, GinState *ginstate, Datum value, ItemPointerData *items, uint32 nitem, bool isBuild)
141 1436 : {
142 : GinBtreeData btree;
143 : GinBtreeStack *stack;
144 : IndexTuple itup;
145 : Page page;
146 :
147 1436 : prepareEntryScan(&btree, index, value, ginstate);
148 :
149 1436 : stack = ginFindLeafPage(&btree, NULL);
150 1436 : page = BufferGetPage(stack->buffer);
151 :
152 1436 : if (btree.findItem(&btree, stack))
153 : {
154 : /* found entry */
155 2 : itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, stack->off));
156 :
157 2 : if (GinIsPostingTree(itup))
158 : {
159 : /* lock root of posting tree */
160 : GinPostingTreeScan *gdi;
161 0 : BlockNumber rootPostingTree = GinGetPostingTree(itup);
162 :
163 : /* release all stack */
164 0 : LockBuffer(stack->buffer, GIN_UNLOCK);
165 0 : freeGinBtreeStack(stack);
166 :
167 : /* insert into posting tree */
168 0 : gdi = prepareScanPostingTree(index, rootPostingTree, FALSE);
169 0 : gdi->btree.isBuild = isBuild;
170 0 : insertItemPointer(gdi, items, nitem);
171 :
172 0 : return;
173 : }
174 :
175 2 : itup = addItemPointersToTuple(index, ginstate, stack, itup, items, nitem, isBuild);
176 :
177 2 : btree.isDelete = TRUE;
178 : }
179 : else
180 : {
181 : /* We suppose, that tuple can store at list one itempointer */
182 1434 : itup = GinFormTuple(ginstate, value, items, 1);
183 1434 : if (itup == NULL || IndexTupleSize(itup) >= GinMaxItemSize)
184 0 : elog(ERROR, "huge tuple");
185 :
186 1434 : if (nitem > 1)
187 : {
188 1310 : IndexTuple previtup = itup;
189 :
190 1310 : itup = addItemPointersToTuple(index, ginstate, stack, previtup, items + 1, nitem - 1, isBuild);
191 1310 : pfree(previtup);
192 : }
193 : }
194 :
195 1436 : btree.entry = itup;
196 1436 : ginInsertValue(&btree, stack);
197 1436 : pfree(itup);
198 : }
199 :
200 : /*
201 : * Saves indexed value in memory accumulator during index creation
202 : * Function isn't used during normal insert
203 : */
204 : static uint32
205 : ginHeapTupleBulkInsert(GinBuildState *buildstate, Datum value, ItemPointer heapptr)
206 708 : {
207 : Datum *entries;
208 : int32 nentries;
209 : MemoryContext oldCtx;
210 :
211 1416 : oldCtx = MemoryContextSwitchTo(buildstate->funcCtx);
212 708 : entries = extractEntriesSU(buildstate->accum.ginstate, value, &nentries);
213 : MemoryContextSwitchTo(oldCtx);
214 :
215 708 : if (nentries == 0)
216 : /* nothing to insert */
217 8 : return 0;
218 :
219 700 : ginInsertRecordBA(&buildstate->accum, heapptr, entries, nentries);
220 :
221 700 : MemoryContextReset(buildstate->funcCtx);
222 :
223 700 : return nentries;
224 : }
225 :
226 : static void
227 : ginBuildCallback(Relation index, HeapTuple htup, Datum *values,
228 : bool *isnull, bool tupleIsAlive, void *state)
229 708 : {
230 708 : GinBuildState *buildstate = (GinBuildState *) state;
231 : MemoryContext oldCtx;
232 :
233 708 : if (*isnull)
234 0 : return;
235 :
236 708 : oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
237 :
238 708 : buildstate->indtuples += ginHeapTupleBulkInsert(buildstate, *values, &htup->t_self);
239 :
240 : /* If we've maxed out our available memory, dump everything to the index */
241 708 : if (buildstate->accum.allocatedMemory >= maintenance_work_mem * 1024L)
242 : {
243 : ItemPointerData *list;
244 : Datum entry;
245 : uint32 nlist;
246 :
247 0 : while ((list = ginGetEntry(&buildstate->accum, &entry, &nlist)) != NULL)
248 0 : ginEntryInsert(index, &buildstate->ginstate, entry, list, nlist, TRUE);
249 :
250 0 : MemoryContextReset(buildstate->tmpCtx);
251 0 : ginInitBA(&buildstate->accum);
252 : }
253 :
254 708 : MemoryContextSwitchTo(oldCtx);
255 : }
256 :
257 : Datum
258 : ginbuild(PG_FUNCTION_ARGS)
259 3 : {
260 3 : Relation heap = (Relation) PG_GETARG_POINTER(0);
261 3 : Relation index = (Relation) PG_GETARG_POINTER(1);
262 3 : IndexInfo *indexInfo = (IndexInfo *) PG_GETARG_POINTER(2);
263 : IndexBuildResult *result;
264 : double reltuples;
265 : GinBuildState buildstate;
266 : Buffer buffer;
267 : ItemPointerData *list;
268 : Datum entry;
269 : uint32 nlist;
270 : MemoryContext oldCtx;
271 :
272 3 : if (RelationGetNumberOfBlocks(index) != 0)
273 0 : elog(ERROR, "index \"%s\" already contains data",
274 : RelationGetRelationName(index));
275 :
276 3 : initGinState(&buildstate.ginstate, index);
277 :
278 : /* initialize the root page */
279 3 : buffer = GinNewBuffer(index);
280 3 : START_CRIT_SECTION();
281 3 : GinInitBuffer(buffer, GIN_LEAF);
282 3 : MarkBufferDirty(buffer);
283 :
284 3 : if (!index->rd_istemp)
285 : {
286 : XLogRecPtr recptr;
287 : XLogRecData rdata;
288 : Page page;
289 :
290 3 : rdata.buffer = InvalidBuffer;
291 3 : rdata.data = (char *) &(index->rd_node);
292 3 : rdata.len = sizeof(RelFileNode);
293 3 : rdata.next = NULL;
294 :
295 3 : page = BufferGetPage(buffer);
296 :
297 :
298 3 : recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_CREATE_INDEX, &rdata);
299 3 : PageSetLSN(page, recptr);
300 3 : PageSetTLI(page, ThisTimeLineID);
301 :
302 : }
303 :
304 3 : UnlockReleaseBuffer(buffer);
305 3 : END_CRIT_SECTION();
306 :
307 : /* build the index */
308 3 : buildstate.indtuples = 0;
309 :
310 : /*
311 : * create a temporary memory context that is reset once for each tuple
312 : * inserted into the index
313 : */
314 3 : buildstate.tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
315 : "Gin build temporary context",
316 : ALLOCSET_DEFAULT_MINSIZE,
317 : ALLOCSET_DEFAULT_INITSIZE,
318 : ALLOCSET_DEFAULT_MAXSIZE);
319 :
320 3 : buildstate.funcCtx = AllocSetContextCreate(buildstate.tmpCtx,
321 : "Gin build temporary context for user-defined function",
322 : ALLOCSET_DEFAULT_MINSIZE,
323 : ALLOCSET_DEFAULT_INITSIZE,
324 : ALLOCSET_DEFAULT_MAXSIZE);
325 :
326 3 : buildstate.accum.ginstate = &buildstate.ginstate;
327 3 : ginInitBA(&buildstate.accum);
328 :
329 : /* do the heap scan */
330 3 : reltuples = IndexBuildHeapScan(heap, index, indexInfo,
331 : ginBuildCallback, (void *) &buildstate);
332 :
333 3 : oldCtx = MemoryContextSwitchTo(buildstate.tmpCtx);
334 1436 : while ((list = ginGetEntry(&buildstate.accum, &entry, &nlist)) != NULL)
335 1430 : ginEntryInsert(index, &buildstate.ginstate, entry, list, nlist, TRUE);
336 3 : MemoryContextSwitchTo(oldCtx);
337 :
338 3 : MemoryContextDelete(buildstate.tmpCtx);
339 :
340 : /*
341 : * Return statistics
342 : */
343 3 : result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult));
344 :
345 3 : result->heap_tuples = reltuples;
346 3 : result->index_tuples = buildstate.indtuples;
347 :
348 3 : PG_RETURN_POINTER(result);
349 : }
350 :
351 : /*
352 : * Inserts value during normal insertion
353 : */
354 : static uint32
355 : ginHeapTupleInsert(Relation index, GinState *ginstate, Datum value, ItemPointer item)
356 4 : {
357 : Datum *entries;
358 : int32 i,
359 : nentries;
360 :
361 4 : entries = extractEntriesSU(ginstate, value, &nentries);
362 :
363 4 : if (nentries == 0)
364 : /* nothing to insert */
365 1 : return 0;
366 :
367 9 : for (i = 0; i < nentries; i++)
368 6 : ginEntryInsert(index, ginstate, entries[i], item, 1, FALSE);
369 :
370 3 : return nentries;
371 : }
372 :
373 : Datum
374 : gininsert(PG_FUNCTION_ARGS)
375 4 : {
376 4 : Relation index = (Relation) PG_GETARG_POINTER(0);
377 4 : Datum *values = (Datum *) PG_GETARG_POINTER(1);
378 4 : bool *isnull = (bool *) PG_GETARG_POINTER(2);
379 4 : ItemPointer ht_ctid = (ItemPointer) PG_GETARG_POINTER(3);
380 :
381 : #ifdef NOT_USED
382 : Relation heapRel = (Relation) PG_GETARG_POINTER(4);
383 : bool checkUnique = PG_GETARG_BOOL(5);
384 : #endif
385 : GinState ginstate;
386 : MemoryContext oldCtx;
387 : MemoryContext insertCtx;
388 : uint32 res;
389 :
390 4 : if (*isnull)
391 0 : PG_RETURN_BOOL(false);
392 :
393 4 : insertCtx = AllocSetContextCreate(CurrentMemoryContext,
394 : "Gin insert temporary context",
395 : ALLOCSET_DEFAULT_MINSIZE,
396 : ALLOCSET_DEFAULT_INITSIZE,
397 : ALLOCSET_DEFAULT_MAXSIZE);
398 :
399 4 : oldCtx = MemoryContextSwitchTo(insertCtx);
400 :
401 4 : initGinState(&ginstate, index);
402 :
403 4 : res = ginHeapTupleInsert(index, &ginstate, *values, ht_ctid);
404 :
405 4 : MemoryContextSwitchTo(oldCtx);
406 4 : MemoryContextDelete(insertCtx);
407 :
408 4 : PG_RETURN_BOOL(res > 0);
409 : }
|