Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * ginget.c
4 : : * fetch tuples from a GIN scan.
5 : : *
6 : : *
7 : : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
8 : : * Portions Copyright (c) 1994, Regents of the University of California
9 : : *
10 : : * IDENTIFICATION
11 : : * src/backend/access/gin/ginget.c
12 : : *-------------------------------------------------------------------------
13 : : */
14 : :
15 : : #include "postgres.h"
16 : :
17 : : #include "access/gin_private.h"
18 : : #include "access/relscan.h"
19 : : #include "common/pg_prng.h"
20 : : #include "miscadmin.h"
21 : : #include "storage/predicate.h"
22 : : #include "utils/datum.h"
23 : : #include "utils/memutils.h"
24 : : #include "utils/rel.h"
25 : :
26 : : /* GUC parameter */
27 : : int GinFuzzySearchLimit = 0;
28 : :
29 : : typedef struct pendingPosition
30 : : {
31 : : Buffer pendingBuffer;
32 : : OffsetNumber firstOffset;
33 : : OffsetNumber lastOffset;
34 : : ItemPointerData item;
35 : : bool *hasMatchKey;
36 : : } pendingPosition;
37 : :
38 : :
39 : : /*
40 : : * Goes to the next page if current offset is outside of bounds
41 : : */
42 : : static bool
2717 teodor@sigaev.ru 43 :CBC 204546 : moveRightIfItNeeded(GinBtreeData *btree, GinBtreeStack *stack, Snapshot snapshot)
44 : : {
3426 kgrittn@postgresql.o 45 : 204546 : Page page = BufferGetPage(stack->buffer);
46 : :
5931 bruce@momjian.us 47 [ + + ]: 204546 : if (stack->off > PageGetMaxOffsetNumber(page))
48 : : {
49 : : /*
50 : : * We scanned the whole page, so we should take right page
51 : : */
52 [ + + ]: 1976 : if (GinPageRightMost(page))
53 : 264 : return false; /* no more pages */
54 : :
4320 heikki.linnakangas@i 55 : 1712 : stack->buffer = ginStepRight(stack->buffer, btree->index, GIN_SHARE);
56 : 1712 : stack->blkno = BufferGetBlockNumber(stack->buffer);
6322 tgl@sss.pgh.pa.us 57 : 1712 : stack->off = FirstOffsetNumber;
2682 teodor@sigaev.ru 58 : 1712 : PredicateLockPage(btree->index, stack->blkno, snapshot);
59 : : }
60 : :
6322 tgl@sss.pgh.pa.us 61 : 204282 : return true;
62 : : }
63 : :
64 : : /*
65 : : * Scan all pages of a posting tree and save all its heap ItemPointers
66 : : * in scanEntry->matchBitmap
67 : : */
68 : : static void
5356 69 : 6 : scanPostingTree(Relation index, GinScanEntry scanEntry,
70 : : BlockNumber rootPostingTree)
71 : : {
72 : : GinBtreeData btree;
73 : : GinBtreeStack *stack;
74 : : Buffer buffer;
75 : : Page page;
76 : :
77 : : /* Descend to the leftmost leaf page */
729 tmunro@postgresql.or 78 : 6 : stack = ginScanBeginPostingTree(&btree, index, rootPostingTree);
4308 heikki.linnakangas@i 79 : 6 : buffer = stack->buffer;
80 : :
6322 tgl@sss.pgh.pa.us 81 : 6 : IncrBufferRefCount(buffer); /* prevent unpin in freeGinBtreeStack */
82 : :
4308 heikki.linnakangas@i 83 : 6 : freeGinBtreeStack(stack);
84 : :
85 : : /*
86 : : * Loop iterates through all leaf pages of posting tree
87 : : */
88 : : for (;;)
89 : : {
3426 kgrittn@postgresql.o 90 : 15 : page = BufferGetPage(buffer);
4245 heikki.linnakangas@i 91 [ + - ]: 15 : if ((GinPageGetOpaque(page)->flags & GIN_DELETED) == 0)
92 : : {
4141 bruce@momjian.us 93 : 15 : int n = GinDataLeafPageGetItemsToTbm(page, scanEntry->matchBitmap);
94 : :
4245 heikki.linnakangas@i 95 : 15 : scanEntry->predictNumberResult += n;
96 : : }
97 : :
5931 bruce@momjian.us 98 [ + + ]: 15 : if (GinPageRightMost(page))
5356 tgl@sss.pgh.pa.us 99 : 6 : break; /* no more pages */
100 : :
4320 heikki.linnakangas@i 101 : 9 : buffer = ginStepRight(buffer, index, GIN_SHARE);
102 : : }
103 : :
5356 tgl@sss.pgh.pa.us 104 : 6 : UnlockReleaseBuffer(buffer);
6322 105 : 6 : }
106 : :
107 : : /*
108 : : * Collects TIDs into scanEntry->matchBitmap for all heap tuples that
109 : : * match the search entry. This supports three different match modes:
110 : : *
111 : : * 1. Partial-match support: scan from current point until the
112 : : * comparePartialFn says we're done.
113 : : * 2. SEARCH_MODE_ALL: scan from current point (which should be first
114 : : * key for the current attnum) until we hit null items or end of attnum
115 : : * 3. SEARCH_MODE_EVERYTHING: scan from current point (which should be first
116 : : * key for the current attnum) until we hit end of attnum
117 : : *
118 : : * Returns true if done, false if it's necessary to restart scan from scratch
119 : : */
120 : : static bool
5356 121 : 440 : collectMatchBitmap(GinBtreeData *btree, GinBtreeStack *stack,
122 : : GinScanEntry scanEntry, Snapshot snapshot)
123 : : {
124 : : OffsetNumber attnum;
125 : : CompactAttribute *attr;
126 : :
127 : : /* Initialize empty bitmap result */
218 128 : 440 : scanEntry->matchBitmap = tbm_create(work_mem * (Size) 1024, NULL);
129 : :
130 : : /* Null query cannot partial-match anything */
5356 131 [ + + ]: 440 : if (scanEntry->isPartialMatch &&
132 [ - + ]: 299 : scanEntry->queryCategory != GIN_CAT_NORM_KEY)
5356 tgl@sss.pgh.pa.us 133 :UBC 0 : return true;
134 : :
135 : : /* Locate tupdesc entry for key column (for attbyval/attlen data) */
5356 tgl@sss.pgh.pa.us 136 :CBC 440 : attnum = scanEntry->attnum;
260 drowley@postgresql.o 137 : 440 : attr = TupleDescCompactAttr(btree->ginstate->origTupdesc, attnum - 1);
138 : :
139 : : /*
140 : : * Predicate lock entry leaf page, following pages will be locked by
141 : : * moveRightIfItNeeded()
142 : : */
796 tmunro@postgresql.or 143 : 440 : PredicateLockPage(btree->index,
144 : : BufferGetBlockNumber(stack->buffer),
145 : : snapshot);
146 : :
147 : : for (;;)
6322 tgl@sss.pgh.pa.us 148 : 204100 : {
149 : : Page page;
150 : : IndexTuple itup;
151 : : Datum idatum;
152 : : GinNullCategory icategory;
153 : :
154 : : /*
155 : : * stack->off points to the interested entry, buffer is already locked
156 : : */
2717 teodor@sigaev.ru 157 [ + + ]: 204540 : if (moveRightIfItNeeded(btree, stack, snapshot) == false)
6322 tgl@sss.pgh.pa.us 158 : 440 : return true;
159 : :
3426 kgrittn@postgresql.o 160 : 204276 : page = BufferGetPage(stack->buffer);
6322 tgl@sss.pgh.pa.us 161 : 204276 : itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, stack->off));
162 : :
163 : : /*
164 : : * If tuple stores another attribute then stop scan
165 : : */
5356 166 [ - + ]: 204276 : if (gintuple_get_attrnum(btree->ginstate, itup) != attnum)
6266 tgl@sss.pgh.pa.us 167 :UBC 0 : return true;
168 : :
169 : : /* Safe to fetch attribute value */
5356 tgl@sss.pgh.pa.us 170 :CBC 204276 : idatum = gintuple_get_key(btree->ginstate, itup, &icategory);
171 : :
172 : : /*
173 : : * Check for appropriate scan stop conditions
174 : : */
175 [ + + ]: 204276 : if (scanEntry->isPartialMatch)
176 : : {
177 : : int32 cmp;
178 : :
179 : : /*
180 : : * In partial match, stop scan at any null (including
181 : : * placeholders); partial matches never match nulls
182 : : */
183 [ + + ]: 1321 : if (icategory != GIN_CAT_NORM_KEY)
184 : 5 : return true;
185 : :
186 : : /*----------
187 : : * Check of partial match.
188 : : * case cmp == 0 => match
189 : : * case cmp > 0 => not match and finish scan
190 : : * case cmp < 0 => not match and continue scan
191 : : *----------
192 : : */
5261 193 : 1316 : cmp = DatumGetInt32(FunctionCall4Coll(&btree->ginstate->comparePartialFn[attnum - 1],
2999 194 : 1316 : btree->ginstate->supportCollation[attnum - 1],
195 : : scanEntry->queryKey,
196 : : idatum,
197 : 1316 : UInt16GetDatum(scanEntry->strategy),
198 : 1316 : PointerGetDatum(scanEntry->extra_data)));
199 : :
5356 200 [ + + ]: 1316 : if (cmp > 0)
201 : 157 : return true;
202 [ + + ]: 1159 : else if (cmp < 0)
203 : : {
204 : 64 : stack->off++;
205 : 64 : continue;
206 : : }
207 : : }
208 [ + - ]: 202955 : else if (scanEntry->searchMode == GIN_SEARCH_MODE_ALL)
209 : : {
210 : : /*
211 : : * In ALL mode, we are not interested in null items, so we can
212 : : * stop if we get to a null-item placeholder (which will be the
213 : : * last entry for a given attnum). We do want to include NULL_KEY
214 : : * and EMPTY_ITEM entries, though.
215 : : */
216 [ + + ]: 202955 : if (icategory == GIN_CAT_NULL_ITEM)
217 : 14 : return true;
218 : : }
219 : :
220 : : /*
221 : : * OK, we want to return the TIDs listed in this entry.
222 : : */
5931 bruce@momjian.us 223 [ + + ]: 204036 : if (GinIsPostingTree(itup))
224 : : {
6322 tgl@sss.pgh.pa.us 225 : 6 : BlockNumber rootPostingTree = GinGetPostingTree(itup);
226 : :
227 : : /*
228 : : * We should unlock current page (but not unpin) during tree scan
229 : : * to prevent deadlock with vacuum processes.
230 : : *
231 : : * We save current entry value (idatum) to be able to re-find our
232 : : * tuple after re-locking
233 : : */
5356 234 [ + - ]: 6 : if (icategory == GIN_CAT_NORM_KEY)
235 : 6 : idatum = datumCopy(idatum, attr->attbyval, attr->attlen);
236 : :
6322 237 : 6 : LockBuffer(stack->buffer, GIN_UNLOCK);
238 : :
239 : : /*
240 : : * Acquire predicate lock on the posting tree. We already hold a
241 : : * lock on the entry page, but insertions to the posting tree
242 : : * don't check for conflicts on that level.
243 : : */
2682 teodor@sigaev.ru 244 : 6 : PredicateLockPage(btree->index, rootPostingTree, snapshot);
245 : :
246 : : /* Collect all the TIDs in this entry's posting tree */
729 tmunro@postgresql.or 247 : 6 : scanPostingTree(btree->index, scanEntry, rootPostingTree);
248 : :
249 : : /*
250 : : * We lock again the entry page and while it was unlocked insert
251 : : * might have occurred, so we need to re-find our position.
252 : : */
6322 tgl@sss.pgh.pa.us 253 : 6 : LockBuffer(stack->buffer, GIN_SHARE);
3426 kgrittn@postgresql.o 254 : 6 : page = BufferGetPage(stack->buffer);
5931 bruce@momjian.us 255 [ + - ]: 6 : if (!GinPageIsLeaf(page))
256 : : {
257 : : /*
258 : : * Root page becomes non-leaf while we unlock it. We will
259 : : * start again, this situation doesn't occur often - root can
260 : : * became a non-leaf only once per life of index.
261 : : */
6322 tgl@sss.pgh.pa.us 262 :UBC 0 : return false;
263 : : }
264 : :
265 : : /* Search forward to re-find idatum */
266 : : for (;;)
267 : : {
2717 teodor@sigaev.ru 268 [ - + ]:CBC 6 : if (moveRightIfItNeeded(btree, stack, snapshot) == false)
1836 tgl@sss.pgh.pa.us 269 [ # # ]:UBC 0 : ereport(ERROR,
270 : : (errcode(ERRCODE_INTERNAL_ERROR),
271 : : errmsg("failed to re-find tuple within index \"%s\"",
272 : : RelationGetRelationName(btree->index))));
273 : :
3426 kgrittn@postgresql.o 274 :CBC 6 : page = BufferGetPage(stack->buffer);
6322 tgl@sss.pgh.pa.us 275 : 6 : itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, stack->off));
276 : :
1836 277 [ + - ]: 6 : if (gintuple_get_attrnum(btree->ginstate, itup) == attnum)
278 : : {
279 : : Datum newDatum;
280 : : GinNullCategory newCategory;
281 : :
282 : 6 : newDatum = gintuple_get_key(btree->ginstate, itup,
283 : : &newCategory);
284 : :
285 [ + - ]: 6 : if (ginCompareEntries(btree->ginstate, attnum,
286 : : newDatum, newCategory,
287 : : idatum, icategory) == 0)
288 : 6 : break; /* Found! */
289 : : }
290 : :
6322 tgl@sss.pgh.pa.us 291 :UBC 0 : stack->off++;
292 : : }
293 : :
5356 tgl@sss.pgh.pa.us 294 [ + - - + ]:CBC 6 : if (icategory == GIN_CAT_NORM_KEY && !attr->attbyval)
5356 tgl@sss.pgh.pa.us 295 :UBC 0 : pfree(DatumGetPointer(idatum));
296 : : }
297 : : else
298 : : {
299 : : ItemPointer ipd;
300 : : int nipd;
301 : :
4245 heikki.linnakangas@i 302 :CBC 204030 : ipd = ginReadTuple(btree->ginstate, scanEntry->attnum, itup, &nipd);
303 : 204030 : tbm_add_tuples(scanEntry->matchBitmap, ipd, nipd, false);
5931 bruce@momjian.us 304 : 204030 : scanEntry->predictNumberResult += GinGetNPosting(itup);
3431 tgl@sss.pgh.pa.us 305 : 204030 : pfree(ipd);
306 : : }
307 : :
308 : : /*
309 : : * Done with this entry, go to the next
310 : : */
6322 311 : 204036 : stack->off++;
312 : : }
313 : : }
314 : :
315 : : /*
316 : : * Start* functions setup beginning state of searches: finds correct buffer and pins it.
317 : : */
318 : : static void
3438 kgrittn@postgresql.o 319 : 2038 : startScanEntry(GinState *ginstate, GinScanEntry entry, Snapshot snapshot)
320 : : {
321 : : GinBtreeData btreeEntry;
322 : : GinBtreeStack *stackEntry;
323 : : Page page;
324 : : bool needUnlock;
325 : :
5356 tgl@sss.pgh.pa.us 326 : 2038 : restartScanEntry:
6010 327 : 2038 : entry->buffer = InvalidBuffer;
5355 328 : 2038 : ItemPointerSetMin(&entry->curItem);
6010 329 : 2038 : entry->offset = InvalidOffsetNumber;
3464 330 [ - + ]: 2038 : if (entry->list)
3464 tgl@sss.pgh.pa.us 331 :UBC 0 : pfree(entry->list);
6010 tgl@sss.pgh.pa.us 332 :CBC 2038 : entry->list = NULL;
333 : 2038 : entry->nlist = 0;
5356 334 : 2038 : entry->matchBitmap = NULL;
194 melanieplageman@gmai 335 : 2038 : entry->matchNtuples = -1;
175 336 : 2038 : entry->matchResult.blockno = InvalidBlockNumber;
2943 peter_e@gmx.net 337 : 2038 : entry->reduceResult = false;
6010 tgl@sss.pgh.pa.us 338 : 2038 : entry->predictNumberResult = 0;
339 : :
340 : : /*
341 : : * we should find entry, and begin scan of posting tree or just store
342 : : * posting list in memory
343 : : */
5356 344 : 2038 : ginPrepareEntryScan(&btreeEntry, entry->attnum,
345 : 2038 : entry->queryKey, entry->queryCategory,
346 : : ginstate);
729 tmunro@postgresql.or 347 : 2038 : stackEntry = ginFindLeafPage(&btreeEntry, true, false);
3426 kgrittn@postgresql.o 348 : 2038 : page = BufferGetPage(stackEntry->buffer);
349 : :
350 : : /* ginFindLeafPage() will have already checked snapshot age. */
2943 peter_e@gmx.net 351 : 2038 : needUnlock = true;
352 : :
353 : 2038 : entry->isFinished = true;
354 : :
5356 tgl@sss.pgh.pa.us 355 [ + + ]: 2038 : if (entry->isPartialMatch ||
356 [ + + ]: 1739 : entry->queryCategory == GIN_CAT_EMPTY_QUERY)
357 : : {
358 : : /*
359 : : * btreeEntry.findItem locates the first item >= given search key.
360 : : * (For GIN_CAT_EMPTY_QUERY, it will find the leftmost index item
361 : : * because of the way the GIN_CAT_EMPTY_QUERY category code is
362 : : * assigned.) We scan forward from there and collect all TIDs needed
363 : : * for the entry type.
364 : : */
6322 365 : 440 : btreeEntry.findItem(&btreeEntry, stackEntry);
3426 kgrittn@postgresql.o 366 : 440 : if (collectMatchBitmap(&btreeEntry, stackEntry, entry, snapshot)
367 [ - + ]: 440 : == false)
368 : : {
369 : : /*
370 : : * GIN tree was seriously restructured, so we will cleanup all
371 : : * found data and rescan. See comments near 'return false' in
372 : : * collectMatchBitmap()
373 : : */
5356 tgl@sss.pgh.pa.us 374 [ # # ]:UBC 0 : if (entry->matchBitmap)
375 : : {
376 [ # # ]: 0 : if (entry->matchIterator)
262 melanieplageman@gmai 377 : 0 : tbm_end_private_iterate(entry->matchIterator);
5356 tgl@sss.pgh.pa.us 378 : 0 : entry->matchIterator = NULL;
379 : 0 : tbm_free(entry->matchBitmap);
380 : 0 : entry->matchBitmap = NULL;
381 : : }
6322 382 : 0 : LockBuffer(stackEntry->buffer, GIN_UNLOCK);
383 : 0 : freeGinBtreeStack(stackEntry);
5356 384 : 0 : goto restartScanEntry;
385 : : }
386 : :
5356 tgl@sss.pgh.pa.us 387 [ + - + + ]:CBC 440 : if (entry->matchBitmap && !tbm_is_empty(entry->matchBitmap))
388 : : {
262 melanieplageman@gmai 389 : 360 : entry->matchIterator =
390 : 360 : tbm_begin_private_iterate(entry->matchBitmap);
2943 peter_e@gmx.net 391 : 360 : entry->isFinished = false;
392 : : }
393 : : }
6322 tgl@sss.pgh.pa.us 394 [ + + ]: 1598 : else if (btreeEntry.findItem(&btreeEntry, stackEntry))
395 : : {
6346 teodor@sigaev.ru 396 : 1098 : IndexTuple itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, stackEntry->off));
397 : :
398 [ + + ]: 1098 : if (GinIsPostingTree(itup))
399 : : {
400 : 32 : BlockNumber rootPostingTree = GinGetPostingTree(itup);
401 : : GinBtreeStack *stack;
402 : : Page entrypage;
403 : : ItemPointerData minItem;
404 : :
405 : : /*
406 : : * This is an equality scan, so lock the root of the posting tree.
407 : : * It represents a lock on the exact key value, and covers all the
408 : : * items in the posting tree.
409 : : */
2682 410 : 32 : PredicateLockPage(ginstate->index, rootPostingTree, snapshot);
411 : :
412 : : /*
413 : : * We should unlock entry page before touching posting tree to
414 : : * prevent deadlocks with vacuum processes. Because entry is never
415 : : * deleted from page and posting tree is never reduced to the
416 : : * posting list, we can unlock page after getting BlockNumber of
417 : : * root of posting tree.
418 : : */
7067 419 : 32 : LockBuffer(stackEntry->buffer, GIN_UNLOCK);
2943 peter_e@gmx.net 420 : 32 : needUnlock = false;
421 : :
4238 heikki.linnakangas@i 422 : 32 : stack = ginScanBeginPostingTree(&entry->btree, ginstate->index,
423 : : rootPostingTree);
4308 424 : 32 : entry->buffer = stack->buffer;
425 : :
426 : : /*
427 : : * We keep buffer pinned because we need to prevent deletion of
428 : : * page during scan. See GIN's vacuum implementation. RefCount is
429 : : * increased to keep buffer pinned after freeGinBtreeStack() call.
430 : : */
6346 teodor@sigaev.ru 431 : 32 : IncrBufferRefCount(entry->buffer);
432 : :
1067 drowley@postgresql.o 433 : 32 : entrypage = BufferGetPage(entry->buffer);
434 : :
435 : : /*
436 : : * Load the first page into memory.
437 : : */
4238 heikki.linnakangas@i 438 : 32 : ItemPointerSetMin(&minItem);
1067 drowley@postgresql.o 439 : 32 : entry->list = GinDataLeafPageGetItems(entrypage, &entry->nlist, minItem);
440 : :
4245 heikki.linnakangas@i 441 : 32 : entry->predictNumberResult = stack->predictNumber * entry->nlist;
442 : :
6912 bruce@momjian.us 443 : 32 : LockBuffer(entry->buffer, GIN_UNLOCK);
4308 heikki.linnakangas@i 444 : 32 : freeGinBtreeStack(stack);
2943 peter_e@gmx.net 445 : 32 : entry->isFinished = false;
446 : : }
447 : : else
448 : : {
449 : : /*
450 : : * Lock the entry leaf page. This is more coarse-grained than
451 : : * necessary, because it will conflict with any insertions that
452 : : * land on the same leaf page, not only the exact key we searched
453 : : * for. But locking an individual tuple would require updating
454 : : * that lock whenever it moves because of insertions or vacuums,
455 : : * which seems too complicated.
456 : : */
2682 teodor@sigaev.ru 457 : 1066 : PredicateLockPage(ginstate->index,
458 : : BufferGetBlockNumber(stackEntry->buffer),
459 : : snapshot);
460 [ + + ]: 1066 : if (GinGetNPosting(itup) > 0)
461 : : {
462 : 1063 : entry->list = ginReadTuple(ginstate, entry->attnum, itup,
463 : : &entry->nlist);
464 : 1063 : entry->predictNumberResult = entry->nlist;
465 : :
466 : 1063 : entry->isFinished = false;
467 : : }
468 : : }
469 : : }
470 : : else
471 : : {
472 : : /*
473 : : * No entry found. Predicate lock the leaf page, to lock the place
474 : : * where the entry would've been, had there been one.
475 : : */
476 : 500 : PredicateLockPage(ginstate->index,
477 : : BufferGetBlockNumber(stackEntry->buffer), snapshot);
478 : : }
479 : :
6346 480 [ + + ]: 2038 : if (needUnlock)
5931 bruce@momjian.us 481 : 2006 : LockBuffer(stackEntry->buffer, GIN_UNLOCK);
6346 teodor@sigaev.ru 482 : 2038 : freeGinBtreeStack(stackEntry);
7067 483 : 2038 : }
484 : :
485 : : /*
486 : : * Comparison function for scan entry indexes. Sorts by predictNumberResult,
487 : : * least frequent items first.
488 : : */
489 : : static int
4229 heikki.linnakangas@i 490 : 1756 : entryIndexByFrequencyCmp(const void *a1, const void *a2, void *arg)
491 : : {
492 : 1756 : const GinScanKey key = (const GinScanKey) arg;
493 : 1756 : int i1 = *(const int *) a1;
494 : 1756 : int i2 = *(const int *) a2;
495 : 1756 : uint32 n1 = key->scanEntry[i1]->predictNumberResult;
496 : 1756 : uint32 n2 = key->scanEntry[i2]->predictNumberResult;
497 : :
498 [ + + ]: 1756 : if (n1 < n2)
499 : 319 : return -1;
500 [ + + ]: 1437 : else if (n1 == n2)
501 : 574 : return 0;
502 : : else
503 : 863 : return 1;
504 : : }
505 : :
506 : : static void
507 : 1032 : startScanKey(GinState *ginstate, GinScanOpaque so, GinScanKey key)
508 : : {
509 : 1032 : MemoryContext oldCtx = CurrentMemoryContext;
510 : : int i;
511 : : int j;
512 : : int *entryIndexes;
513 : :
5355 tgl@sss.pgh.pa.us 514 : 1032 : ItemPointerSetMin(&key->curItem);
515 : 1032 : key->curItemMatches = false;
516 : 1032 : key->recheckCurItem = false;
517 : 1032 : key->isFinished = false;
518 : :
519 : : /*
520 : : * Divide the entries into two distinct sets: required and additional.
521 : : * Additional entries are not enough for a match alone, without any items
522 : : * from the required set, but are needed by the consistent function to
523 : : * decide if an item matches. When scanning, we can skip over items from
524 : : * additional entries that have no corresponding matches in any of the
525 : : * required entries. That speeds up queries like "frequent & rare"
526 : : * considerably, if the frequent term can be put in the additional set.
527 : : *
528 : : * There can be many legal ways to divide them entries into these two
529 : : * sets. A conservative division is to just put everything in the required
530 : : * set, but the more you can put in the additional set, the more you can
531 : : * skip during the scan. To maximize skipping, we try to put as many
532 : : * frequent items as possible into additional, and less frequent ones into
533 : : * required. To do that, sort the entries by frequency
534 : : * (predictNumberResult), and put entries into the required set in that
535 : : * order, until the consistent function says that none of the remaining
536 : : * entries can form a match, without any items from the required set. The
537 : : * rest go to the additional set.
538 : : *
539 : : * Exclude-only scan keys are known to have no required entries.
540 : : */
2058 akorotkov@postgresql 541 [ + + ]: 1032 : if (key->excludeOnly)
542 : : {
543 : 21 : MemoryContextSwitchTo(so->keyCtx);
544 : :
545 : 21 : key->nrequired = 0;
546 : 21 : key->nadditional = key->nentries;
547 : 21 : key->additionalEntries = palloc(key->nadditional * sizeof(GinScanEntry));
548 [ + + ]: 24 : for (i = 0; i < key->nadditional; i++)
549 : 3 : key->additionalEntries[i] = key->scanEntry[i];
550 : : }
551 [ + + ]: 1011 : else if (key->nentries > 1)
552 : : {
4229 heikki.linnakangas@i 553 : 278 : MemoryContextSwitchTo(so->tempCtx);
554 : :
555 : 278 : entryIndexes = (int *) palloc(sizeof(int) * key->nentries);
556 [ + + ]: 1580 : for (i = 0; i < key->nentries; i++)
557 : 1302 : entryIndexes[i] = i;
558 : 278 : qsort_arg(entryIndexes, key->nentries, sizeof(int),
559 : : entryIndexByFrequencyCmp, key);
560 : :
184 tgl@sss.pgh.pa.us 561 [ + + ]: 1302 : for (i = 1; i < key->nentries; i++)
562 : 1024 : key->entryRes[entryIndexes[i]] = GIN_MAYBE;
4229 heikki.linnakangas@i 563 [ + + ]: 926 : for (i = 0; i < key->nentries - 1; i++)
564 : : {
565 : : /* Pass all entries <= i as FALSE, and the rest as MAYBE */
184 tgl@sss.pgh.pa.us 566 : 859 : key->entryRes[entryIndexes[i]] = GIN_FALSE;
567 : :
4229 heikki.linnakangas@i 568 [ + + ]: 859 : if (key->triConsistentFn(key) == GIN_FALSE)
569 : 211 : break;
570 : :
571 : : /* Make this loop interruptible in case there are many keys */
184 tgl@sss.pgh.pa.us 572 [ - + ]: 648 : CHECK_FOR_INTERRUPTS();
573 : : }
574 : : /* i is now the last required entry. */
575 : :
3867 heikki.linnakangas@i 576 : 278 : MemoryContextSwitchTo(so->keyCtx);
577 : :
4229 578 : 278 : key->nrequired = i + 1;
579 : 278 : key->nadditional = key->nentries - key->nrequired;
580 : 278 : key->requiredEntries = palloc(key->nrequired * sizeof(GinScanEntry));
581 : 278 : key->additionalEntries = palloc(key->nadditional * sizeof(GinScanEntry));
582 : :
583 : 278 : j = 0;
584 [ + + ]: 1204 : for (i = 0; i < key->nrequired; i++)
585 : 926 : key->requiredEntries[i] = key->scanEntry[entryIndexes[j++]];
586 [ + + ]: 654 : for (i = 0; i < key->nadditional; i++)
587 : 376 : key->additionalEntries[i] = key->scanEntry[entryIndexes[j++]];
588 : :
589 : : /* clean up after consistentFn calls (also frees entryIndexes) */
590 : 278 : MemoryContextReset(so->tempCtx);
591 : : }
592 : : else
593 : : {
3867 594 : 733 : MemoryContextSwitchTo(so->keyCtx);
595 : :
4229 596 : 733 : key->nrequired = 1;
597 : 733 : key->nadditional = 0;
598 : 733 : key->requiredEntries = palloc(1 * sizeof(GinScanEntry));
599 : 733 : key->requiredEntries[0] = key->scanEntry[0];
600 : : }
3867 601 : 1032 : MemoryContextSwitchTo(oldCtx);
5355 tgl@sss.pgh.pa.us 602 : 1032 : }
603 : :
604 : : static void
605 : 966 : startScan(IndexScanDesc scan)
606 : : {
607 : 966 : GinScanOpaque so = (GinScanOpaque) scan->opaque;
608 : 966 : GinState *ginstate = &so->ginstate;
609 : : uint32 i;
610 : :
611 [ + + ]: 3004 : for (i = 0; i < so->totalentries; i++)
3438 kgrittn@postgresql.o 612 : 2038 : startScanEntry(ginstate, so->entries[i], scan->xs_snapshot);
613 : :
6346 teodor@sigaev.ru 614 [ + + ]: 966 : if (GinFuzzySearchLimit > 0)
615 : : {
616 : : /*
617 : : * If all of keys more than threshold we will try to reduce result, we
618 : : * hope (and only hope, for intersection operation of array our
619 : : * supposition isn't true), that total result will not more than
620 : : * minimal predictNumberResult.
621 : : */
3873 heikki.linnakangas@i 622 : 3 : bool reduce = true;
623 : :
5355 tgl@sss.pgh.pa.us 624 [ + + ]: 6 : for (i = 0; i < so->totalentries; i++)
625 : : {
626 [ - + ]: 3 : if (so->entries[i]->predictNumberResult <= so->totalentries * GinFuzzySearchLimit)
627 : : {
3873 heikki.linnakangas@i 628 :UBC 0 : reduce = false;
629 : 0 : break;
630 : : }
631 : : }
3873 heikki.linnakangas@i 632 [ + - ]:CBC 3 : if (reduce)
633 : : {
634 [ + + ]: 6 : for (i = 0; i < so->totalentries; i++)
635 : : {
5355 tgl@sss.pgh.pa.us 636 : 3 : so->entries[i]->predictNumberResult /= so->totalentries;
2943 peter_e@gmx.net 637 : 3 : so->entries[i]->reduceResult = true;
638 : : }
639 : : }
640 : : }
641 : :
642 : : /*
643 : : * Now that we have the estimates for the entry frequencies, finish
644 : : * initializing the scan keys.
645 : : */
6912 bruce@momjian.us 646 [ + + ]: 1998 : for (i = 0; i < so->nkeys; i++)
4229 heikki.linnakangas@i 647 : 1032 : startScanKey(ginstate, so, so->keys + i);
7067 teodor@sigaev.ru 648 : 966 : }
649 : :
650 : : /*
651 : : * Load the next batch of item pointers from a posting tree.
652 : : *
653 : : * Note that we copy the page into GinScanEntry->list array and unlock it, but
654 : : * keep it pinned to prevent interference with vacuum.
655 : : */
656 : : static void
3438 kgrittn@postgresql.o 657 : 50 : entryLoadMoreItems(GinState *ginstate, GinScanEntry entry,
658 : : ItemPointerData advancePast)
659 : : {
660 : : Page page;
661 : : int i;
662 : : bool stepright;
663 : :
4238 heikki.linnakangas@i 664 [ - + ]: 50 : if (!BufferIsValid(entry->buffer))
665 : : {
4238 heikki.linnakangas@i 666 :UBC 0 : entry->isFinished = true;
667 : 0 : return;
668 : : }
669 : :
670 : : /*
671 : : * We have two strategies for finding the correct page: step right from
672 : : * the current page, or descend the tree again from the root. If
673 : : * advancePast equals the current item, the next matching item should be
674 : : * on the next page, so we step right. Otherwise, descend from root.
675 : : */
4238 heikki.linnakangas@i 676 [ + + ]:CBC 50 : if (ginCompareItemPointers(&entry->curItem, &advancePast) == 0)
677 : : {
678 : 47 : stepright = true;
679 : 47 : LockBuffer(entry->buffer, GIN_SHARE);
680 : : }
681 : : else
682 : : {
683 : : GinBtreeStack *stack;
684 : :
685 : 3 : ReleaseBuffer(entry->buffer);
686 : :
687 : : /*
688 : : * Set the search key, and find the correct leaf page.
689 : : */
690 [ - + - - ]: 3 : if (ItemPointerIsLossyPage(&advancePast))
691 : : {
4238 heikki.linnakangas@i 692 :UBC 0 : ItemPointerSet(&entry->btree.itemptr,
693 : 0 : GinItemPointerGetBlockNumber(&advancePast) + 1,
694 : : FirstOffsetNumber);
695 : : }
696 : : else
697 : : {
3084 alvherre@alvh.no-ip. 698 :CBC 3 : ItemPointerSet(&entry->btree.itemptr,
699 : : GinItemPointerGetBlockNumber(&advancePast),
2999 tgl@sss.pgh.pa.us 700 : 3 : OffsetNumberNext(GinItemPointerGetOffsetNumber(&advancePast)));
701 : : }
4238 heikki.linnakangas@i 702 : 3 : entry->btree.fullScan = false;
729 tmunro@postgresql.or 703 : 3 : stack = ginFindLeafPage(&entry->btree, true, false);
704 : :
705 : : /* we don't need the stack, just the buffer. */
4238 heikki.linnakangas@i 706 : 3 : entry->buffer = stack->buffer;
707 : 3 : IncrBufferRefCount(entry->buffer);
708 : 3 : freeGinBtreeStack(stack);
709 : 3 : stepright = false;
710 : : }
711 : :
712 [ - + ]: 50 : elog(DEBUG2, "entryLoadMoreItems, %u/%u, skip: %d",
713 : : GinItemPointerGetBlockNumber(&advancePast),
714 : : GinItemPointerGetOffsetNumber(&advancePast),
715 : : !stepright);
716 : :
3426 kgrittn@postgresql.o 717 : 50 : page = BufferGetPage(entry->buffer);
718 : : for (;;)
719 : : {
4238 heikki.linnakangas@i 720 : 53 : entry->offset = InvalidOffsetNumber;
721 [ + + ]: 53 : if (entry->list)
722 : : {
723 : 47 : pfree(entry->list);
724 : 47 : entry->list = NULL;
725 : 47 : entry->nlist = 0;
726 : : }
727 : :
728 [ + + ]: 53 : if (stepright)
729 : : {
730 : : /*
731 : : * We've processed all the entries on this page. If it was the
732 : : * last page in the tree, we're done.
733 : : */
734 [ + + ]: 50 : if (GinPageRightMost(page))
735 : : {
736 : 3 : UnlockReleaseBuffer(entry->buffer);
737 : 3 : entry->buffer = InvalidBuffer;
2943 peter_e@gmx.net 738 : 3 : entry->isFinished = true;
4238 heikki.linnakangas@i 739 : 3 : return;
740 : : }
741 : :
742 : : /*
743 : : * Step to next page, following the right link. then find the
744 : : * first ItemPointer greater than advancePast.
745 : : */
746 : 47 : entry->buffer = ginStepRight(entry->buffer,
747 : : ginstate->index,
748 : : GIN_SHARE);
3426 kgrittn@postgresql.o 749 : 47 : page = BufferGetPage(entry->buffer);
750 : : }
4238 heikki.linnakangas@i 751 : 50 : stepright = true;
752 : :
753 [ - + ]: 50 : if (GinPageGetOpaque(page)->flags & GIN_DELETED)
4141 bruce@momjian.us 754 :UBC 0 : continue; /* page was deleted by concurrent vacuum */
755 : :
756 : : /*
757 : : * The first item > advancePast might not be on this page, but
758 : : * somewhere to the right, if the page was split, or a non-match from
759 : : * another key in the query allowed us to skip some items from this
760 : : * entry. Keep following the right-links until we re-find the correct
761 : : * page.
762 : : */
4238 heikki.linnakangas@i 763 [ + + - + ]:CBC 68 : if (!GinPageRightMost(page) &&
764 : 18 : ginCompareItemPointers(&advancePast, GinDataPageGetRightBound(page)) >= 0)
765 : : {
766 : : /*
767 : : * the item we're looking is > the right bound of the page, so it
768 : : * can't be on this page.
769 : : */
4238 heikki.linnakangas@i 770 :UBC 0 : continue;
771 : : }
772 : :
4238 heikki.linnakangas@i 773 :CBC 50 : entry->list = GinDataLeafPageGetItems(page, &entry->nlist, advancePast);
774 : :
775 [ + + ]: 962 : for (i = 0; i < entry->nlist; i++)
776 : : {
777 [ + + ]: 959 : if (ginCompareItemPointers(&advancePast, &entry->list[i]) < 0)
778 : : {
779 : 47 : entry->offset = i;
780 : :
781 [ + + ]: 47 : if (GinPageRightMost(page))
782 : : {
783 : : /* after processing the copied items, we're done. */
784 : 29 : UnlockReleaseBuffer(entry->buffer);
785 : 29 : entry->buffer = InvalidBuffer;
786 : : }
787 : : else
788 : 18 : LockBuffer(entry->buffer, GIN_UNLOCK);
6346 teodor@sigaev.ru 789 : 47 : return;
790 : : }
791 : : }
792 : : }
793 : : }
794 : :
795 : : #define gin_rand() pg_prng_double(&pg_global_prng_state)
796 : : #define dropItem(e) ( gin_rand() > ((double)GinFuzzySearchLimit)/((double)((e)->predictNumberResult)) )
797 : :
798 : : /*
799 : : * Sets entry->curItem to next heap item pointer > advancePast, for one entry
800 : : * of one scan key, or sets entry->isFinished to true if there are no more.
801 : : *
802 : : * Item pointers are returned in ascending order.
803 : : *
804 : : * Note: this can return a "lossy page" item pointer, indicating that the
805 : : * entry potentially matches all items on that heap page. However, it is
806 : : * not allowed to return both a lossy page pointer and exact (regular)
807 : : * item pointers for the same page. (Doing so would break the key-combination
808 : : * logic in keyGetItem and scanGetItem; see comment in scanGetItem.) In the
809 : : * current implementation this is guaranteed by the behavior of tidbitmaps.
810 : : */
811 : : static void
4238 heikki.linnakangas@i 812 : 548297 : entryGetItem(GinState *ginstate, GinScanEntry entry,
813 : : ItemPointerData advancePast)
814 : : {
5516 tgl@sss.pgh.pa.us 815 [ - + ]: 548297 : Assert(!entry->isFinished);
816 : :
4238 heikki.linnakangas@i 817 [ + + - + ]: 548297 : Assert(!ItemPointerIsValid(&entry->curItem) ||
818 : : ginCompareItemPointers(&entry->curItem, &advancePast) <= 0);
819 : :
5355 tgl@sss.pgh.pa.us 820 [ + + ]: 548297 : if (entry->matchBitmap)
821 : : {
822 : : /* A bitmap result */
4238 heikki.linnakangas@i 823 : 134705 : BlockNumber advancePastBlk = GinItemPointerGetBlockNumber(&advancePast);
824 : 134705 : OffsetNumber advancePastOff = GinItemPointerGetOffsetNumber(&advancePast);
825 : :
826 : : for (;;)
827 : : {
828 : : /*
829 : : * If we've exhausted all items on this block, move to next block
830 : : * in the bitmap. tbm_private_iterate() sets matchResult.blockno
831 : : * to InvalidBlockNumber when the bitmap is exhausted.
832 : : */
175 melanieplageman@gmai 833 : 137268 : while ((!BlockNumberIsValid(entry->matchResult.blockno)) ||
834 [ + - ]: 136908 : (!entry->matchResult.lossy &&
194 835 [ + + ]: 136908 : entry->offset >= entry->matchNtuples) ||
175 836 [ + + - + : 405958 : entry->matchResult.blockno < advancePastBlk ||
- + ]
4137 heikki.linnakangas@i 837 [ - - ]: 134345 : (ItemPointerIsLossyPage(&advancePast) &&
175 melanieplageman@gmai 838 [ # # ]:UBC 0 : entry->matchResult.blockno == advancePastBlk))
839 : : {
175 melanieplageman@gmai 840 [ + + ]:CBC 2923 : if (!tbm_private_iterate(entry->matchIterator, &entry->matchResult))
841 : : {
842 [ - + ]: 360 : Assert(!BlockNumberIsValid(entry->matchResult.blockno));
5516 tgl@sss.pgh.pa.us 843 : 360 : ItemPointerSetInvalid(&entry->curItem);
262 melanieplageman@gmai 844 : 360 : tbm_end_private_iterate(entry->matchIterator);
5356 tgl@sss.pgh.pa.us 845 : 360 : entry->matchIterator = NULL;
2943 peter_e@gmx.net 846 : 360 : entry->isFinished = true;
6322 tgl@sss.pgh.pa.us 847 : 360 : break;
848 : : }
849 : :
850 : : /* Exact pages need their tuple offsets extracted. */
175 melanieplageman@gmai 851 [ + - ]: 2563 : if (!entry->matchResult.lossy)
852 : 2563 : entry->matchNtuples = tbm_extract_page_tuple(&entry->matchResult,
194 853 : 2563 : entry->matchOffsets,
854 : : TBM_MAX_TUPLES_PER_PAGE);
855 : :
856 : : /*
857 : : * Reset counter to the beginning of entry->matchResult. Note:
858 : : * entry->offset is still greater than matchResult.ntuples if
859 : : * matchResult is lossy. So, on next call we will get next
860 : : * result from TIDBitmap.
861 : : */
6322 tgl@sss.pgh.pa.us 862 : 2563 : entry->offset = 0;
863 : : }
4167 heikki.linnakangas@i 864 [ + + ]: 134705 : if (entry->isFinished)
865 : 360 : break;
866 : :
867 : : /*
868 : : * We're now on the first page after advancePast which has any
869 : : * items on it. If it's a lossy result, return that.
870 : : */
175 melanieplageman@gmai 871 [ - + ]: 134345 : if (entry->matchResult.lossy)
872 : : {
6010 tgl@sss.pgh.pa.us 873 :UBC 0 : ItemPointerSetLossyPage(&entry->curItem,
874 : : entry->matchResult.blockno);
875 : :
876 : : /*
877 : : * We might as well fall out of the loop; we could not
878 : : * estimate number of results on this page to support correct
879 : : * reducing of result even if it's enabled.
880 : : */
881 : 0 : break;
882 : : }
883 : :
884 : : /*
885 : : * Not a lossy page. If tuple offsets were extracted,
886 : : * entry->matchNtuples must be > -1
887 : : */
194 melanieplageman@gmai 888 [ - + ]:CBC 134345 : Assert(entry->matchNtuples > -1);
889 : :
890 : : /* Skip over any offsets <= advancePast, and return that. */
175 891 [ + + ]: 134345 : if (entry->matchResult.blockno == advancePastBlk)
892 : : {
194 893 [ - + ]: 132142 : Assert(entry->matchNtuples > 0);
894 : :
895 : : /*
896 : : * First, do a quick check against the last offset on the
897 : : * page. If that's > advancePast, so are all the other
898 : : * offsets, so just go back to the top to get the next page.
899 : : */
900 [ - + ]: 132142 : if (entry->matchOffsets[entry->matchNtuples - 1] <= advancePastOff)
901 : : {
194 melanieplageman@gmai 902 :UBC 0 : entry->offset = entry->matchNtuples;
4167 heikki.linnakangas@i 903 : 0 : continue;
904 : : }
905 : :
906 : : /* Otherwise scan to find the first item > advancePast */
194 melanieplageman@gmai 907 [ - + ]:CBC 132142 : while (entry->matchOffsets[entry->offset] <= advancePastOff)
4238 heikki.linnakangas@i 908 :UBC 0 : entry->offset++;
909 : : }
910 : :
6010 tgl@sss.pgh.pa.us 911 :CBC 134345 : ItemPointerSet(&entry->curItem,
912 : : entry->matchResult.blockno,
194 melanieplageman@gmai 913 : 134345 : entry->matchOffsets[entry->offset]);
6010 tgl@sss.pgh.pa.us 914 : 134345 : entry->offset++;
915 : :
916 : : /* Done unless we need to reduce the result */
1982 917 [ - + - - ]: 134345 : if (!entry->reduceResult || !dropItem(entry))
918 : : break;
919 : : }
920 : : }
6346 teodor@sigaev.ru 921 [ + + ]: 413592 : else if (!BufferIsValid(entry->buffer))
922 : : {
923 : : /*
924 : : * A posting list from an entry tuple, or the last page of a posting
925 : : * tree.
926 : : */
927 : : for (;;)
928 : : {
4238 heikki.linnakangas@i 929 [ + + ]: 200842 : if (entry->offset >= entry->nlist)
930 : : {
931 : 815 : ItemPointerSetInvalid(&entry->curItem);
2943 peter_e@gmx.net 932 : 815 : entry->isFinished = true;
4238 heikki.linnakangas@i 933 : 815 : break;
934 : : }
935 : :
936 : 200027 : entry->curItem = entry->list[entry->offset++];
937 : :
938 : : /* If we're not past advancePast, keep scanning */
1982 tgl@sss.pgh.pa.us 939 [ + + ]: 200027 : if (ginCompareItemPointers(&entry->curItem, &advancePast) <= 0)
940 : 33317 : continue;
941 : :
942 : : /* Done unless we need to reduce the result */
943 [ + + + + ]: 166710 : if (!entry->reduceResult || !dropItem(entry))
944 : : break;
945 : : }
946 : : }
947 : : else
948 : : {
949 : : /* A posting tree */
950 : : for (;;)
951 : : {
952 : : /* If we've processed the current batch, load more items */
4238 heikki.linnakangas@i 953 [ + + ]: 339450 : while (entry->offset >= entry->nlist)
954 : : {
729 tmunro@postgresql.or 955 : 50 : entryLoadMoreItems(ginstate, entry, advancePast);
956 : :
4238 heikki.linnakangas@i 957 [ + + ]: 50 : if (entry->isFinished)
958 : : {
959 : 3 : ItemPointerSetInvalid(&entry->curItem);
960 : 3 : return;
961 : : }
962 : : }
963 : :
964 : 339400 : entry->curItem = entry->list[entry->offset++];
965 : :
966 : : /* If we're not past advancePast, keep scanning */
1982 tgl@sss.pgh.pa.us 967 [ + + ]: 339400 : if (ginCompareItemPointers(&entry->curItem, &advancePast) <= 0)
968 : 23553 : continue;
969 : :
970 : : /* Done unless we need to reduce the result */
971 [ + + + + ]: 315847 : if (!entry->reduceResult || !dropItem(entry))
972 : : break;
973 : :
974 : : /*
975 : : * Advance advancePast (so that entryLoadMoreItems will load the
976 : : * right data), and keep scanning
977 : : */
978 : 59496 : advancePast = entry->curItem;
979 : : }
980 : : }
981 : : }
982 : :
983 : : /*
984 : : * Identify the "current" item among the input entry streams for this scan key
985 : : * that is greater than advancePast, and test whether it passes the scan key
986 : : * qual condition.
987 : : *
988 : : * The current item is the smallest curItem among the inputs. key->curItem
989 : : * is set to that value. key->curItemMatches is set to indicate whether that
990 : : * TID passes the consistentFn test. If so, key->recheckCurItem is set true
991 : : * iff recheck is needed for this item pointer (including the case where the
992 : : * item pointer is a lossy page pointer).
993 : : *
994 : : * If all entry streams are exhausted, sets key->isFinished to true.
995 : : *
996 : : * Item pointers must be returned in ascending order.
997 : : *
998 : : * Note: this can return a "lossy page" item pointer, indicating that the
999 : : * key potentially matches all items on that heap page. However, it is
1000 : : * not allowed to return both a lossy page pointer and exact (regular)
1001 : : * item pointers for the same page. (Doing so would break the key-combination
1002 : : * logic in scanGetItem.)
1003 : : */
1004 : : static void
4238 heikki.linnakangas@i 1005 : 503756 : keyGetItem(GinState *ginstate, MemoryContext tempCtx, GinScanKey key,
1006 : : ItemPointerData advancePast)
1007 : : {
1008 : : ItemPointerData minItem;
1009 : : ItemPointerData curPageLossy;
1010 : : uint32 i;
1011 : : bool haveLossyEntry;
1012 : : GinScanEntry entry;
1013 : : GinTernaryValue res;
1014 : : MemoryContext oldCtx;
1015 : : bool allFinished;
1016 : :
5516 tgl@sss.pgh.pa.us 1017 [ - + ]: 503756 : Assert(!key->isFinished);
1018 : :
1019 : : /*
1020 : : * We might have already tested this item; if so, no need to repeat work.
1021 : : * (Note: the ">" case can happen, if advancePast is exact but we
1022 : : * previously had to set curItem to a lossy-page pointer.)
1023 : : */
4238 heikki.linnakangas@i 1024 [ + + ]: 503756 : if (ginCompareItemPointers(&key->curItem, &advancePast) > 0)
1025 : 972 : return;
1026 : :
1027 : : /*
1028 : : * Find the minimum item > advancePast among the active entry streams.
1029 : : *
1030 : : * Note: a lossy-page entry is encoded by a ItemPointer with max value for
1031 : : * offset (0xffff), so that it will sort after any exact entries for the
1032 : : * same page. So we'll prefer to return exact pointers not lossy
1033 : : * pointers, which is good.
1034 : : */
5355 tgl@sss.pgh.pa.us 1035 : 503750 : ItemPointerSetMax(&minItem);
4238 heikki.linnakangas@i 1036 : 503750 : allFinished = true;
4229 1037 [ + + ]: 1364013 : for (i = 0; i < key->nrequired; i++)
1038 : : {
1039 : 860263 : entry = key->requiredEntries[i];
1040 : :
1041 [ + + ]: 860263 : if (entry->isFinished)
1042 : 263901 : continue;
1043 : :
1044 : : /*
1045 : : * Advance this stream if necessary.
1046 : : *
1047 : : * In particular, since entry->curItem was initialized with
1048 : : * ItemPointerSetMin, this ensures we fetch the first item for each
1049 : : * entry on the first call.
1050 : : */
1051 [ + + ]: 596362 : if (ginCompareItemPointers(&entry->curItem, &advancePast) <= 0)
1052 : : {
729 tmunro@postgresql.or 1053 : 517419 : entryGetItem(ginstate, entry, advancePast);
4229 heikki.linnakangas@i 1054 [ + + ]: 517419 : if (entry->isFinished)
1055 : 1089 : continue;
1056 : : }
1057 : :
1058 : 595273 : allFinished = false;
1059 [ + + ]: 595273 : if (ginCompareItemPointers(&entry->curItem, &minItem) < 0)
1060 : 550172 : minItem = entry->curItem;
1061 : : }
1062 : :
2058 akorotkov@postgresql 1063 [ + + + + ]: 503750 : if (allFinished && !key->excludeOnly)
1064 : : {
1065 : : /* all entries are finished */
2943 peter_e@gmx.net 1066 : 966 : key->isFinished = true;
5355 tgl@sss.pgh.pa.us 1067 : 966 : return;
1068 : : }
1069 : :
2058 akorotkov@postgresql 1070 [ + + ]: 502784 : if (!key->excludeOnly)
1071 : : {
1072 : : /*
1073 : : * For a normal scan key, we now know there are no matches < minItem.
1074 : : *
1075 : : * If minItem is lossy, it means that there were no exact items on the
1076 : : * page among requiredEntries, because lossy pointers sort after exact
1077 : : * items. However, there might be exact items for the same page among
1078 : : * additionalEntries, so we mustn't advance past them.
1079 : : */
1080 [ - + - - ]: 499553 : if (ItemPointerIsLossyPage(&minItem))
1081 : : {
2058 akorotkov@postgresql 1082 [ # # ]:UBC 0 : if (GinItemPointerGetBlockNumber(&advancePast) <
1083 : 0 : GinItemPointerGetBlockNumber(&minItem))
1084 : : {
1085 : 0 : ItemPointerSet(&advancePast,
1086 : : GinItemPointerGetBlockNumber(&minItem),
1087 : : InvalidOffsetNumber);
1088 : : }
1089 : : }
1090 : : else
1091 : : {
2058 akorotkov@postgresql 1092 [ - + ]:CBC 499553 : Assert(GinItemPointerGetOffsetNumber(&minItem) > 0);
3084 alvherre@alvh.no-ip. 1093 : 499553 : ItemPointerSet(&advancePast,
1094 : : GinItemPointerGetBlockNumber(&minItem),
2058 akorotkov@postgresql 1095 : 499553 : OffsetNumberPrev(GinItemPointerGetOffsetNumber(&minItem)));
1096 : : }
1097 : : }
1098 : : else
1099 : : {
1100 : : /*
1101 : : * excludeOnly scan keys don't have any entries that are necessarily
1102 : : * present in matching items. So, we consider the item just after
1103 : : * advancePast.
1104 : : */
1105 [ - + ]: 3231 : Assert(key->nrequired == 0);
1106 : 3231 : ItemPointerSet(&minItem,
1107 : : GinItemPointerGetBlockNumber(&advancePast),
1108 : 3231 : OffsetNumberNext(GinItemPointerGetOffsetNumber(&advancePast)));
1109 : : }
1110 : :
1111 : : /*
1112 : : * We might not have loaded all the entry streams for this TID yet. We
1113 : : * could call the consistent function, passing MAYBE for those entries, to
1114 : : * see if it can decide if this TID matches based on the information we
1115 : : * have. But if the consistent-function is expensive, and cannot in fact
1116 : : * decide with partial information, that could be a big loss. So, load all
1117 : : * the additional entries, before calling the consistent function.
1118 : : */
4229 heikki.linnakangas@i 1119 [ + + ]: 540114 : for (i = 0; i < key->nadditional; i++)
1120 : : {
1121 : 37330 : entry = key->additionalEntries[i];
1122 : :
1123 [ + + ]: 37330 : if (entry->isFinished)
1124 : 206 : continue;
1125 : :
1126 [ + + ]: 37124 : if (ginCompareItemPointers(&entry->curItem, &advancePast) <= 0)
1127 : : {
729 tmunro@postgresql.or 1128 : 30878 : entryGetItem(ginstate, entry, advancePast);
4229 heikki.linnakangas@i 1129 [ + + ]: 30878 : if (entry->isFinished)
1130 : 89 : continue;
1131 : : }
1132 : :
1133 : : /*
1134 : : * Normally, none of the items in additionalEntries can have a curItem
1135 : : * larger than minItem. But if minItem is a lossy page, then there
1136 : : * might be exact items on the same page among additionalEntries.
1137 : : */
1138 [ - + ]: 37035 : if (ginCompareItemPointers(&entry->curItem, &minItem) < 0)
1139 : : {
4229 heikki.linnakangas@i 1140 [ # # # # ]:UBC 0 : Assert(ItemPointerIsLossyPage(&minItem));
1141 : 0 : minItem = entry->curItem;
1142 : : }
1143 : : }
1144 : :
1145 : : /*
1146 : : * Ok, we've advanced all the entries up to minItem now. Set key->curItem,
1147 : : * and perform consistentFn test.
1148 : : *
1149 : : * Lossy-page entries pose a problem, since we don't know the correct
1150 : : * entryRes state to pass to the consistentFn, and we also don't know what
1151 : : * its combining logic will be (could be AND, OR, or even NOT). If the
1152 : : * logic is OR then the consistentFn might succeed for all items in the
1153 : : * lossy page even when none of the other entries match.
1154 : : *
1155 : : * Our strategy is to call the tri-state consistent function, with the
1156 : : * lossy-page entries set to MAYBE, and all the other entries FALSE. If it
1157 : : * returns FALSE, none of the lossy items alone are enough for a match, so
1158 : : * we don't need to return a lossy-page pointer. Otherwise, return a
1159 : : * lossy-page pointer to indicate that the whole heap page must be
1160 : : * checked. (On subsequent calls, we'll do nothing until minItem is past
1161 : : * the page altogether, thus ensuring that we never return both regular
1162 : : * and lossy pointers for the same page.)
1163 : : *
1164 : : * An exception is that it doesn't matter what we pass for lossy pointers
1165 : : * in "hidden" entries, because the consistentFn's result can't depend on
1166 : : * them. We could pass them as MAYBE as well, but if we're using the
1167 : : * "shim" implementation of a tri-state consistent function (see
1168 : : * ginlogic.c), it's better to pass as few MAYBEs as possible. So pass
1169 : : * them as true.
1170 : : *
1171 : : * Note that only lossy-page entries pointing to the current item's page
1172 : : * should trigger this processing; we might have future lossy pages in the
1173 : : * entry array, but they aren't relevant yet.
1174 : : */
4229 heikki.linnakangas@i 1175 :CBC 502784 : key->curItem = minItem;
5355 tgl@sss.pgh.pa.us 1176 : 502784 : ItemPointerSetLossyPage(&curPageLossy,
1177 : : GinItemPointerGetBlockNumber(&key->curItem));
1178 : 502784 : haveLossyEntry = false;
1179 [ + + ]: 1398763 : for (i = 0; i < key->nentries; i++)
1180 : : {
1181 : 895979 : entry = key->scanEntry[i];
2943 peter_e@gmx.net 1182 [ + + - + ]: 1528287 : if (entry->isFinished == false &&
5355 tgl@sss.pgh.pa.us 1183 : 632308 : ginCompareItemPointers(&entry->curItem, &curPageLossy) == 0)
1184 : : {
4229 heikki.linnakangas@i 1185 [ # # ]:UBC 0 : if (i < key->nuserentries)
1186 : 0 : key->entryRes[i] = GIN_MAYBE;
1187 : : else
1188 : 0 : key->entryRes[i] = GIN_TRUE;
5355 tgl@sss.pgh.pa.us 1189 : 0 : haveLossyEntry = true;
1190 : : }
1191 : : else
4229 heikki.linnakangas@i 1192 :CBC 895979 : key->entryRes[i] = GIN_FALSE;
1193 : : }
1194 : :
1195 : : /* prepare for calling consistentFn in temp context */
5355 tgl@sss.pgh.pa.us 1196 : 502784 : oldCtx = MemoryContextSwitchTo(tempCtx);
1197 : :
1198 [ - + ]: 502784 : if (haveLossyEntry)
1199 : : {
1200 : : /* Have lossy-page entries, so see if whole page matches */
4229 heikki.linnakangas@i 1201 :UBC 0 : res = key->triConsistentFn(key);
1202 : :
1203 [ # # # # ]: 0 : if (res == GIN_TRUE || res == GIN_MAYBE)
1204 : : {
1205 : : /* Yes, so clean up ... */
5355 tgl@sss.pgh.pa.us 1206 : 0 : MemoryContextSwitchTo(oldCtx);
1207 : 0 : MemoryContextReset(tempCtx);
1208 : :
1209 : : /* and return lossy pointer for whole page */
1210 : 0 : key->curItem = curPageLossy;
1211 : 0 : key->curItemMatches = true;
1212 : 0 : key->recheckCurItem = true;
1213 : 0 : return;
1214 : : }
1215 : : }
1216 : :
1217 : : /*
1218 : : * At this point we know that we don't need to return a lossy whole-page
1219 : : * pointer, but we might have matches for individual exact item pointers,
1220 : : * possibly in combination with a lossy pointer. Pass lossy pointers as
1221 : : * MAYBE to the ternary consistent function, to let it decide if this
1222 : : * tuple satisfies the overall key, even though we don't know if the lossy
1223 : : * entries match.
1224 : : *
1225 : : * Prepare entryRes array to be passed to consistentFn.
1226 : : */
5355 tgl@sss.pgh.pa.us 1227 [ + + ]:CBC 1398763 : for (i = 0; i < key->nentries; i++)
1228 : : {
1229 : 895979 : entry = key->scanEntry[i];
4229 heikki.linnakangas@i 1230 [ + + ]: 895979 : if (entry->isFinished)
1231 : 263671 : key->entryRes[i] = GIN_FALSE;
1232 : : #if 0
1233 : :
1234 : : /*
1235 : : * This case can't currently happen, because we loaded all the entries
1236 : : * for this item earlier.
1237 : : */
1238 : : else if (ginCompareItemPointers(&entry->curItem, &advancePast) <= 0)
1239 : : key->entryRes[i] = GIN_MAYBE;
1240 : : #endif
1241 [ - + ]: 632308 : else if (ginCompareItemPointers(&entry->curItem, &curPageLossy) == 0)
4229 heikki.linnakangas@i 1242 :UBC 0 : key->entryRes[i] = GIN_MAYBE;
4229 heikki.linnakangas@i 1243 [ + + ]:CBC 632308 : else if (ginCompareItemPointers(&entry->curItem, &minItem) == 0)
1244 : 541981 : key->entryRes[i] = GIN_TRUE;
1245 : : else
1246 : 90327 : key->entryRes[i] = GIN_FALSE;
1247 : : }
1248 : :
1249 : 502784 : res = key->triConsistentFn(key);
1250 : :
1251 [ + + + - ]: 502784 : switch (res)
1252 : : {
1253 : 432967 : case GIN_TRUE:
1254 : 432967 : key->curItemMatches = true;
1255 : : /* triConsistentFn set recheckCurItem */
1256 : 432967 : break;
1257 : :
1258 : 10013 : case GIN_FALSE:
1259 : 10013 : key->curItemMatches = false;
1260 : 10013 : break;
1261 : :
1262 : 59804 : case GIN_MAYBE:
1263 : 59804 : key->curItemMatches = true;
1264 : 59804 : key->recheckCurItem = true;
1265 : 59804 : break;
1266 : :
4229 heikki.linnakangas@i 1267 :UBC 0 : default:
1268 : :
1269 : : /*
1270 : : * the 'default' case shouldn't happen, but if the consistent
1271 : : * function returns something bogus, this is the safe result
1272 : : */
1273 : 0 : key->curItemMatches = true;
1274 : 0 : key->recheckCurItem = true;
1275 : 0 : break;
1276 : : }
1277 : :
1278 : : /*
1279 : : * We have a tuple, and we know if it matches or not. If it's a non-match,
1280 : : * we could continue to find the next matching tuple, but let's break out
1281 : : * and give scanGetItem a chance to advance the other keys. They might be
1282 : : * able to skip past to a much higher TID, allowing us to save work.
1283 : : */
1284 : :
1285 : : /* clean up after consistentFn calls */
5355 tgl@sss.pgh.pa.us 1286 :CBC 502784 : MemoryContextSwitchTo(oldCtx);
1287 : 502784 : MemoryContextReset(tempCtx);
1288 : : }
1289 : :
1290 : : /*
1291 : : * Get next heap item pointer (after advancePast) from scan.
1292 : : * Returns true if anything found.
1293 : : * On success, *item and *recheck are set.
1294 : : *
1295 : : * Note: this is very nearly the same logic as in keyGetItem(), except
1296 : : * that we know the keys are to be combined with AND logic, whereas in
1297 : : * keyGetItem() the combination logic is known only to the consistentFn.
1298 : : */
1299 : : static bool
4238 heikki.linnakangas@i 1300 : 490458 : scanGetItem(IndexScanDesc scan, ItemPointerData advancePast,
1301 : : ItemPointerData *item, bool *recheck)
1302 : : {
5356 tgl@sss.pgh.pa.us 1303 : 490458 : GinScanOpaque so = (GinScanOpaque) scan->opaque;
1304 : : uint32 i;
1305 : : bool match;
1306 : :
1307 : : /*----------
1308 : : * Advance the scan keys in lock-step, until we find an item that matches
1309 : : * all the keys. If any key reports isFinished, meaning its subset of the
1310 : : * entries is exhausted, we can stop. Otherwise, set *item to the next
1311 : : * matching item.
1312 : : *
1313 : : * This logic works only if a keyGetItem stream can never contain both
1314 : : * exact and lossy pointers for the same page. Else we could have a
1315 : : * case like
1316 : : *
1317 : : * stream 1 stream 2
1318 : : * ... ...
1319 : : * 42/6 42/7
1320 : : * 50/1 42/0xffff
1321 : : * ... ...
1322 : : *
1323 : : * We would conclude that 42/6 is not a match and advance stream 1,
1324 : : * thus never detecting the match to the lossy pointer in stream 2.
1325 : : * (keyGetItem has a similar problem versus entryGetItem.)
1326 : : *----------
1327 : : */
1328 : : do
1329 : : {
11 1330 [ - + ]: 500495 : CHECK_FOR_INTERRUPTS();
1331 : :
4238 heikki.linnakangas@i 1332 : 500495 : ItemPointerSetMin(item);
1333 : 500495 : match = true;
1334 [ + + + - ]: 993272 : for (i = 0; i < so->nkeys && match; i++)
1335 : : {
5356 tgl@sss.pgh.pa.us 1336 : 503756 : GinScanKey key = so->keys + i;
1337 : :
1338 : : /*
1339 : : * If we're considering a lossy page, skip excludeOnly keys. They
1340 : : * can't exclude the whole page anyway.
1341 : : */
2058 akorotkov@postgresql 1342 [ - + - - : 503756 : if (ItemPointerIsLossyPage(item) && key->excludeOnly)
- - ]
1343 : : {
1344 : : /*
1345 : : * ginNewScanKey() should never mark the first key as
1346 : : * excludeOnly.
1347 : : */
2058 akorotkov@postgresql 1348 [ # # ]:UBC 0 : Assert(i > 0);
1349 : 0 : continue;
1350 : : }
1351 : :
1352 : : /* Fetch the next item for this key that is > advancePast. */
729 tmunro@postgresql.or 1353 :CBC 503756 : keyGetItem(&so->ginstate, so->tempCtx, key, advancePast);
1354 : :
5356 tgl@sss.pgh.pa.us 1355 [ + + ]: 503756 : if (key->isFinished)
4238 heikki.linnakangas@i 1356 : 966 : return false;
1357 : :
1358 : : /*
1359 : : * If it's not a match, we can immediately conclude that nothing
1360 : : * <= this item matches, without checking the rest of the keys.
1361 : : */
1362 [ + + ]: 502790 : if (!key->curItemMatches)
1363 : : {
1364 : 10013 : advancePast = key->curItem;
1365 : 10013 : match = false;
1366 : 10013 : break;
1367 : : }
1368 : :
1369 : : /*
1370 : : * It's a match. We can conclude that nothing < matches, so the
1371 : : * other key streams can skip to this item.
1372 : : *
1373 : : * Beware of lossy pointers, though; from a lossy pointer, we can
1374 : : * only conclude that nothing smaller than this *block* matches.
1375 : : */
1376 [ - + - - ]: 492777 : if (ItemPointerIsLossyPage(&key->curItem))
1377 : : {
4238 heikki.linnakangas@i 1378 [ # # ]:UBC 0 : if (GinItemPointerGetBlockNumber(&advancePast) <
1379 : 0 : GinItemPointerGetBlockNumber(&key->curItem))
1380 : : {
3084 alvherre@alvh.no-ip. 1381 : 0 : ItemPointerSet(&advancePast,
2999 tgl@sss.pgh.pa.us 1382 : 0 : GinItemPointerGetBlockNumber(&key->curItem),
1383 : : InvalidOffsetNumber);
1384 : : }
1385 : : }
1386 : : else
1387 : : {
3084 alvherre@alvh.no-ip. 1388 [ - + ]:CBC 492777 : Assert(GinItemPointerGetOffsetNumber(&key->curItem) > 0);
1389 : 492777 : ItemPointerSet(&advancePast,
1390 : 492777 : GinItemPointerGetBlockNumber(&key->curItem),
1391 : 492777 : OffsetNumberPrev(GinItemPointerGetOffsetNumber(&key->curItem)));
1392 : : }
1393 : :
1394 : : /*
1395 : : * If this is the first key, remember this location as a potential
1396 : : * match, and proceed to check the rest of the keys.
1397 : : *
1398 : : * Otherwise, check if this is the same item that we checked the
1399 : : * previous keys for (or a lossy pointer for the same page). If
1400 : : * not, loop back to check the previous keys for this item (we
1401 : : * will check this key again too, but keyGetItem returns quickly
1402 : : * for that)
1403 : : */
4238 heikki.linnakangas@i 1404 [ + + ]: 492777 : if (i == 0)
1405 : : {
1406 : 490567 : *item = key->curItem;
1407 : : }
1408 : : else
1409 : : {
1410 [ - + - - : 4420 : if (ItemPointerIsLossyPage(&key->curItem) ||
- + ]
1411 [ - - ]: 2210 : ItemPointerIsLossyPage(item))
1412 : : {
4141 bruce@momjian.us 1413 [ # # ]:UBC 0 : Assert(GinItemPointerGetBlockNumber(&key->curItem) >= GinItemPointerGetBlockNumber(item));
4238 heikki.linnakangas@i 1414 : 0 : match = (GinItemPointerGetBlockNumber(&key->curItem) ==
1415 : 0 : GinItemPointerGetBlockNumber(item));
1416 : : }
1417 : : else
1418 : : {
4238 heikki.linnakangas@i 1419 [ - + ]:CBC 2210 : Assert(ginCompareItemPointers(&key->curItem, item) >= 0);
1420 : 2210 : match = (ginCompareItemPointers(&key->curItem, item) == 0);
1421 : : }
1422 : : }
1423 : : }
1424 [ + + ]: 499529 : } while (!match);
1425 : :
1426 [ - + - - ]: 489492 : Assert(!ItemPointerIsMin(item));
1427 : :
1428 : : /*
1429 : : * Now *item contains the first ItemPointer after previous result that
1430 : : * satisfied all the keys for that exact TID, or a lossy reference to the
1431 : : * same page.
1432 : : *
1433 : : * We must return recheck = true if any of the keys are marked recheck.
1434 : : */
5356 tgl@sss.pgh.pa.us 1435 : 489492 : *recheck = false;
1436 [ + + ]: 913836 : for (i = 0; i < so->nkeys; i++)
1437 : : {
1438 : 489678 : GinScanKey key = so->keys + i;
1439 : :
1440 [ + + ]: 489678 : if (key->recheckCurItem)
1441 : : {
1442 : 65334 : *recheck = true;
1443 : 65334 : break;
1444 : : }
1445 : : }
1446 : :
2943 peter_e@gmx.net 1447 : 489492 : return true;
1448 : : }
1449 : :
1450 : :
1451 : : /*
1452 : : * Functions for scanning the pending list
1453 : : */
1454 : :
1455 : :
1456 : : /*
1457 : : * Get ItemPointer of next heap row to be checked from pending list.
1458 : : * Returns false if there are no more. On pages with several heap rows
1459 : : * it returns each row separately, on page with part of heap row returns
1460 : : * per page data. pos->firstOffset and pos->lastOffset are set to identify
1461 : : * the range of pending-list tuples belonging to this heap row.
1462 : : *
1463 : : * The pendingBuffer is presumed pinned and share-locked on entry, and is
1464 : : * pinned and share-locked on success exit. On failure exit it's released.
1465 : : */
1466 : : static bool
6010 tgl@sss.pgh.pa.us 1467 : 810 : scanGetCandidate(IndexScanDesc scan, pendingPosition *pos)
1468 : : {
1469 : : OffsetNumber maxoff;
1470 : : Page page;
1471 : : IndexTuple itup;
1472 : :
5931 bruce@momjian.us 1473 : 810 : ItemPointerSetInvalid(&pos->item);
1474 : : for (;;)
1475 : : {
3426 kgrittn@postgresql.o 1476 : 810 : page = BufferGetPage(pos->pendingBuffer);
1477 : :
6010 tgl@sss.pgh.pa.us 1478 : 810 : maxoff = PageGetMaxOffsetNumber(page);
5931 bruce@momjian.us 1479 [ + + ]: 810 : if (pos->firstOffset > maxoff)
1480 : : {
6010 tgl@sss.pgh.pa.us 1481 : 84 : BlockNumber blkno = GinPageGetOpaque(page)->rightlink;
1482 : :
5931 bruce@momjian.us 1483 [ + - ]: 84 : if (blkno == InvalidBlockNumber)
1484 : : {
6010 tgl@sss.pgh.pa.us 1485 : 84 : UnlockReleaseBuffer(pos->pendingBuffer);
5931 bruce@momjian.us 1486 : 84 : pos->pendingBuffer = InvalidBuffer;
1487 : :
6010 tgl@sss.pgh.pa.us 1488 : 84 : return false;
1489 : : }
1490 : : else
1491 : : {
1492 : : /*
1493 : : * Here we must prevent deletion of next page by insertcleanup
1494 : : * process, which may be trying to obtain exclusive lock on
1495 : : * current page. So, we lock next page before releasing the
1496 : : * current one
1497 : : */
5931 bruce@momjian.us 1498 :UBC 0 : Buffer tmpbuf = ReadBuffer(scan->indexRelation, blkno);
1499 : :
6010 tgl@sss.pgh.pa.us 1500 : 0 : LockBuffer(tmpbuf, GIN_SHARE);
1501 : 0 : UnlockReleaseBuffer(pos->pendingBuffer);
1502 : :
1503 : 0 : pos->pendingBuffer = tmpbuf;
1504 : 0 : pos->firstOffset = FirstOffsetNumber;
1505 : : }
1506 : : }
1507 : : else
1508 : : {
6010 tgl@sss.pgh.pa.us 1509 :CBC 726 : itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, pos->firstOffset));
1510 : 726 : pos->item = itup->t_tid;
5931 bruce@momjian.us 1511 [ + - ]: 726 : if (GinPageHasFullRow(page))
1512 : : {
1513 : : /*
1514 : : * find itempointer to the next row
1515 : : */
1516 [ + + ]: 1695 : for (pos->lastOffset = pos->firstOffset + 1; pos->lastOffset <= maxoff; pos->lastOffset++)
1517 : : {
6010 tgl@sss.pgh.pa.us 1518 : 1611 : itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, pos->lastOffset));
1519 [ + + ]: 1611 : if (!ItemPointerEquals(&pos->item, &itup->t_tid))
1520 : 642 : break;
1521 : : }
1522 : : }
1523 : : else
1524 : : {
1525 : : /*
1526 : : * All itempointers are the same on this page
1527 : : */
6010 tgl@sss.pgh.pa.us 1528 :UBC 0 : pos->lastOffset = maxoff + 1;
1529 : : }
1530 : :
1531 : : /*
1532 : : * Now pos->firstOffset points to the first tuple of current heap
1533 : : * row, pos->lastOffset points to the first tuple of next heap row
1534 : : * (or to the end of page)
1535 : : */
6010 tgl@sss.pgh.pa.us 1536 :CBC 726 : break;
1537 : : }
1538 : : }
1539 : :
1540 : 726 : return true;
1541 : : }
1542 : :
1543 : : /*
1544 : : * Scan pending-list page from current tuple (off) up till the first of:
1545 : : * - match is found (then returns true)
1546 : : * - no later match is possible
1547 : : * - tuple's attribute number is not equal to entry's attrnum
1548 : : * - reach end of page
1549 : : *
1550 : : * datum[]/category[]/datumExtracted[] arrays are used to cache the results
1551 : : * of gintuple_get_key() on the current page.
1552 : : */
1553 : : static bool
6010 tgl@sss.pgh.pa.us 1554 :UBC 0 : matchPartialInPendingList(GinState *ginstate, Page page,
1555 : : OffsetNumber off, OffsetNumber maxoff,
1556 : : GinScanEntry entry,
1557 : : Datum *datum, GinNullCategory *category,
1558 : : bool *datumExtracted)
1559 : : {
1560 : : IndexTuple itup;
1561 : : int32 cmp;
1562 : :
1563 : : /* Partial match to a null is not possible */
5356 1564 [ # # ]: 0 : if (entry->queryCategory != GIN_CAT_NORM_KEY)
1565 : 0 : return false;
1566 : :
5931 bruce@momjian.us 1567 [ # # ]: 0 : while (off < maxoff)
1568 : : {
6010 tgl@sss.pgh.pa.us 1569 : 0 : itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, off));
1570 : :
5356 1571 [ # # ]: 0 : if (gintuple_get_attrnum(ginstate, itup) != entry->attnum)
6010 1572 : 0 : return false;
1573 : :
5931 bruce@momjian.us 1574 [ # # ]: 0 : if (datumExtracted[off - 1] == false)
1575 : : {
5356 tgl@sss.pgh.pa.us 1576 : 0 : datum[off - 1] = gintuple_get_key(ginstate, itup,
1577 : 0 : &category[off - 1]);
5931 bruce@momjian.us 1578 : 0 : datumExtracted[off - 1] = true;
1579 : : }
1580 : :
1581 : : /* Once we hit nulls, no further match is possible */
5356 tgl@sss.pgh.pa.us 1582 [ # # ]: 0 : if (category[off - 1] != GIN_CAT_NORM_KEY)
1583 : 0 : return false;
1584 : :
1585 : : /*----------
1586 : : * Check partial match.
1587 : : * case cmp == 0 => match
1588 : : * case cmp > 0 => not match and end scan (no later match possible)
1589 : : * case cmp < 0 => not match and continue scan
1590 : : *----------
1591 : : */
5261 1592 : 0 : cmp = DatumGetInt32(FunctionCall4Coll(&ginstate->comparePartialFn[entry->attnum - 1],
2999 1593 : 0 : ginstate->supportCollation[entry->attnum - 1],
1594 : : entry->queryKey,
5261 1595 : 0 : datum[off - 1],
5203 bruce@momjian.us 1596 : 0 : UInt16GetDatum(entry->strategy),
2999 tgl@sss.pgh.pa.us 1597 : 0 : PointerGetDatum(entry->extra_data)));
6009 1598 [ # # ]: 0 : if (cmp == 0)
6010 1599 : 0 : return true;
6009 1600 [ # # ]: 0 : else if (cmp > 0)
6010 1601 : 0 : return false;
1602 : :
5998 teodor@sigaev.ru 1603 : 0 : off++;
1604 : : }
1605 : :
6010 tgl@sss.pgh.pa.us 1606 : 0 : return false;
1607 : : }
1608 : :
1609 : : /*
1610 : : * Set up the entryRes array for each key by looking at
1611 : : * every entry for current heap row in pending list.
1612 : : *
1613 : : * Returns true if each scan key has at least one entryRes match.
1614 : : * This corresponds to the situations where the normal index search will
1615 : : * try to apply the key's consistentFn. (A tuple not meeting that requirement
1616 : : * cannot be returned by the normal search since no entry stream will
1617 : : * source its TID.)
1618 : : *
1619 : : * The pendingBuffer is presumed pinned and share-locked on entry.
1620 : : */
1621 : : static bool
5356 tgl@sss.pgh.pa.us 1622 :CBC 726 : collectMatchesForHeapRow(IndexScanDesc scan, pendingPosition *pos)
1623 : : {
5931 bruce@momjian.us 1624 : 726 : GinScanOpaque so = (GinScanOpaque) scan->opaque;
1625 : : OffsetNumber attrnum;
1626 : : Page page;
1627 : : IndexTuple itup;
1628 : : int i,
1629 : : j;
1630 : :
1631 : : /*
1632 : : * Reset all entryRes and hasMatchKey flags
1633 : : */
6010 tgl@sss.pgh.pa.us 1634 [ + + ]: 1968 : for (i = 0; i < so->nkeys; i++)
1635 : : {
1636 : 1242 : GinScanKey key = so->keys + i;
1637 : :
4229 heikki.linnakangas@i 1638 : 1242 : memset(key->entryRes, GIN_FALSE, key->nentries);
1639 : : }
2943 peter_e@gmx.net 1640 : 726 : memset(pos->hasMatchKey, false, so->nkeys);
1641 : :
1642 : : /*
1643 : : * Outer loop iterates over multiple pending-list pages when a single heap
1644 : : * row has entries spanning those pages.
1645 : : */
1646 : : for (;;)
6010 tgl@sss.pgh.pa.us 1647 :UBC 0 : {
1648 : : Datum datum[BLCKSZ / sizeof(IndexTupleData)];
1649 : : GinNullCategory category[BLCKSZ / sizeof(IndexTupleData)];
1650 : : bool datumExtracted[BLCKSZ / sizeof(IndexTupleData)];
1651 : :
5931 bruce@momjian.us 1652 [ - + ]:CBC 726 : Assert(pos->lastOffset > pos->firstOffset);
5356 tgl@sss.pgh.pa.us 1653 : 726 : memset(datumExtracted + pos->firstOffset - 1, 0,
1654 : 726 : sizeof(bool) * (pos->lastOffset - pos->firstOffset));
1655 : :
3426 kgrittn@postgresql.o 1656 : 726 : page = BufferGetPage(pos->pendingBuffer);
1657 : :
5931 bruce@momjian.us 1658 [ + + ]: 1968 : for (i = 0; i < so->nkeys; i++)
1659 : : {
1660 : 1242 : GinScanKey key = so->keys + i;
1661 : :
1662 [ + + ]: 2406 : for (j = 0; j < key->nentries; j++)
1663 : : {
5355 tgl@sss.pgh.pa.us 1664 : 1164 : GinScanEntry entry = key->scanEntry[j];
5931 bruce@momjian.us 1665 : 1164 : OffsetNumber StopLow = pos->firstOffset,
1666 : 1164 : StopHigh = pos->lastOffset,
1667 : : StopMiddle;
1668 : :
1669 : : /* If already matched on earlier page, do no extra work */
1670 [ - + ]: 1164 : if (key->entryRes[j])
6010 tgl@sss.pgh.pa.us 1671 :UBC 0 : continue;
1672 : :
1673 : : /*
1674 : : * Interesting tuples are from pos->firstOffset to
1675 : : * pos->lastOffset and they are ordered by (attnum, Datum) as
1676 : : * it's done in entry tree. So we can use binary search to
1677 : : * avoid linear scanning.
1678 : : */
6010 tgl@sss.pgh.pa.us 1679 [ + + ]:CBC 2598 : while (StopLow < StopHigh)
1680 : : {
1681 : : int res;
1682 : :
1683 : 2047 : StopMiddle = StopLow + ((StopHigh - StopLow) >> 1);
1684 : :
1685 : 2047 : itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, StopMiddle));
1686 : :
1687 : 2047 : attrnum = gintuple_get_attrnum(&so->ginstate, itup);
1688 : :
1689 [ + + ]: 2047 : if (key->attnum < attrnum)
1690 : : {
1691 : 399 : StopHigh = StopMiddle;
5356 1692 : 399 : continue;
1693 : : }
1694 [ + + ]: 1648 : if (key->attnum > attrnum)
1695 : : {
6010 1696 : 330 : StopLow = StopMiddle + 1;
5356 1697 : 330 : continue;
1698 : : }
1699 : :
1700 [ + + ]: 1318 : if (datumExtracted[StopMiddle - 1] == false)
1701 : : {
1702 : 2508 : datum[StopMiddle - 1] =
1703 : 1254 : gintuple_get_key(&so->ginstate, itup,
1704 : 1254 : &category[StopMiddle - 1]);
1705 : 1254 : datumExtracted[StopMiddle - 1] = true;
1706 : : }
1707 : :
1708 [ + + ]: 1318 : if (entry->queryCategory == GIN_CAT_EMPTY_QUERY)
1709 : : {
1710 : : /* special behavior depending on searchMode */
1711 [ + - ]: 542 : if (entry->searchMode == GIN_SEARCH_MODE_ALL)
1712 : : {
1713 : : /* match anything except NULL_ITEM */
1714 [ + + ]: 542 : if (category[StopMiddle - 1] == GIN_CAT_NULL_ITEM)
1715 : 189 : res = -1;
1716 : : else
1717 : 353 : res = 0;
1718 : : }
1719 : : else
1720 : : {
1721 : : /* match everything */
5356 tgl@sss.pgh.pa.us 1722 :UBC 0 : res = 0;
1723 : : }
1724 : : }
1725 : : else
1726 : : {
5438 tgl@sss.pgh.pa.us 1727 :CBC 776 : res = ginCompareEntries(&so->ginstate,
1728 : 776 : entry->attnum,
1729 : : entry->queryKey,
5356 1730 : 776 : entry->queryCategory,
1731 : 776 : datum[StopMiddle - 1],
1732 : 776 : category[StopMiddle - 1]);
1733 : : }
1734 : :
1735 [ + + ]: 1318 : if (res == 0)
1736 : : {
1737 : : /*
1738 : : * Found exact match (there can be only one, except in
1739 : : * EMPTY_QUERY mode).
1740 : : *
1741 : : * If doing partial match, scan forward from here to
1742 : : * end of page to check for matches.
1743 : : *
1744 : : * See comment above about tuple's ordering.
1745 : : */
1746 [ - + ]: 613 : if (entry->isPartialMatch)
5356 tgl@sss.pgh.pa.us 1747 :UBC 0 : key->entryRes[j] =
1748 : 0 : matchPartialInPendingList(&so->ginstate,
1749 : : page,
1750 : : StopMiddle,
1751 : 0 : pos->lastOffset,
1752 : : entry,
1753 : : datum,
1754 : : category,
1755 : : datumExtracted);
1756 : : else
5356 tgl@sss.pgh.pa.us 1757 :CBC 613 : key->entryRes[j] = true;
1758 : :
1759 : : /* done with binary search */
1760 : 613 : break;
1761 : : }
1762 [ + + ]: 705 : else if (res < 0)
1763 : 677 : StopHigh = StopMiddle;
1764 : : else
1765 : 28 : StopLow = StopMiddle + 1;
1766 : : }
1767 : :
5931 bruce@momjian.us 1768 [ + + - + ]: 1164 : if (StopLow >= StopHigh && entry->isPartialMatch)
1769 : : {
1770 : : /*
1771 : : * No exact match on this page. If doing partial match,
1772 : : * scan from the first tuple greater than target value to
1773 : : * end of page. Note that since we don't remember whether
1774 : : * the comparePartialFn told us to stop early on a
1775 : : * previous page, we will uselessly apply comparePartialFn
1776 : : * to the first tuple on each subsequent page.
1777 : : */
6010 tgl@sss.pgh.pa.us 1778 :UBC 0 : key->entryRes[j] =
1779 : 0 : matchPartialInPendingList(&so->ginstate,
1780 : : page,
1781 : : StopHigh,
1782 : 0 : pos->lastOffset,
1783 : : entry,
1784 : : datum,
1785 : : category,
1786 : : datumExtracted);
1787 : : }
1788 : :
5776 teodor@sigaev.ru 1789 :CBC 1164 : pos->hasMatchKey[i] |= key->entryRes[j];
1790 : : }
1791 : : }
1792 : :
1793 : : /* Advance firstOffset over the scanned tuples */
6010 tgl@sss.pgh.pa.us 1794 : 726 : pos->firstOffset = pos->lastOffset;
1795 : :
5931 bruce@momjian.us 1796 [ + - ]: 726 : if (GinPageHasFullRow(page))
1797 : : {
1798 : : /*
1799 : : * We have examined all pending entries for the current heap row.
1800 : : * Break out of loop over pages.
1801 : : */
5356 tgl@sss.pgh.pa.us 1802 : 726 : break;
1803 : : }
1804 : : else
1805 : : {
1806 : : /*
1807 : : * Advance to next page of pending entries for the current heap
1808 : : * row. Complain if there isn't one.
1809 : : */
5356 tgl@sss.pgh.pa.us 1810 :UBC 0 : ItemPointerData item = pos->item;
1811 : :
1812 [ # # ]: 0 : if (scanGetCandidate(scan, pos) == false ||
1813 [ # # ]: 0 : !ItemPointerEquals(&pos->item, &item))
1814 [ # # ]: 0 : elog(ERROR, "could not find additional pending pages for same heap tuple");
1815 : : }
1816 : : }
1817 : :
1818 : : /*
1819 : : * All scan keys except excludeOnly require at least one entry to match.
1820 : : * excludeOnly keys are an exception, because their implied
1821 : : * GIN_CAT_EMPTY_QUERY scanEntry always matches. So return "true" if all
1822 : : * non-excludeOnly scan keys have at least one match.
1823 : : */
5356 tgl@sss.pgh.pa.us 1824 [ + + ]:CBC 1263 : for (i = 0; i < so->nkeys; i++)
1825 : : {
2058 akorotkov@postgresql 1826 [ + + + + ]: 984 : if (pos->hasMatchKey[i] == false && !so->keys[i].excludeOnly)
5356 tgl@sss.pgh.pa.us 1827 : 447 : return false;
1828 : : }
1829 : :
1830 : 279 : return true;
1831 : : }
1832 : :
1833 : : /*
1834 : : * Collect all matched rows from pending list into bitmap.
1835 : : */
1836 : : static void
6010 1837 : 966 : scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
1838 : : {
1839 : 966 : GinScanOpaque so = (GinScanOpaque) scan->opaque;
1840 : : MemoryContext oldCtx;
1841 : : bool recheck,
1842 : : match;
1843 : : int i;
1844 : : pendingPosition pos;
5931 bruce@momjian.us 1845 : 966 : Buffer metabuffer = ReadBuffer(scan->indexRelation, GIN_METAPAGE_BLKNO);
1846 : : Page page;
1847 : : BlockNumber blkno;
1848 : :
6010 tgl@sss.pgh.pa.us 1849 : 966 : *ntids = 0;
1850 : :
1851 : : /*
1852 : : * Acquire predicate lock on the metapage, to conflict with any fastupdate
1853 : : * insertions.
1854 : : */
2682 teodor@sigaev.ru 1855 : 966 : PredicateLockPage(scan->indexRelation, GIN_METAPAGE_BLKNO, scan->xs_snapshot);
1856 : :
6010 tgl@sss.pgh.pa.us 1857 : 966 : LockBuffer(metabuffer, GIN_SHARE);
3426 kgrittn@postgresql.o 1858 : 966 : page = BufferGetPage(metabuffer);
3438 1859 : 966 : blkno = GinPageGetMeta(page)->head;
1860 : :
1861 : : /*
1862 : : * fetch head of list before unlocking metapage. head page must be pinned
1863 : : * to prevent deletion by vacuum process
1864 : : */
5931 bruce@momjian.us 1865 [ + + ]: 966 : if (blkno == InvalidBlockNumber)
1866 : : {
1867 : : /* No pending list, so proceed with normal scan */
1868 : 882 : UnlockReleaseBuffer(metabuffer);
6010 tgl@sss.pgh.pa.us 1869 : 882 : return;
1870 : : }
1871 : :
1872 : 84 : pos.pendingBuffer = ReadBuffer(scan->indexRelation, blkno);
1873 : 84 : LockBuffer(pos.pendingBuffer, GIN_SHARE);
1874 : 84 : pos.firstOffset = FirstOffsetNumber;
5931 bruce@momjian.us 1875 : 84 : UnlockReleaseBuffer(metabuffer);
5776 teodor@sigaev.ru 1876 : 84 : pos.hasMatchKey = palloc(sizeof(bool) * so->nkeys);
1877 : :
1878 : : /*
1879 : : * loop for each heap row. scanGetCandidate returns full row or row's
1880 : : * tuples from first page.
1881 : : */
5931 bruce@momjian.us 1882 [ + + ]: 810 : while (scanGetCandidate(scan, &pos))
1883 : : {
1884 : : /*
1885 : : * Check entries in tuple and set up entryRes array.
1886 : : *
1887 : : * If pending tuples belonging to the current heap row are spread
1888 : : * across several pages, collectMatchesForHeapRow will read all of
1889 : : * those pages.
1890 : : */
5356 tgl@sss.pgh.pa.us 1891 [ + + ]: 726 : if (!collectMatchesForHeapRow(scan, &pos))
6010 1892 : 447 : continue;
1893 : :
1894 : : /*
1895 : : * Matching of entries of one row is finished, so check row using
1896 : : * consistent functions.
1897 : : */
1898 : 279 : oldCtx = MemoryContextSwitchTo(so->tempCtx);
1899 : 279 : recheck = false;
1900 : 279 : match = true;
1901 : :
6009 1902 [ + + ]: 706 : for (i = 0; i < so->nkeys; i++)
1903 : : {
5931 bruce@momjian.us 1904 : 429 : GinScanKey key = so->keys + i;
1905 : :
4229 heikki.linnakangas@i 1906 [ + + ]: 429 : if (!key->boolConsistentFn(key))
1907 : : {
6010 tgl@sss.pgh.pa.us 1908 : 2 : match = false;
6009 1909 : 2 : break;
1910 : : }
5515 1911 : 427 : recheck |= key->recheckCurItem;
1912 : : }
1913 : :
6010 1914 : 279 : MemoryContextSwitchTo(oldCtx);
1915 : 279 : MemoryContextReset(so->tempCtx);
1916 : :
5931 bruce@momjian.us 1917 [ + + ]: 279 : if (match)
1918 : : {
6010 tgl@sss.pgh.pa.us 1919 : 277 : tbm_add_tuples(tbm, &pos.item, 1, recheck);
1920 : 277 : (*ntids)++;
1921 : : }
1922 : : }
1923 : :
5776 teodor@sigaev.ru 1924 : 84 : pfree(pos.hasMatchKey);
1925 : : }
1926 : :
1927 : :
1928 : : #define GinIsVoidRes(s) ( ((GinScanOpaque) scan->opaque)->isVoidRes )
1929 : :
1930 : : int64
3520 tgl@sss.pgh.pa.us 1931 : 972 : gingetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
1932 : : {
3872 heikki.linnakangas@i 1933 : 972 : GinScanOpaque so = (GinScanOpaque) scan->opaque;
1934 : : int64 ntids;
1935 : : ItemPointerData iptr;
1936 : : bool recheck;
1937 : :
1938 : : /*
1939 : : * Set up the scan keys, and check for unsatisfiable query.
1940 : : */
3759 bruce@momjian.us 1941 : 972 : ginFreeScanKeys(so); /* there should be no keys yet, but just to be
1942 : : * sure */
3872 heikki.linnakangas@i 1943 : 972 : ginNewScanKey(scan);
1944 : :
6793 teodor@sigaev.ru 1945 [ + + ]: 972 : if (GinIsVoidRes(scan))
3520 tgl@sss.pgh.pa.us 1946 : 6 : return 0;
1947 : :
6010 1948 : 966 : ntids = 0;
1949 : :
1950 : : /*
1951 : : * First, scan the pending list and collect any matching entries into the
1952 : : * bitmap. After we scan a pending item, some other backend could post it
1953 : : * into the main index, and so we might visit it a second time during the
1954 : : * main scan. This is okay because we'll just re-set the same bit in the
1955 : : * bitmap. (The possibility of duplicate visits is a major reason why GIN
1956 : : * can't support the amgettuple API, however.) Note that it would not do
1957 : : * to scan the main index before the pending list, since concurrent
1958 : : * cleanup could then make us miss entries entirely.
1959 : : */
1960 : 966 : scanPendingInsert(scan, tbm, &ntids);
1961 : :
1962 : : /*
1963 : : * Now scan the main index.
1964 : : */
6793 teodor@sigaev.ru 1965 : 966 : startScan(scan);
1966 : :
5516 tgl@sss.pgh.pa.us 1967 : 966 : ItemPointerSetMin(&iptr);
1968 : :
1969 : : for (;;)
1970 : : {
4238 heikki.linnakangas@i 1971 [ + + ]: 490458 : if (!scanGetItem(scan, iptr, &iptr, &recheck))
7067 teodor@sigaev.ru 1972 : 966 : break;
1973 : :
5931 bruce@momjian.us 1974 [ - + - - ]: 489492 : if (ItemPointerIsLossyPage(&iptr))
6010 tgl@sss.pgh.pa.us 1975 :UBC 0 : tbm_add_page(tbm, ItemPointerGetBlockNumber(&iptr));
1976 : : else
6010 tgl@sss.pgh.pa.us 1977 :CBC 489492 : tbm_add_tuples(tbm, &iptr, 1, recheck);
6358 1978 : 489492 : ntids++;
1979 : : }
1980 : :
3520 1981 : 966 : return ntids;
1982 : : }
|