Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * blutils.c
4 : : * Bloom index utilities.
5 : : *
6 : : * Portions Copyright (c) 2016-2024, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1990-1993, Regents of the University of California
8 : : *
9 : : * IDENTIFICATION
10 : : * contrib/bloom/blutils.c
11 : : *
12 : : *-------------------------------------------------------------------------
13 : : */
14 : : #include "postgres.h"
15 : :
16 : : #include "access/amapi.h"
17 : : #include "access/generic_xlog.h"
18 : : #include "access/reloptions.h"
19 : : #include "bloom.h"
20 : : #include "catalog/index.h"
21 : : #include "commands/vacuum.h"
22 : : #include "miscadmin.h"
23 : : #include "storage/bufmgr.h"
24 : : #include "storage/freespace.h"
25 : : #include "storage/indexfsm.h"
26 : : #include "storage/lmgr.h"
27 : : #include "utils/memutils.h"
28 : :
29 : : /* Signature dealing macros - note i is assumed to be of type int */
30 : : #define GETWORD(x,i) ( *( (BloomSignatureWord *)(x) + ( (i) / SIGNWORDBITS ) ) )
31 : : #define CLRBIT(x,i) GETWORD(x,i) &= ~( 0x01 << ( (i) % SIGNWORDBITS ) )
32 : : #define SETBIT(x,i) GETWORD(x,i) |= ( 0x01 << ( (i) % SIGNWORDBITS ) )
33 : : #define GETBIT(x,i) ( (GETWORD(x,i) >> ( (i) % SIGNWORDBITS )) & 0x01 )
34 : :
2935 teodor@sigaev.ru 35 :CBC 97 : PG_FUNCTION_INFO_V1(blhandler);
36 : :
37 : : /* Kind of relation options for bloom index */
38 : : static relopt_kind bl_relopt_kind;
39 : :
40 : : /* parse table for fillRelOptions */
41 : : static relopt_parse_elt bl_relopt_tab[INDEX_MAX_KEYS + 1];
42 : :
43 : : static int32 myRand(void);
44 : : static void mySrand(uint32 seed);
45 : :
46 : : /*
47 : : * Module initialize function: initialize info about Bloom relation options.
48 : : *
49 : : * Note: keep this in sync with makeDefaultBloomOptions().
50 : : */
51 : : void
52 : 95 : _PG_init(void)
53 : : {
54 : : int i;
55 : : char buf[16];
56 : :
57 : 95 : bl_relopt_kind = add_reloption_kind();
58 : :
59 : : /* Option for length of signature */
60 : 95 : add_int_reloption(bl_relopt_kind, "length",
61 : : "Length of signature in bits",
62 : : DEFAULT_BLOOM_LENGTH, 1, MAX_BLOOM_LENGTH,
63 : : AccessExclusiveLock);
2872 tgl@sss.pgh.pa.us 64 : 95 : bl_relopt_tab[0].optname = "length";
65 : 95 : bl_relopt_tab[0].opttype = RELOPT_TYPE_INT;
66 : 95 : bl_relopt_tab[0].offset = offsetof(BloomOptions, bloomLength);
67 : :
68 : : /* Number of bits for each possible index column: col1, col2, ... */
2935 teodor@sigaev.ru 69 [ + + ]: 3135 : for (i = 0; i < INDEX_MAX_KEYS; i++)
70 : : {
2872 tgl@sss.pgh.pa.us 71 : 3040 : snprintf(buf, sizeof(buf), "col%d", i + 1);
2935 teodor@sigaev.ru 72 : 3040 : add_int_reloption(bl_relopt_kind, buf,
73 : : "Number of bits generated for each index column",
74 : : DEFAULT_BLOOM_BITS, 1, MAX_BLOOM_BITS,
75 : : AccessExclusiveLock);
2872 tgl@sss.pgh.pa.us 76 : 3040 : bl_relopt_tab[i + 1].optname = MemoryContextStrdup(TopMemoryContext,
77 : : buf);
78 : 3040 : bl_relopt_tab[i + 1].opttype = RELOPT_TYPE_INT;
2489 79 : 3040 : bl_relopt_tab[i + 1].offset = offsetof(BloomOptions, bitSize[0]) + sizeof(int) * i;
80 : : }
2935 teodor@sigaev.ru 81 : 95 : }
82 : :
83 : : /*
84 : : * Construct a default set of Bloom options.
85 : : */
86 : : static BloomOptions *
2872 tgl@sss.pgh.pa.us 87 :UBC 0 : makeDefaultBloomOptions(void)
88 : : {
89 : : BloomOptions *opts;
90 : : int i;
91 : :
92 : 0 : opts = (BloomOptions *) palloc0(sizeof(BloomOptions));
93 : : /* Convert DEFAULT_BLOOM_LENGTH from # of bits to # of words */
94 : 0 : opts->bloomLength = (DEFAULT_BLOOM_LENGTH + SIGNWORDBITS - 1) / SIGNWORDBITS;
95 [ # # ]: 0 : for (i = 0; i < INDEX_MAX_KEYS; i++)
96 : 0 : opts->bitSize[i] = DEFAULT_BLOOM_BITS;
97 : 0 : SET_VARSIZE(opts, sizeof(BloomOptions));
98 : 0 : return opts;
99 : : }
100 : :
101 : : /*
102 : : * Bloom handler function: return IndexAmRoutine with access method parameters
103 : : * and callbacks.
104 : : */
105 : : Datum
2935 teodor@sigaev.ru 106 :CBC 118 : blhandler(PG_FUNCTION_ARGS)
107 : : {
108 : 118 : IndexAmRoutine *amroutine = makeNode(IndexAmRoutine);
109 : :
2908 110 : 118 : amroutine->amstrategies = BLOOM_NSTRATEGIES;
111 : 118 : amroutine->amsupport = BLOOM_NPROC;
1476 akorotkov@postgresql 112 : 118 : amroutine->amoptsprocnum = BLOOM_OPTIONS_PROC;
2935 teodor@sigaev.ru 113 : 118 : amroutine->amcanorder = false;
114 : 118 : amroutine->amcanorderbyop = false;
115 : 118 : amroutine->amcanbackward = false;
116 : 118 : amroutine->amcanunique = false;
117 : 118 : amroutine->amcanmulticol = true;
118 : 118 : amroutine->amoptionalkey = true;
119 : 118 : amroutine->amsearcharray = false;
120 : 118 : amroutine->amsearchnulls = false;
121 : 118 : amroutine->amstorage = false;
122 : 118 : amroutine->amclusterable = false;
123 : 118 : amroutine->ampredlocks = false;
2615 rhaas@postgresql.org 124 : 118 : amroutine->amcanparallel = false;
128 tomas.vondra@postgre 125 :GNC 118 : amroutine->amcanbuildparallel = false;
2199 teodor@sigaev.ru 126 :CBC 118 : amroutine->amcaninclude = false;
1551 akapila@postgresql.o 127 : 118 : amroutine->amusemaintenanceworkmem = false;
128 : 118 : amroutine->amparallelvacuumoptions =
129 : : VACUUM_OPTION_PARALLEL_BULKDEL | VACUUM_OPTION_PARALLEL_CLEANUP;
2803 tgl@sss.pgh.pa.us 130 : 118 : amroutine->amkeytype = InvalidOid;
131 : :
2935 teodor@sigaev.ru 132 : 118 : amroutine->ambuild = blbuild;
133 : 118 : amroutine->ambuildempty = blbuildempty;
2803 tgl@sss.pgh.pa.us 134 : 118 : amroutine->aminsert = blinsert;
141 tomas.vondra@postgre 135 :GNC 118 : amroutine->aminsertcleanup = NULL;
2935 teodor@sigaev.ru 136 :CBC 118 : amroutine->ambulkdelete = blbulkdelete;
137 : 118 : amroutine->amvacuumcleanup = blvacuumcleanup;
138 : 118 : amroutine->amcanreturn = NULL;
139 : 118 : amroutine->amcostestimate = blcostestimate;
140 : 118 : amroutine->amoptions = bloptions;
2801 tgl@sss.pgh.pa.us 141 : 118 : amroutine->amproperty = NULL;
1839 alvherre@alvh.no-ip. 142 : 118 : amroutine->ambuildphasename = NULL;
2935 teodor@sigaev.ru 143 : 118 : amroutine->amvalidate = blvalidate;
1352 tgl@sss.pgh.pa.us 144 : 118 : amroutine->amadjustmembers = NULL;
2803 145 : 118 : amroutine->ambeginscan = blbeginscan;
146 : 118 : amroutine->amrescan = blrescan;
147 : 118 : amroutine->amgettuple = NULL;
148 : 118 : amroutine->amgetbitmap = blgetbitmap;
149 : 118 : amroutine->amendscan = blendscan;
150 : 118 : amroutine->ammarkpos = NULL;
151 : 118 : amroutine->amrestrpos = NULL;
2637 rhaas@postgresql.org 152 : 118 : amroutine->amestimateparallelscan = NULL;
153 : 118 : amroutine->aminitparallelscan = NULL;
154 : 118 : amroutine->amparallelrescan = NULL;
155 : :
2935 teodor@sigaev.ru 156 : 118 : PG_RETURN_POINTER(amroutine);
157 : : }
158 : :
159 : : /*
160 : : * Fill BloomState structure for particular index.
161 : : */
162 : : void
163 : 104403 : initBloomState(BloomState *state, Relation index)
164 : : {
165 : : int i;
166 : :
167 : 104403 : state->nColumns = index->rd_att->natts;
168 : :
169 : : /* Initialize hash function for each attribute */
170 [ + + ]: 313209 : for (i = 0; i < index->rd_att->natts; i++)
171 : : {
172 : 208806 : fmgr_info_copy(&(state->hashFn[i]),
173 : 208806 : index_getprocinfo(index, i + 1, BLOOM_HASH_PROC),
174 : : CurrentMemoryContext);
1850 peter@eisentraut.org 175 : 208806 : state->collations[i] = index->rd_indcollation[i];
176 : : }
177 : :
178 : : /* Initialize amcache if needed with options from metapage */
2935 teodor@sigaev.ru 179 [ + + ]: 104403 : if (!index->rd_amcache)
180 : : {
181 : : Buffer buffer;
182 : : Page page;
183 : : BloomMetaPageData *meta;
184 : : BloomOptions *opts;
185 : :
186 : 91 : opts = MemoryContextAlloc(index->rd_indexcxt, sizeof(BloomOptions));
187 : :
188 : 91 : buffer = ReadBuffer(index, BLOOM_METAPAGE_BLKNO);
189 : 91 : LockBuffer(buffer, BUFFER_LOCK_SHARE);
190 : :
2916 kgrittn@postgresql.o 191 : 91 : page = BufferGetPage(buffer);
192 : :
2935 teodor@sigaev.ru 193 [ - + ]: 91 : if (!BloomPageIsMeta(page))
2935 teodor@sigaev.ru 194 [ # # ]:UBC 0 : elog(ERROR, "Relation is not a bloom index");
2916 kgrittn@postgresql.o 195 :CBC 91 : meta = BloomPageGetMeta(BufferGetPage(buffer));
196 : :
2935 teodor@sigaev.ru 197 [ - + ]: 91 : if (meta->magickNumber != BLOOM_MAGICK_NUMBER)
2935 teodor@sigaev.ru 198 [ # # ]:UBC 0 : elog(ERROR, "Relation is not a bloom index");
199 : :
2935 teodor@sigaev.ru 200 :CBC 91 : *opts = meta->opts;
201 : :
202 : 91 : UnlockReleaseBuffer(buffer);
203 : :
204 : 91 : index->rd_amcache = (void *) opts;
205 : : }
206 : :
2933 tgl@sss.pgh.pa.us 207 : 104403 : memcpy(&state->opts, index->rd_amcache, sizeof(state->opts));
2935 teodor@sigaev.ru 208 : 104403 : state->sizeOfBloomTuple = BLOOMTUPLEHDRSZ +
2872 tgl@sss.pgh.pa.us 209 : 104403 : sizeof(BloomSignatureWord) * state->opts.bloomLength;
2935 teodor@sigaev.ru 210 : 104403 : }
211 : :
212 : : /*
213 : : * Random generator copied from FreeBSD. Using own random generator here for
214 : : * two reasons:
215 : : *
216 : : * 1) In this case random numbers are used for on-disk storage. Usage of
217 : : * PostgreSQL number generator would obstruct it from all possible changes.
218 : : * 2) Changing seed of PostgreSQL random generator would be undesirable side
219 : : * effect.
220 : : */
221 : : static int32 next;
222 : :
223 : : static int32
2924 tgl@sss.pgh.pa.us 224 : 865433 : myRand(void)
225 : : {
226 : : /*----------
227 : : * Compute x = (7^5 * x) mod (2^31 - 1)
228 : : * without overflowing 31 bits:
229 : : * (2^31 - 1) = 127773 * (7^5) + 2836
230 : : * From "Random number generators: good ones are hard to find",
231 : : * Park and Miller, Communications of the ACM, vol. 31, no. 10,
232 : : * October 1988, p. 1195.
233 : : *----------
234 : : */
235 : : int32 hi,
236 : : lo,
237 : : x;
238 : :
239 : : /* Must be in [1, 0x7ffffffe] range at this point. */
2935 teodor@sigaev.ru 240 : 865433 : hi = next / 127773;
241 : 865433 : lo = next % 127773;
242 : 865433 : x = 16807 * lo - 2836 * hi;
243 [ + + ]: 865433 : if (x < 0)
244 : 175256 : x += 0x7fffffff;
245 : 865433 : next = x;
246 : : /* Transform to [0, 0x7ffffffd] range. */
247 : 865433 : return (x - 1);
248 : : }
249 : :
250 : : static void
251 : 492032 : mySrand(uint32 seed)
252 : : {
253 : 492032 : next = seed;
254 : : /* Transform to [1, 0x7ffffffe] range. */
255 : 492032 : next = (next % 0x7ffffffe) + 1;
256 : 492032 : }
257 : :
258 : : /*
259 : : * Add bits of given value to the signature.
260 : : */
261 : : void
2872 tgl@sss.pgh.pa.us 262 : 246016 : signValue(BloomState *state, BloomSignatureWord *sign, Datum value, int attno)
263 : : {
264 : : uint32 hashVal;
265 : : int nBit,
266 : : j;
267 : :
268 : : /*
269 : : * init generator with "column's" number to get "hashed" seed for new
270 : : * value. We don't want to map the same numbers from different columns
271 : : * into the same bits!
272 : : */
2935 teodor@sigaev.ru 273 : 246016 : mySrand(attno);
274 : :
275 : : /*
276 : : * Init hash sequence to map our value into bits. the same values in
277 : : * different columns will be mapped into different bits because of step
278 : : * above
279 : : */
1850 peter@eisentraut.org 280 : 246016 : hashVal = DatumGetInt32(FunctionCall1Coll(&state->hashFn[attno], state->collations[attno], value));
2935 teodor@sigaev.ru 281 : 246016 : mySrand(hashVal ^ myRand());
282 : :
2933 tgl@sss.pgh.pa.us 283 [ + + ]: 865433 : for (j = 0; j < state->opts.bitSize[attno]; j++)
284 : : {
285 : : /* prevent multiple evaluation in SETBIT macro */
2872 286 : 619417 : nBit = myRand() % (state->opts.bloomLength * SIGNWORDBITS);
2935 teodor@sigaev.ru 287 : 619417 : SETBIT(sign, nBit);
288 : : }
289 : 246016 : }
290 : :
291 : : /*
292 : : * Make bloom tuple from values.
293 : : */
294 : : BloomTuple *
295 : 122750 : BloomFormTuple(BloomState *state, ItemPointer iptr, Datum *values, bool *isnull)
296 : : {
297 : : int i;
298 : 122750 : BloomTuple *res = (BloomTuple *) palloc0(state->sizeOfBloomTuple);
299 : :
300 : 122750 : res->heapPtr = *iptr;
301 : :
302 : : /* Blooming each column */
303 [ + + ]: 368250 : for (i = 0; i < state->nColumns; i++)
304 : : {
305 : : /* skip nulls */
306 [ - + ]: 245500 : if (isnull[i])
2935 teodor@sigaev.ru 307 :UBC 0 : continue;
308 : :
2935 teodor@sigaev.ru 309 :CBC 245500 : signValue(state, res->sign, values[i], i);
310 : : }
311 : :
312 : 122750 : return res;
313 : : }
314 : :
315 : : /*
316 : : * Add new bloom tuple to the page. Returns true if new tuple was successfully
317 : : * added to the page. Returns false if it doesn't fit on the page.
318 : : */
319 : : bool
320 : 123538 : BloomPageAddItem(BloomState *state, Page page, BloomTuple *tuple)
321 : : {
322 : : BloomTuple *itup;
323 : : BloomPageOpaque opaque;
324 : : Pointer ptr;
325 : :
326 : : /* We shouldn't be pointed to an invalid page */
2801 tgl@sss.pgh.pa.us 327 [ + - - + ]: 123538 : Assert(!PageIsNew(page) && !BloomPageIsDeleted(page));
328 : :
329 : : /* Does new tuple fit on the page? */
2935 teodor@sigaev.ru 330 [ + + ]: 123538 : if (BloomPageGetFreeSpace(state, page) < state->sizeOfBloomTuple)
331 : 788 : return false;
332 : :
333 : : /* Copy new tuple to the end of page */
334 : 122750 : opaque = BloomPageGetOpaque(page);
335 : 122750 : itup = BloomPageGetTuple(state, page, opaque->maxoff + 1);
336 : 122750 : memcpy((Pointer) itup, (Pointer) tuple, state->sizeOfBloomTuple);
337 : :
338 : : /* Adjust maxoff and pd_lower */
339 : 122750 : opaque->maxoff++;
340 : 122750 : ptr = (Pointer) BloomPageGetTuple(state, page, opaque->maxoff + 1);
341 : 122750 : ((PageHeader) page)->pd_lower = ptr - page;
342 : :
343 : : /* Assert we didn't overrun available space */
2801 tgl@sss.pgh.pa.us 344 [ - + ]: 122750 : Assert(((PageHeader) page)->pd_lower <= ((PageHeader) page)->pd_upper);
345 : :
2935 teodor@sigaev.ru 346 : 122750 : return true;
347 : : }
348 : :
349 : : /*
350 : : * Allocate a new page (either by recycling, or by extending the index file)
351 : : * The returned buffer is already pinned and exclusive-locked
352 : : * Caller is responsible for initializing the page by calling BloomInitPage
353 : : */
354 : : Buffer
355 : 149 : BloomNewBuffer(Relation index)
356 : : {
357 : : Buffer buffer;
358 : :
359 : : /* First, try to get a page from FSM */
360 : : for (;;)
2935 teodor@sigaev.ru 361 :UBC 0 : {
2935 teodor@sigaev.ru 362 :CBC 149 : BlockNumber blkno = GetFreeIndexPage(index);
363 : :
364 [ + + ]: 149 : if (blkno == InvalidBlockNumber)
365 : 148 : break;
366 : :
367 : 1 : buffer = ReadBuffer(index, blkno);
368 : :
369 : : /*
370 : : * We have to guard against the possibility that someone else already
371 : : * recycled this page; the buffer may be locked if so.
372 : : */
373 [ + - ]: 1 : if (ConditionalLockBuffer(buffer))
374 : : {
2916 kgrittn@postgresql.o 375 : 1 : Page page = BufferGetPage(buffer);
376 : :
2935 teodor@sigaev.ru 377 [ - + ]: 1 : if (PageIsNew(page))
2935 teodor@sigaev.ru 378 :UBC 0 : return buffer; /* OK to use, if never initialized */
379 : :
2935 teodor@sigaev.ru 380 [ + - ]:CBC 1 : if (BloomPageIsDeleted(page))
381 : 1 : return buffer; /* OK to use */
382 : :
2935 teodor@sigaev.ru 383 :UBC 0 : LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
384 : : }
385 : :
386 : : /* Can't use it, so release buffer and try again */
387 : 0 : ReleaseBuffer(buffer);
388 : : }
389 : :
390 : : /* Must extend the file */
235 tmunro@postgresql.or 391 :CBC 148 : buffer = ExtendBufferedRel(BMR_REL(index), MAIN_FORKNUM, NULL,
392 : : EB_LOCK_FIRST);
393 : :
2935 teodor@sigaev.ru 394 : 148 : return buffer;
395 : : }
396 : :
397 : : /*
398 : : * Initialize any page of a bloom index.
399 : : */
400 : : void
401 : 155 : BloomInitPage(Page page, uint16 flags)
402 : : {
403 : : BloomPageOpaque opaque;
404 : :
405 : 155 : PageInit(page, BLCKSZ, sizeof(BloomPageOpaqueData));
406 : :
407 : 155 : opaque = BloomPageGetOpaque(page);
408 : 155 : opaque->flags = flags;
2924 409 : 155 : opaque->bloom_page_id = BLOOM_PAGE_ID;
2935 410 : 155 : }
411 : :
412 : : /*
413 : : * Fill in metapage for bloom index.
414 : : */
415 : : void
2882 tgl@sss.pgh.pa.us 416 : 6 : BloomFillMetapage(Relation index, Page metaPage)
417 : : {
418 : : BloomOptions *opts;
419 : : BloomMetaPageData *metadata;
420 : :
421 : : /*
422 : : * Choose the index's options. If reloptions have been assigned, use
423 : : * those, otherwise create default options.
424 : : */
425 : 6 : opts = (BloomOptions *) index->rd_options;
426 [ - + ]: 6 : if (!opts)
2872 tgl@sss.pgh.pa.us 427 :UBC 0 : opts = makeDefaultBloomOptions();
428 : :
429 : : /*
430 : : * Initialize contents of meta page, including a copy of the options,
431 : : * which are now frozen for the life of the index.
432 : : */
2882 tgl@sss.pgh.pa.us 433 :CBC 6 : BloomInitPage(metaPage, BLOOM_META);
434 : 6 : metadata = BloomPageGetMeta(metaPage);
435 : 6 : memset(metadata, 0, sizeof(BloomMetaPageData));
436 : 6 : metadata->magickNumber = BLOOM_MAGICK_NUMBER;
437 : 6 : metadata->opts = *opts;
438 : 6 : ((PageHeader) metaPage)->pd_lower += sizeof(BloomMetaPageData);
439 : :
440 : : /* If this fails, probably FreeBlockNumberArray size calc is wrong: */
2801 441 [ - + ]: 6 : Assert(((PageHeader) metaPage)->pd_lower <= ((PageHeader) metaPage)->pd_upper);
2882 442 : 6 : }
443 : :
444 : : /*
445 : : * Initialize metapage for bloom index.
446 : : */
447 : : void
235 heikki.linnakangas@i 448 : 6 : BloomInitMetapage(Relation index, ForkNumber forknum)
449 : : {
450 : : Buffer metaBuffer;
451 : : Page metaPage;
452 : : GenericXLogState *state;
453 : :
454 : : /*
455 : : * Make a new page; since it is first page it should be associated with
456 : : * block number 0 (BLOOM_METAPAGE_BLKNO). No need to hold the extension
457 : : * lock because there cannot be concurrent inserters yet.
458 : : */
459 : 6 : metaBuffer = ReadBufferExtended(index, forknum, P_NEW, RBM_NORMAL, NULL);
460 : 6 : LockBuffer(metaBuffer, BUFFER_LOCK_EXCLUSIVE);
2935 teodor@sigaev.ru 461 [ - + ]: 6 : Assert(BufferGetBlockNumber(metaBuffer) == BLOOM_METAPAGE_BLKNO);
462 : :
463 : : /* Initialize contents of meta page */
464 : 6 : state = GenericXLogStart(index);
2882 tgl@sss.pgh.pa.us 465 : 6 : metaPage = GenericXLogRegisterBuffer(state, metaBuffer,
466 : : GENERIC_XLOG_FULL_IMAGE);
467 : 6 : BloomFillMetapage(index, metaPage);
2935 teodor@sigaev.ru 468 : 6 : GenericXLogFinish(state);
469 : :
470 : 6 : UnlockReleaseBuffer(metaBuffer);
471 : 6 : }
472 : :
473 : : /*
474 : : * Parse reloptions for bloom index, producing a BloomOptions struct.
475 : : */
476 : : bytea *
477 : 116 : bloptions(Datum reloptions, bool validate)
478 : : {
479 : : BloomOptions *rdopts;
480 : :
481 : : /* Parse the user-given reloptions */
1622 michael@paquier.xyz 482 : 116 : rdopts = (BloomOptions *) build_reloptions(reloptions, validate,
483 : : bl_relopt_kind,
484 : : sizeof(BloomOptions),
485 : : bl_relopt_tab,
486 : : lengthof(bl_relopt_tab));
487 : :
488 : : /* Convert signature length from # of bits to # to words, rounding up */
489 [ + - ]: 114 : if (rdopts)
490 : 114 : rdopts->bloomLength = (rdopts->bloomLength + SIGNWORDBITS - 1) / SIGNWORDBITS;
491 : :
2935 teodor@sigaev.ru 492 : 114 : return (bytea *) rdopts;
493 : : }
|