Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * blinsert.c
4 : * Bloom index build and insert functions.
5 : *
6 : * Copyright (c) 2016-2025, PostgreSQL Global Development Group
7 : *
8 : * IDENTIFICATION
9 : * contrib/bloom/blinsert.c
10 : *
11 : *-------------------------------------------------------------------------
12 : */
13 : #include "postgres.h"
14 :
15 : #include "access/genam.h"
16 : #include "access/generic_xlog.h"
17 : #include "access/tableam.h"
18 : #include "bloom.h"
19 : #include "miscadmin.h"
20 : #include "nodes/execnodes.h"
21 : #include "storage/bufmgr.h"
22 : #include "utils/memutils.h"
23 : #include "utils/rel.h"
24 :
25 190 : PG_MODULE_MAGIC_EXT(
26 : .name = "bloom",
27 : .version = PG_VERSION
28 : );
29 :
30 : /*
31 : * State of bloom index build. We accumulate one page data here before
32 : * flushing it to buffer manager.
33 : */
34 : typedef struct
35 : {
36 : BloomState blstate; /* bloom index state */
37 : int64 indtuples; /* total number of tuples indexed */
38 : MemoryContext tmpCtx; /* temporary memory context reset after each
39 : * tuple */
40 : PGAlignedBlock data; /* cached page */
41 : int count; /* number of tuples in cached page */
42 : } BloomBuildState;
43 :
44 : /*
45 : * Flush page cached in BloomBuildState.
46 : */
47 : static void
48 72 : flushCachedPage(Relation index, BloomBuildState *buildstate)
49 : {
50 : Page page;
51 72 : Buffer buffer = BloomNewBuffer(index);
52 : GenericXLogState *state;
53 :
54 72 : state = GenericXLogStart(index);
55 72 : page = GenericXLogRegisterBuffer(state, buffer, GENERIC_XLOG_FULL_IMAGE);
56 72 : memcpy(page, buildstate->data.data, BLCKSZ);
57 72 : GenericXLogFinish(state);
58 72 : UnlockReleaseBuffer(buffer);
59 72 : }
60 :
61 : /*
62 : * (Re)initialize cached page in BloomBuildState.
63 : */
64 : static void
65 72 : initCachedPage(BloomBuildState *buildstate)
66 : {
67 72 : BloomInitPage(buildstate->data.data, 0);
68 72 : buildstate->count = 0;
69 72 : }
70 :
71 : /*
72 : * Per-tuple callback for table_index_build_scan.
73 : */
74 : static void
75 37500 : bloomBuildCallback(Relation index, ItemPointer tid, Datum *values,
76 : bool *isnull, bool tupleIsAlive, void *state)
77 : {
78 37500 : BloomBuildState *buildstate = (BloomBuildState *) state;
79 : MemoryContext oldCtx;
80 : BloomTuple *itup;
81 :
82 37500 : oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
83 :
84 37500 : itup = BloomFormTuple(&buildstate->blstate, tid, values, isnull);
85 :
86 : /* Try to add next item to cached page */
87 37500 : if (BloomPageAddItem(&buildstate->blstate, buildstate->data.data, itup))
88 : {
89 : /* Next item was added successfully */
90 37438 : buildstate->count++;
91 : }
92 : else
93 : {
94 : /* Cached page is full, flush it out and make a new one */
95 62 : flushCachedPage(index, buildstate);
96 :
97 62 : CHECK_FOR_INTERRUPTS();
98 :
99 62 : initCachedPage(buildstate);
100 :
101 62 : if (!BloomPageAddItem(&buildstate->blstate, buildstate->data.data, itup))
102 : {
103 : /* We shouldn't be here since we're inserting to the empty page */
104 0 : elog(ERROR, "could not add new bloom tuple to empty page");
105 : }
106 :
107 : /* Next item was added successfully */
108 62 : buildstate->count++;
109 : }
110 :
111 : /* Update total tuple count */
112 37500 : buildstate->indtuples += 1;
113 :
114 37500 : MemoryContextSwitchTo(oldCtx);
115 37500 : MemoryContextReset(buildstate->tmpCtx);
116 37500 : }
117 :
118 : /*
119 : * Build a new bloom index.
120 : */
121 : IndexBuildResult *
122 10 : blbuild(Relation heap, Relation index, IndexInfo *indexInfo)
123 : {
124 : IndexBuildResult *result;
125 : double reltuples;
126 : BloomBuildState buildstate;
127 :
128 10 : if (RelationGetNumberOfBlocks(index) != 0)
129 0 : elog(ERROR, "index \"%s\" already contains data",
130 : RelationGetRelationName(index));
131 :
132 : /* Initialize the meta page */
133 10 : BloomInitMetapage(index, MAIN_FORKNUM);
134 :
135 : /* Initialize the bloom build state */
136 10 : memset(&buildstate, 0, sizeof(buildstate));
137 10 : initBloomState(&buildstate.blstate, index);
138 10 : buildstate.tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
139 : "Bloom build temporary context",
140 : ALLOCSET_DEFAULT_SIZES);
141 10 : initCachedPage(&buildstate);
142 :
143 : /* Do the heap scan */
144 10 : reltuples = table_index_build_scan(heap, index, indexInfo, true, true,
145 : bloomBuildCallback, &buildstate,
146 : NULL);
147 :
148 : /* Flush last page if needed (it will be, unless heap was empty) */
149 10 : if (buildstate.count > 0)
150 10 : flushCachedPage(index, &buildstate);
151 :
152 10 : MemoryContextDelete(buildstate.tmpCtx);
153 :
154 10 : result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult));
155 10 : result->heap_tuples = reltuples;
156 10 : result->index_tuples = buildstate.indtuples;
157 :
158 10 : return result;
159 : }
160 :
161 : /*
162 : * Build an empty bloom index in the initialization fork.
163 : */
164 : void
165 2 : blbuildempty(Relation index)
166 : {
167 : /* Initialize the meta page */
168 2 : BloomInitMetapage(index, INIT_FORKNUM);
169 2 : }
170 :
171 : /*
172 : * Insert new tuple to the bloom index.
173 : */
174 : bool
175 208000 : blinsert(Relation index, Datum *values, bool *isnull,
176 : ItemPointer ht_ctid, Relation heapRel,
177 : IndexUniqueCheck checkUnique,
178 : bool indexUnchanged,
179 : IndexInfo *indexInfo)
180 : {
181 : BloomState blstate;
182 : BloomTuple *itup;
183 : MemoryContext oldCtx;
184 : MemoryContext insertCtx;
185 : BloomMetaPageData *metaData;
186 : Buffer buffer,
187 : metaBuffer;
188 : Page page,
189 : metaPage;
190 208000 : BlockNumber blkno = InvalidBlockNumber;
191 : OffsetNumber nStart;
192 : GenericXLogState *state;
193 :
194 208000 : insertCtx = AllocSetContextCreate(CurrentMemoryContext,
195 : "Bloom insert temporary context",
196 : ALLOCSET_DEFAULT_SIZES);
197 :
198 208000 : oldCtx = MemoryContextSwitchTo(insertCtx);
199 :
200 208000 : initBloomState(&blstate, index);
201 208000 : itup = BloomFormTuple(&blstate, ht_ctid, values, isnull);
202 :
203 : /*
204 : * At first, try to insert new tuple to the first page in notFullPage
205 : * array. If successful, we don't need to modify the meta page.
206 : */
207 208000 : metaBuffer = ReadBuffer(index, BLOOM_METAPAGE_BLKNO);
208 208000 : LockBuffer(metaBuffer, BUFFER_LOCK_SHARE);
209 208000 : metaData = BloomPageGetMeta(BufferGetPage(metaBuffer));
210 :
211 208000 : if (metaData->nEnd > metaData->nStart)
212 : {
213 207998 : blkno = metaData->notFullPage[metaData->nStart];
214 : Assert(blkno != InvalidBlockNumber);
215 :
216 : /* Don't hold metabuffer lock while doing insert */
217 207998 : LockBuffer(metaBuffer, BUFFER_LOCK_UNLOCK);
218 :
219 207998 : buffer = ReadBuffer(index, blkno);
220 207998 : LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
221 :
222 207998 : state = GenericXLogStart(index);
223 207998 : page = GenericXLogRegisterBuffer(state, buffer, 0);
224 :
225 : /*
226 : * We might have found a page that was recently deleted by VACUUM. If
227 : * so, we can reuse it, but we must reinitialize it.
228 : */
229 207998 : if (PageIsNew(page) || BloomPageIsDeleted(page))
230 0 : BloomInitPage(page, 0);
231 :
232 207998 : if (BloomPageAddItem(&blstate, page, itup))
233 : {
234 : /* Success! Apply the change, clean up, and exit */
235 206484 : GenericXLogFinish(state);
236 206484 : UnlockReleaseBuffer(buffer);
237 206484 : ReleaseBuffer(metaBuffer);
238 206484 : MemoryContextSwitchTo(oldCtx);
239 206484 : MemoryContextDelete(insertCtx);
240 206484 : return false;
241 : }
242 :
243 : /* Didn't fit, must try other pages */
244 1514 : GenericXLogAbort(state);
245 1514 : UnlockReleaseBuffer(buffer);
246 : }
247 : else
248 : {
249 : /* No entries in notFullPage */
250 2 : LockBuffer(metaBuffer, BUFFER_LOCK_UNLOCK);
251 : }
252 :
253 : /*
254 : * Try other pages in notFullPage array. We will have to change nStart in
255 : * metapage. Thus, grab exclusive lock on metapage.
256 : */
257 1516 : LockBuffer(metaBuffer, BUFFER_LOCK_EXCLUSIVE);
258 :
259 : /* nStart might have changed while we didn't have lock */
260 1516 : nStart = metaData->nStart;
261 :
262 : /* Skip first page if we already tried it above */
263 1516 : if (nStart < metaData->nEnd &&
264 1514 : blkno == metaData->notFullPage[nStart])
265 1514 : nStart++;
266 :
267 : /*
268 : * This loop iterates for each page we try from the notFullPage array, and
269 : * will also initialize a GenericXLogState for the fallback case of having
270 : * to allocate a new page.
271 : */
272 : for (;;)
273 : {
274 1516 : state = GenericXLogStart(index);
275 :
276 : /* get modifiable copy of metapage */
277 1516 : metaPage = GenericXLogRegisterBuffer(state, metaBuffer, 0);
278 1516 : metaData = BloomPageGetMeta(metaPage);
279 :
280 1516 : if (nStart >= metaData->nEnd)
281 226 : break; /* no more entries in notFullPage array */
282 :
283 1290 : blkno = metaData->notFullPage[nStart];
284 : Assert(blkno != InvalidBlockNumber);
285 :
286 1290 : buffer = ReadBuffer(index, blkno);
287 1290 : LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
288 1290 : page = GenericXLogRegisterBuffer(state, buffer, 0);
289 :
290 : /* Basically same logic as above */
291 1290 : if (PageIsNew(page) || BloomPageIsDeleted(page))
292 0 : BloomInitPage(page, 0);
293 :
294 1290 : if (BloomPageAddItem(&blstate, page, itup))
295 : {
296 : /* Success! Apply the changes, clean up, and exit */
297 1290 : metaData->nStart = nStart;
298 1290 : GenericXLogFinish(state);
299 1290 : UnlockReleaseBuffer(buffer);
300 1290 : UnlockReleaseBuffer(metaBuffer);
301 1290 : MemoryContextSwitchTo(oldCtx);
302 1290 : MemoryContextDelete(insertCtx);
303 1290 : return false;
304 : }
305 :
306 : /* Didn't fit, must try other pages */
307 0 : GenericXLogAbort(state);
308 0 : UnlockReleaseBuffer(buffer);
309 0 : nStart++;
310 : }
311 :
312 : /*
313 : * Didn't find place to insert in notFullPage array. Allocate new page.
314 : * (XXX is it good to do this while holding ex-lock on the metapage??)
315 : */
316 226 : buffer = BloomNewBuffer(index);
317 :
318 226 : page = GenericXLogRegisterBuffer(state, buffer, GENERIC_XLOG_FULL_IMAGE);
319 226 : BloomInitPage(page, 0);
320 :
321 226 : if (!BloomPageAddItem(&blstate, page, itup))
322 : {
323 : /* We shouldn't be here since we're inserting to an empty page */
324 0 : elog(ERROR, "could not add new bloom tuple to empty page");
325 : }
326 :
327 : /* Reset notFullPage array to contain just this new page */
328 226 : metaData->nStart = 0;
329 226 : metaData->nEnd = 1;
330 226 : metaData->notFullPage[0] = BufferGetBlockNumber(buffer);
331 :
332 : /* Apply the changes, clean up, and exit */
333 226 : GenericXLogFinish(state);
334 :
335 226 : UnlockReleaseBuffer(buffer);
336 226 : UnlockReleaseBuffer(metaBuffer);
337 :
338 226 : MemoryContextSwitchTo(oldCtx);
339 226 : MemoryContextDelete(insertCtx);
340 :
341 226 : return false;
342 : }
|