* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/gin/ginget.c,v 1.27.2.1 2009/11/13 11:17:22 teodor Exp $
+ * $PostgreSQL: pgsql/src/backend/access/gin/ginget.c,v 1.27.2.2 2010/07/31 00:31:12 tgl Exp $
*-------------------------------------------------------------------------
*/
for (i = 0; i < key->nentries; i++)
startScanEntry(index, ginstate, key->scanEntry + i);
- memset(key->entryRes, TRUE, sizeof(bool) * key->nentries);
key->isFinished = FALSE;
key->firstCall = FALSE;
for (;;)
{
- entry->offset++;
-
- if (entry->offset <= entry->nlist)
+ if (entry->offset < entry->nlist)
{
- entry->curItem = entry->list[entry->offset - 1];
+ entry->curItem = entry->list[entry->offset++];
return;
}
if (blkno == InvalidBlockNumber)
{
ReleaseBuffer(entry->buffer);
- ItemPointerSet(&entry->curItem, InvalidBlockNumber, InvalidOffsetNumber);
+ ItemPointerSetInvalid(&entry->curItem);
entry->buffer = InvalidBuffer;
entry->isFinished = TRUE;
return;
page = BufferGetPage(entry->buffer);
entry->offset = InvalidOffsetNumber;
- if (!ItemPointerIsValid(&entry->curItem) || findItemInPage(page, &entry->curItem, &entry->offset))
+ if (!ItemPointerIsValid(&entry->curItem) ||
+ findItemInPage(page, &entry->curItem, &entry->offset))
{
/*
* Found position equal to or greater than stored
LockBuffer(entry->buffer, GIN_UNLOCK);
if (!ItemPointerIsValid(&entry->curItem) ||
- compareItemPointers(&entry->curItem, entry->list + entry->offset - 1) == 0)
+ compareItemPointers(&entry->curItem,
+ entry->list + entry->offset - 1) == 0)
{
/*
* First pages are deleted or empty, or we found exact
#define dropItem(e) ( gin_rand() > ((double)GinFuzzySearchLimit)/((double)((e)->predictNumberResult)) )
/*
- * Sets entry->curItem to new found heap item pointer for one
- * entry of one scan key
+ * Sets entry->curItem to next heap item pointer for one entry of one scan key,
+ * or sets entry->isFinished to TRUE if there are no more.
*/
-static bool
+static void
entryGetItem(Relation index, GinScanEntry entry)
{
+ Assert(!entry->isFinished);
+
if (entry->master)
{
entry->isFinished = entry->master->isFinished;
if (entry->partialMatchResult == NULL)
{
- ItemPointerSet(&entry->curItem, InvalidBlockNumber, InvalidOffsetNumber);
+ ItemPointerSetInvalid(&entry->curItem);
tbm_end_iterate(entry->partialMatchIterator);
entry->partialMatchIterator = NULL;
entry->isFinished = TRUE;
entry->curItem = entry->list[entry->offset - 1];
else
{
- ItemPointerSet(&entry->curItem, InvalidBlockNumber, InvalidOffsetNumber);
+ ItemPointerSetInvalid(&entry->curItem);
entry->isFinished = TRUE;
}
}
do
{
entryGetNextItem(index, entry);
- } while (entry->isFinished == FALSE && entry->reduceResult == TRUE && dropItem(entry));
+ } while (entry->isFinished == FALSE &&
+ entry->reduceResult == TRUE &&
+ dropItem(entry));
}
-
- return entry->isFinished;
}
/*
- * Sets key->curItem to new found heap item pointer for one scan key
- * Returns isFinished, ie TRUE means we did NOT get a new item pointer!
- * Also, *keyrecheck is set true if recheck is needed for this scan key.
- * Note: lossy page could be returned after items from the same page.
+ * Sets key->curItem to next heap item pointer for one scan key, advancing
+ * past any item pointers <= advancePast.
+ * Sets key->isFinished to TRUE if there are no more.
+ *
+ * On success, key->recheckCurItem is set true iff recheck is needed for this
+ * item pointer (including the case where the item pointer is a lossy page
+ * pointer).
+ *
+ * Note: lossy page could be returned after single items from the same page.
+ * This is OK since the results will just be used to build a bitmap; we'll
+ * set a bitmap entry more than once, but never actually return a row twice.
*/
-static bool
+static void
keyGetItem(Relation index, GinState *ginstate, MemoryContext tempCtx,
- GinScanKey key, bool *keyrecheck)
+ GinScanKey key, ItemPointer advancePast)
{
+ ItemPointerData myAdvancePast = *advancePast;
uint32 i;
+ uint32 lossyEntry;
+ bool haveLossyEntry;
GinScanEntry entry;
bool res;
MemoryContext oldCtx;
- if (key->isFinished)
- return TRUE;
+ Assert(!key->isFinished);
do
{
/*
- * move forward from previously value and set new curItem, which is
- * minimal from entries->curItems. Lossy page is encoded by
- * ItemPointer with max value for offset (0xffff), so if there is an
- * non-lossy entries on lossy page they will returned too and after
- * that the whole page. That's not a problem for resulting tidbitmap.
+ * Advance any entries that are <= myAdvancePast. In particular,
+ * since entry->curItem was initialized with ItemPointerSetMin, this
+ * ensures we fetch the first item for each entry on the first call.
+ * Then set key->curItem to the minimum of the valid entry curItems.
+ *
+ * Note: a lossy-page entry is encoded by a ItemPointer with max value
+ * for offset (0xffff), so that it will sort after any exact entries
+ * for the same page. So we'll prefer to return exact pointers not
+ * lossy pointers, which is good. Also, when we advance past an exact
+ * entry after processing it, we will not advance past lossy entries
+ * for the same page in other keys, which is NECESSARY for correct
+ * results (since we might have additional entries for the same page
+ * in the first key).
*/
ItemPointerSetMax(&key->curItem);
+
for (i = 0; i < key->nentries; i++)
{
entry = key->scanEntry + i;
- if (key->entryRes[i])
- {
- /*
- * Move forward only entries which was the least on previous
- * call, key->entryRes[i] points that current entry was a
- * result of loop/call.
- */
- if (entry->isFinished == FALSE && entryGetItem(index, entry) == FALSE)
- {
- if (compareItemPointers(&entry->curItem, &key->curItem) < 0)
- key->curItem = entry->curItem;
- }
- else
- key->entryRes[i] = FALSE;
- }
- else if (entry->isFinished == FALSE)
- {
- if (compareItemPointers(&entry->curItem, &key->curItem) < 0)
- key->curItem = entry->curItem;
- }
+ while (entry->isFinished == FALSE &&
+ compareItemPointers(&entry->curItem, &myAdvancePast) <= 0)
+ entryGetItem(index, entry);
+
+ if (entry->isFinished == FALSE &&
+ compareItemPointers(&entry->curItem, &key->curItem) < 0)
+ key->curItem = entry->curItem;
}
if (ItemPointerIsMax(&key->curItem))
{
/* all entries are finished */
key->isFinished = TRUE;
- return TRUE;
+ return;
}
/*
- * Now key->curItem contains closest ItemPointer to previous result.
- *
- * if key->nentries == 1 then the consistentFn should always succeed,
- * but we must call it anyway to find out the recheck status.
+ * Now key->curItem contains first ItemPointer after previous result.
+ * Advance myAdvancePast to this value, so that if the consistentFn
+ * rejects the entry and we loop around again, we will advance to the
+ * next available item pointer.
*/
+ myAdvancePast = key->curItem;
- /*----------
- * entryRes array is used for:
- * - as an argument for consistentFn
- * - entry->curItem with corresponding key->entryRes[i] == false are
- * greater than key->curItem, so next loop/call they should be
- * renewed by entryGetItem(). So, we need to set up an array before
- * checking of lossy page.
- *----------
+ /*
+ * Prepare entryRes array to be passed to consistentFn.
+ *
+ * If key->nentries == 1 then the consistentFn should always succeed,
+ * but we must call it anyway to find out the recheck status.
+ *
+ * Lossy-page entries pose a problem, since we don't know the correct
+ * entryRes state to pass to the consistentFn, and we also don't know
+ * what its combining logic will be (could be AND, OR, or even NOT).
+ * Our strategy for a single lossy-page entry is to try the
+ * consistentFn both ways and return a hit if it accepts either one
+ * (forcing the hit to be marked lossy so it will be rechecked).
+ *
+ * This idea could be generalized to more than one lossy-page entry,
+ * but ideally lossy-page entries should be infrequent so it would
+ * seldom be the case that we have more than one. If we do find more
+ * than one, we just punt and return the item as lossy.
+ *
+ * Note that only lossy-page entries pointing to the current item's
+ * page should trigger this processing.
*/
+ lossyEntry = 0;
+ haveLossyEntry = false;
for (i = 0; i < key->nentries; i++)
{
entry = key->scanEntry + i;
- if (entry->isFinished == FALSE &&
- compareItemPointers(&entry->curItem, &key->curItem) == 0)
+ if (entry->isFinished)
+ key->entryRes[i] = FALSE;
+ else if (ItemPointerIsLossyPage(&entry->curItem) &&
+ GinItemPointerGetBlockNumber(&entry->curItem) ==
+ GinItemPointerGetBlockNumber(&key->curItem))
+ {
+ if (haveLossyEntry)
+ {
+ /* Too many lossy entries, punt */
+ key->recheckCurItem = true;
+ return;
+ }
+ lossyEntry = i;
+ haveLossyEntry = true;
+ /* initially assume TRUE */
+ key->entryRes[i] = TRUE;
+ }
+ else if (compareItemPointers(&entry->curItem, &key->curItem) == 0)
key->entryRes[i] = TRUE;
else
key->entryRes[i] = FALSE;
}
+ oldCtx = MemoryContextSwitchTo(tempCtx);
+
/*
- * Initialize *keyrecheck in case the consistentFn doesn't know it
+ * Initialize recheckCurItem in case the consistentFn doesn't know it
* should set it. The safe assumption in that case is to force
* recheck.
*/
- *keyrecheck = true;
-
- /*
- * If one of the entry's scans returns lossy result, return it without
- * further checking - we can't call consistentFn for lack of data.
- */
- if (ItemPointerIsLossyPage(&key->curItem))
- return FALSE;
+ key->recheckCurItem = true;
- oldCtx = MemoryContextSwitchTo(tempCtx);
res = DatumGetBool(FunctionCall6(&ginstate->consistentFn[key->attnum - 1],
PointerGetDatum(key->entryRes),
UInt16GetDatum(key->strategy),
key->query,
UInt32GetDatum(key->nentries),
PointerGetDatum(key->extra_data),
- PointerGetDatum(keyrecheck)));
+ PointerGetDatum(&key->recheckCurItem)));
+
+ if (!res && haveLossyEntry)
+ {
+ /* try the other way for the lossy item */
+ key->entryRes[lossyEntry] = FALSE;
+ key->recheckCurItem = true;
+
+ res = DatumGetBool(FunctionCall6(&ginstate->consistentFn[key->attnum - 1],
+ PointerGetDatum(key->entryRes),
+ UInt16GetDatum(key->strategy),
+ key->query,
+ UInt32GetDatum(key->nentries),
+ PointerGetDatum(key->extra_data),
+ PointerGetDatum(&key->recheckCurItem)));
+ }
+
MemoryContextSwitchTo(oldCtx);
MemoryContextReset(tempCtx);
- } while (!res);
- return FALSE;
+ /* If we matched a lossy entry, force recheckCurItem = true */
+ if (haveLossyEntry)
+ key->recheckCurItem = true;
+ } while (!res);
}
j;
/*
- * Resets entryRes
+ * Reset entryRes
*/
for (i = 0; i < so->nkeys; i++)
{
}
/*
- * Get heap item pointer from scan
- * returns true if found
+ * Get next heap item pointer (after advancePast) from scan.
+ * Returns true if anything found.
+ * On success, *item and *recheck are set.
+ *
+ * Note: this is very nearly the same logic as in keyGetItem(), except
+ * that we know the keys are to be combined with AND logic, whereas in
+ * keyGetItem() the combination logic is known only to the consistentFn.
*/
static bool
-scanGetItem(IndexScanDesc scan, ItemPointerData *item, bool *recheck)
+scanGetItem(IndexScanDesc scan, ItemPointer advancePast,
+ ItemPointerData *item, bool *recheck)
{
GinScanOpaque so = (GinScanOpaque) scan->opaque;
+ ItemPointerData myAdvancePast = *advancePast;
uint32 i;
- bool keyrecheck;
+ bool match;
+
+ for (;;)
+ {
+ /*
+ * Advance any keys that are <= myAdvancePast. In particular,
+ * since key->curItem was initialized with ItemPointerSetMin, this
+ * ensures we fetch the first item for each key on the first call.
+ * Then set *item to the minimum of the key curItems.
+ *
+ * Note: a lossy-page entry is encoded by a ItemPointer with max value
+ * for offset (0xffff), so that it will sort after any exact entries
+ * for the same page. So we'll prefer to return exact pointers not
+ * lossy pointers, which is good. Also, when we advance past an exact
+ * entry after processing it, we will not advance past lossy entries
+ * for the same page in other keys, which is NECESSARY for correct
+ * results (since we might have additional entries for the same page
+ * in the first key).
+ */
+ ItemPointerSetMax(item);
+
+ for (i = 0; i < so->nkeys; i++)
+ {
+ GinScanKey key = so->keys + i;
+
+ while (key->isFinished == FALSE &&
+ compareItemPointers(&key->curItem, &myAdvancePast) <= 0)
+ keyGetItem(scan->indexRelation, &so->ginstate, so->tempCtx,
+ key, &myAdvancePast);
+
+ if (key->isFinished)
+ return FALSE; /* finished one of keys */
+
+ if (compareItemPointers(&key->curItem, item) < 0)
+ *item = key->curItem;
+ }
+
+ Assert(!ItemPointerIsMax(item));
+
+ /*
+ * Now *item contains first ItemPointer after previous result.
+ *
+ * The item is a valid hit only if all the keys returned either
+ * that exact TID, or a lossy reference to the same page.
+ */
+ match = true;
+ for (i = 0; i < so->nkeys; i++)
+ {
+ GinScanKey key = so->keys + i;
+
+ if (compareItemPointers(item, &key->curItem) == 0)
+ continue;
+ if (ItemPointerIsLossyPage(&key->curItem) &&
+ GinItemPointerGetBlockNumber(&key->curItem) ==
+ GinItemPointerGetBlockNumber(item))
+ continue;
+ match = false;
+ break;
+ }
+
+ if (match)
+ break;
+
+ /*
+ * No hit. Update myAdvancePast to this TID, so that on the next
+ * pass we'll move to the next possible entry.
+ */
+ myAdvancePast = *item;
+ }
/*
- * We return recheck = true if any of the keyGetItem calls return
- * keyrecheck = true. Note that because the second loop might advance
- * some keys, this could theoretically be too conservative. In practice
- * though, we expect that a consistentFn's recheck result will depend only
- * on the operator and the query, so for any one key it should stay the
- * same regardless of advancing to new items. So it's not worth working
- * harder.
+ * We must return recheck = true if any of the keys are marked recheck.
*/
*recheck = false;
-
- ItemPointerSetMin(item);
for (i = 0; i < so->nkeys; i++)
{
GinScanKey key = so->keys + i;
- if (keyGetItem(scan->indexRelation, &so->ginstate, so->tempCtx,
- key, &keyrecheck))
- return FALSE; /* finished one of keys */
- if (compareItemPointers(item, &key->curItem) < 0)
- *item = key->curItem;
- *recheck |= keyrecheck;
- }
-
- for (i = 1; i <= so->nkeys; i++)
- {
- GinScanKey key = so->keys + i - 1;
-
- for (;;)
+ if (key->recheckCurItem)
{
- int cmp = compareItemPointers(item, &key->curItem);
-
- if (cmp != 0 && (ItemPointerIsLossyPage(item) || ItemPointerIsLossyPage(&key->curItem)))
- {
- /*
- * if one of ItemPointers points to the whole page then
- * compare only page's number
- */
- if (ItemPointerGetBlockNumber(item) == ItemPointerGetBlockNumber(&key->curItem))
- cmp = 0;
- else
- cmp = (ItemPointerGetBlockNumber(item) > ItemPointerGetBlockNumber(&key->curItem)) ? 1 : -1;
- }
-
- if (cmp == 0)
- break;
- else if (cmp > 0)
- {
- if (keyGetItem(scan->indexRelation, &so->ginstate, so->tempCtx,
- key, &keyrecheck))
- return FALSE; /* finished one of keys */
- *recheck |= keyrecheck;
- }
- else
- { /* returns to begin */
- *item = key->curItem;
- i = 0;
- break;
- }
+ *recheck = true;
+ break;
}
}
IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
TIDBitmap *tbm = (TIDBitmap *) PG_GETARG_POINTER(1);
int64 ntids;
+ ItemPointerData iptr;
+ bool recheck;
if (GinIsNewKey(scan))
newScanKey(scan);
*/
startScan(scan);
+ ItemPointerSetMin(&iptr);
+
for (;;)
{
- ItemPointerData iptr;
- bool recheck;
-
CHECK_FOR_INTERRUPTS();
- if (!scanGetItem(scan, &iptr, &recheck))
+ if (!scanGetItem(scan, &iptr, &iptr, &recheck))
break;
if (ItemPointerIsLossyPage(&iptr))