mirror of
https://github.com/postgres/postgres.git
synced 2025-07-18 17:42:25 +03:00
Rewrite the key-combination logic in GIN's keyGetItem() and scanGetItem()
routines to make them behave better in the presence of "lossy" index pointers. The previous coding was outright incorrect for some cases, as recently reported by Artur Dabrowski: scanGetItem would fail to return index entries in cases where one index key had multiple exact pointers on the same page as another key had a lossy pointer. Also, keyGetItem was extremely inefficient for cases where a single index key generates multiple "entry" streams, such as an @@ operator with a multiple-clause tsquery. The presence of a lossy page pointer in any one stream defeated its ability to use the opclass consistentFn, resulting in probing many heap pages that didn't really need to be visited. In Artur's example case, a query like WHERE tsvector @@ to_tsquery('a & b') was about 50X slower than the theoretically equivalent WHERE tsvector @@ to_tsquery('a') AND tsvector @@ to_tsquery('b') The way that I chose to fix this was to have GIN call the consistentFn twice with both TRUE and FALSE values for the in-doubt entry stream, returning a hit if either call produces TRUE, but not if they both return FALSE. The code handles this for the case of a single in-doubt entry stream, but punts (falling back to the stupid behavior) if there's more than one lossy reference to the same page. The idea could be scaled up to deal with multiple lossy references, but I think that would probably be wasted complexity. At least to judge by Artur's example, such cases don't occur often enough to be worth trying to optimize. Back-patch to 8.4. 8.3 did not have lossy GIN index pointers, so not subject to these problems.
This commit is contained in:
@ -8,7 +8,7 @@
|
|||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/access/gin/ginget.c,v 1.30 2010/02/26 02:00:33 momjian Exp $
|
* $PostgreSQL: pgsql/src/backend/access/gin/ginget.c,v 1.30.4.1 2010/07/31 00:31:04 tgl Exp $
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -412,7 +412,6 @@ startScanKey(Relation index, GinState *ginstate, GinScanKey key)
|
|||||||
for (i = 0; i < key->nentries; i++)
|
for (i = 0; i < key->nentries; i++)
|
||||||
startScanEntry(index, ginstate, key->scanEntry + i);
|
startScanEntry(index, ginstate, key->scanEntry + i);
|
||||||
|
|
||||||
memset(key->entryRes, TRUE, sizeof(bool) * key->nentries);
|
|
||||||
key->isFinished = FALSE;
|
key->isFinished = FALSE;
|
||||||
key->firstCall = FALSE;
|
key->firstCall = FALSE;
|
||||||
|
|
||||||
@ -461,11 +460,9 @@ entryGetNextItem(Relation index, GinScanEntry entry)
|
|||||||
|
|
||||||
for (;;)
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -484,7 +481,7 @@ entryGetNextItem(Relation index, GinScanEntry entry)
|
|||||||
if (blkno == InvalidBlockNumber)
|
if (blkno == InvalidBlockNumber)
|
||||||
{
|
{
|
||||||
ReleaseBuffer(entry->buffer);
|
ReleaseBuffer(entry->buffer);
|
||||||
ItemPointerSet(&entry->curItem, InvalidBlockNumber, InvalidOffsetNumber);
|
ItemPointerSetInvalid(&entry->curItem);
|
||||||
entry->buffer = InvalidBuffer;
|
entry->buffer = InvalidBuffer;
|
||||||
entry->isFinished = TRUE;
|
entry->isFinished = TRUE;
|
||||||
return;
|
return;
|
||||||
@ -495,7 +492,8 @@ entryGetNextItem(Relation index, GinScanEntry entry)
|
|||||||
page = BufferGetPage(entry->buffer);
|
page = BufferGetPage(entry->buffer);
|
||||||
|
|
||||||
entry->offset = InvalidOffsetNumber;
|
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
|
* Found position equal to or greater than stored
|
||||||
@ -507,7 +505,8 @@ entryGetNextItem(Relation index, GinScanEntry entry)
|
|||||||
LockBuffer(entry->buffer, GIN_UNLOCK);
|
LockBuffer(entry->buffer, GIN_UNLOCK);
|
||||||
|
|
||||||
if (!ItemPointerIsValid(&entry->curItem) ||
|
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
|
* First pages are deleted or empty, or we found exact
|
||||||
@ -532,12 +531,14 @@ entryGetNextItem(Relation index, GinScanEntry entry)
|
|||||||
#define dropItem(e) ( gin_rand() > ((double)GinFuzzySearchLimit)/((double)((e)->predictNumberResult)) )
|
#define dropItem(e) ( gin_rand() > ((double)GinFuzzySearchLimit)/((double)((e)->predictNumberResult)) )
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Sets entry->curItem to new found heap item pointer for one
|
* Sets entry->curItem to next heap item pointer for one entry of one scan key,
|
||||||
* 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)
|
entryGetItem(Relation index, GinScanEntry entry)
|
||||||
{
|
{
|
||||||
|
Assert(!entry->isFinished);
|
||||||
|
|
||||||
if (entry->master)
|
if (entry->master)
|
||||||
{
|
{
|
||||||
entry->isFinished = entry->master->isFinished;
|
entry->isFinished = entry->master->isFinished;
|
||||||
@ -554,7 +555,7 @@ entryGetItem(Relation index, GinScanEntry entry)
|
|||||||
|
|
||||||
if (entry->partialMatchResult == NULL)
|
if (entry->partialMatchResult == NULL)
|
||||||
{
|
{
|
||||||
ItemPointerSet(&entry->curItem, InvalidBlockNumber, InvalidOffsetNumber);
|
ItemPointerSetInvalid(&entry->curItem);
|
||||||
tbm_end_iterate(entry->partialMatchIterator);
|
tbm_end_iterate(entry->partialMatchIterator);
|
||||||
entry->partialMatchIterator = NULL;
|
entry->partialMatchIterator = NULL;
|
||||||
entry->isFinished = TRUE;
|
entry->isFinished = TRUE;
|
||||||
@ -600,7 +601,7 @@ entryGetItem(Relation index, GinScanEntry entry)
|
|||||||
entry->curItem = entry->list[entry->offset - 1];
|
entry->curItem = entry->list[entry->offset - 1];
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ItemPointerSet(&entry->curItem, InvalidBlockNumber, InvalidOffsetNumber);
|
ItemPointerSetInvalid(&entry->curItem);
|
||||||
entry->isFinished = TRUE;
|
entry->isFinished = TRUE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -609,127 +610,175 @@ entryGetItem(Relation index, GinScanEntry entry)
|
|||||||
do
|
do
|
||||||
{
|
{
|
||||||
entryGetNextItem(index, entry);
|
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
|
* Sets key->curItem to next heap item pointer for one scan key, advancing
|
||||||
* Returns isFinished, ie TRUE means we did NOT get a new item pointer!
|
* past any item pointers <= advancePast.
|
||||||
* Also, *keyrecheck is set true if recheck is needed for this scan key.
|
* Sets key->isFinished to TRUE if there are no more.
|
||||||
* Note: lossy page could be returned after items from the same page.
|
*
|
||||||
|
* 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,
|
keyGetItem(Relation index, GinState *ginstate, MemoryContext tempCtx,
|
||||||
GinScanKey key, bool *keyrecheck)
|
GinScanKey key, ItemPointer advancePast)
|
||||||
{
|
{
|
||||||
|
ItemPointerData myAdvancePast = *advancePast;
|
||||||
uint32 i;
|
uint32 i;
|
||||||
|
uint32 lossyEntry;
|
||||||
|
bool haveLossyEntry;
|
||||||
GinScanEntry entry;
|
GinScanEntry entry;
|
||||||
bool res;
|
bool res;
|
||||||
MemoryContext oldCtx;
|
MemoryContext oldCtx;
|
||||||
|
|
||||||
if (key->isFinished)
|
Assert(!key->isFinished);
|
||||||
return TRUE;
|
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* move forward from previously value and set new curItem, which is
|
* Advance any entries that are <= myAdvancePast. In particular,
|
||||||
* minimal from entries->curItems. Lossy page is encoded by
|
* since entry->curItem was initialized with ItemPointerSetMin, this
|
||||||
* ItemPointer with max value for offset (0xffff), so if there is an
|
* ensures we fetch the first item for each entry on the first call.
|
||||||
* non-lossy entries on lossy page they will returned too and after
|
* Then set key->curItem to the minimum of the valid entry curItems.
|
||||||
* that the whole page. That's not a problem for resulting tidbitmap.
|
*
|
||||||
|
* 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);
|
ItemPointerSetMax(&key->curItem);
|
||||||
|
|
||||||
for (i = 0; i < key->nentries; i++)
|
for (i = 0; i < key->nentries; i++)
|
||||||
{
|
{
|
||||||
entry = key->scanEntry + i;
|
entry = key->scanEntry + i;
|
||||||
|
|
||||||
if (key->entryRes[i])
|
while (entry->isFinished == FALSE &&
|
||||||
{
|
compareItemPointers(&entry->curItem, &myAdvancePast) <= 0)
|
||||||
/*
|
entryGetItem(index, entry);
|
||||||
* Move forward only entries which was the least on previous
|
|
||||||
* call, key->entryRes[i] points that current entry was a
|
if (entry->isFinished == FALSE &&
|
||||||
* result of loop/call.
|
compareItemPointers(&entry->curItem, &key->curItem) < 0)
|
||||||
*/
|
|
||||||
if (entry->isFinished == FALSE && entryGetItem(index, entry) == FALSE)
|
|
||||||
{
|
|
||||||
if (compareItemPointers(&entry->curItem, &key->curItem) < 0)
|
|
||||||
key->curItem = entry->curItem;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ItemPointerIsMax(&key->curItem))
|
if (ItemPointerIsMax(&key->curItem))
|
||||||
{
|
{
|
||||||
/* all entries are finished */
|
/* all entries are finished */
|
||||||
key->isFinished = TRUE;
|
key->isFinished = TRUE;
|
||||||
return TRUE;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Now key->curItem contains closest ItemPointer to previous result.
|
* Now key->curItem contains first ItemPointer after previous result.
|
||||||
*
|
* Advance myAdvancePast to this value, so that if the consistentFn
|
||||||
* if key->nentries == 1 then the consistentFn should always succeed,
|
* rejects the entry and we loop around again, we will advance to the
|
||||||
* but we must call it anyway to find out the recheck status.
|
* next available item pointer.
|
||||||
*/
|
*/
|
||||||
|
myAdvancePast = key->curItem;
|
||||||
|
|
||||||
/*----------
|
/*
|
||||||
* entryRes array is used for:
|
* Prepare entryRes array to be passed to consistentFn.
|
||||||
* - as an argument for consistentFn
|
*
|
||||||
* - entry->curItem with corresponding key->entryRes[i] == false are
|
* If key->nentries == 1 then the consistentFn should always succeed,
|
||||||
* greater than key->curItem, so next loop/call they should be
|
* but we must call it anyway to find out the recheck status.
|
||||||
* renewed by entryGetItem(). So, we need to set up an array before
|
*
|
||||||
* checking of lossy page.
|
* 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++)
|
for (i = 0; i < key->nentries; i++)
|
||||||
{
|
{
|
||||||
entry = key->scanEntry + i;
|
entry = key->scanEntry + i;
|
||||||
|
|
||||||
if (entry->isFinished == FALSE &&
|
if (entry->isFinished)
|
||||||
compareItemPointers(&entry->curItem, &key->curItem) == 0)
|
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;
|
key->entryRes[i] = TRUE;
|
||||||
else
|
else
|
||||||
key->entryRes[i] = FALSE;
|
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
|
* should set it. The safe assumption in that case is to force
|
||||||
* recheck.
|
* recheck.
|
||||||
*/
|
*/
|
||||||
*keyrecheck = true;
|
key->recheckCurItem = 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;
|
|
||||||
|
|
||||||
oldCtx = MemoryContextSwitchTo(tempCtx);
|
|
||||||
res = DatumGetBool(FunctionCall6(&ginstate->consistentFn[key->attnum - 1],
|
res = DatumGetBool(FunctionCall6(&ginstate->consistentFn[key->attnum - 1],
|
||||||
PointerGetDatum(key->entryRes),
|
PointerGetDatum(key->entryRes),
|
||||||
UInt16GetDatum(key->strategy),
|
UInt16GetDatum(key->strategy),
|
||||||
key->query,
|
key->query,
|
||||||
UInt32GetDatum(key->nentries),
|
UInt32GetDatum(key->nentries),
|
||||||
PointerGetDatum(key->extra_data),
|
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);
|
MemoryContextSwitchTo(oldCtx);
|
||||||
MemoryContextReset(tempCtx);
|
MemoryContextReset(tempCtx);
|
||||||
} while (!res);
|
|
||||||
|
|
||||||
return FALSE;
|
/* If we matched a lossy entry, force recheckCurItem = true */
|
||||||
|
if (haveLossyEntry)
|
||||||
|
key->recheckCurItem = true;
|
||||||
|
} while (!res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -904,7 +953,7 @@ collectDatumForItem(IndexScanDesc scan, pendingPosition *pos)
|
|||||||
j;
|
j;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Resets entryRes
|
* Reset entryRes
|
||||||
*/
|
*/
|
||||||
for (i = 0; i < so->nkeys; i++)
|
for (i = 0; i < so->nkeys; i++)
|
||||||
{
|
{
|
||||||
@ -1145,77 +1194,105 @@ scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get heap item pointer from scan
|
* Get next heap item pointer (after advancePast) from scan.
|
||||||
* returns true if found
|
* 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
|
static bool
|
||||||
scanGetItem(IndexScanDesc scan, ItemPointerData *item, bool *recheck)
|
scanGetItem(IndexScanDesc scan, ItemPointer advancePast,
|
||||||
|
ItemPointerData *item, bool *recheck)
|
||||||
{
|
{
|
||||||
GinScanOpaque so = (GinScanOpaque) scan->opaque;
|
GinScanOpaque so = (GinScanOpaque) scan->opaque;
|
||||||
|
ItemPointerData myAdvancePast = *advancePast;
|
||||||
uint32 i;
|
uint32 i;
|
||||||
bool keyrecheck;
|
bool match;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
/*
|
/*
|
||||||
* We return recheck = true if any of the keyGetItem calls return
|
* Advance any keys that are <= myAdvancePast. In particular,
|
||||||
* keyrecheck = true. Note that because the second loop might advance
|
* since key->curItem was initialized with ItemPointerSetMin, this
|
||||||
* some keys, this could theoretically be too conservative. In practice
|
* ensures we fetch the first item for each key on the first call.
|
||||||
* though, we expect that a consistentFn's recheck result will depend only
|
* Then set *item to the minimum of the key curItems.
|
||||||
* 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
|
* Note: a lossy-page entry is encoded by a ItemPointer with max value
|
||||||
* harder.
|
* 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).
|
||||||
*/
|
*/
|
||||||
*recheck = false;
|
ItemPointerSetMax(item);
|
||||||
|
|
||||||
ItemPointerSetMin(item);
|
|
||||||
for (i = 0; i < so->nkeys; i++)
|
for (i = 0; i < so->nkeys; i++)
|
||||||
{
|
{
|
||||||
GinScanKey key = so->keys + i;
|
GinScanKey key = so->keys + i;
|
||||||
|
|
||||||
if (keyGetItem(scan->indexRelation, &so->ginstate, so->tempCtx,
|
while (key->isFinished == FALSE &&
|
||||||
key, &keyrecheck))
|
compareItemPointers(&key->curItem, &myAdvancePast) <= 0)
|
||||||
|
keyGetItem(scan->indexRelation, &so->ginstate, so->tempCtx,
|
||||||
|
key, &myAdvancePast);
|
||||||
|
|
||||||
|
if (key->isFinished)
|
||||||
return FALSE; /* finished one of keys */
|
return FALSE; /* finished one of keys */
|
||||||
if (compareItemPointers(item, &key->curItem) < 0)
|
|
||||||
|
if (compareItemPointers(&key->curItem, item) < 0)
|
||||||
*item = key->curItem;
|
*item = key->curItem;
|
||||||
*recheck |= keyrecheck;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 1; i <= so->nkeys; i++)
|
Assert(!ItemPointerIsMax(item));
|
||||||
{
|
|
||||||
GinScanKey key = so->keys + i - 1;
|
|
||||||
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
int cmp = compareItemPointers(item, &key->curItem);
|
|
||||||
|
|
||||||
if (cmp != 0 && (ItemPointerIsLossyPage(item) || ItemPointerIsLossyPage(&key->curItem)))
|
|
||||||
{
|
|
||||||
/*
|
/*
|
||||||
* if one of ItemPointers points to the whole page then
|
* Now *item contains first ItemPointer after previous result.
|
||||||
* compare only page's number
|
*
|
||||||
|
* The item is a valid hit only if all the keys returned either
|
||||||
|
* that exact TID, or a lossy reference to the same page.
|
||||||
*/
|
*/
|
||||||
if (ItemPointerGetBlockNumber(item) == ItemPointerGetBlockNumber(&key->curItem))
|
match = true;
|
||||||
cmp = 0;
|
for (i = 0; i < so->nkeys; i++)
|
||||||
else
|
{
|
||||||
cmp = (ItemPointerGetBlockNumber(item) > ItemPointerGetBlockNumber(&key->curItem)) ? 1 : -1;
|
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 (cmp == 0)
|
if (match)
|
||||||
break;
|
break;
|
||||||
else if (cmp > 0)
|
|
||||||
|
/*
|
||||||
|
* No hit. Update myAdvancePast to this TID, so that on the next
|
||||||
|
* pass we'll move to the next possible entry.
|
||||||
|
*/
|
||||||
|
myAdvancePast = *item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We must return recheck = true if any of the keys are marked recheck.
|
||||||
|
*/
|
||||||
|
*recheck = false;
|
||||||
|
for (i = 0; i < so->nkeys; i++)
|
||||||
{
|
{
|
||||||
if (keyGetItem(scan->indexRelation, &so->ginstate, so->tempCtx,
|
GinScanKey key = so->keys + i;
|
||||||
key, &keyrecheck))
|
|
||||||
return FALSE; /* finished one of keys */
|
if (key->recheckCurItem)
|
||||||
*recheck |= keyrecheck;
|
{
|
||||||
}
|
*recheck = true;
|
||||||
else
|
|
||||||
{ /* returns to begin */
|
|
||||||
*item = key->curItem;
|
|
||||||
i = 0;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@ -1229,6 +1306,8 @@ gingetbitmap(PG_FUNCTION_ARGS)
|
|||||||
IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
|
IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
|
||||||
TIDBitmap *tbm = (TIDBitmap *) PG_GETARG_POINTER(1);
|
TIDBitmap *tbm = (TIDBitmap *) PG_GETARG_POINTER(1);
|
||||||
int64 ntids;
|
int64 ntids;
|
||||||
|
ItemPointerData iptr;
|
||||||
|
bool recheck;
|
||||||
|
|
||||||
if (GinIsNewKey(scan))
|
if (GinIsNewKey(scan))
|
||||||
newScanKey(scan);
|
newScanKey(scan);
|
||||||
@ -1255,14 +1334,13 @@ gingetbitmap(PG_FUNCTION_ARGS)
|
|||||||
*/
|
*/
|
||||||
startScan(scan);
|
startScan(scan);
|
||||||
|
|
||||||
|
ItemPointerSetMin(&iptr);
|
||||||
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
ItemPointerData iptr;
|
|
||||||
bool recheck;
|
|
||||||
|
|
||||||
CHECK_FOR_INTERRUPTS();
|
CHECK_FOR_INTERRUPTS();
|
||||||
|
|
||||||
if (!scanGetItem(scan, &iptr, &recheck))
|
if (!scanGetItem(scan, &iptr, &iptr, &recheck))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (ItemPointerIsLossyPage(&iptr))
|
if (ItemPointerIsLossyPage(&iptr))
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/access/gin/ginscan.c,v 1.26 2010/01/18 11:50:43 teodor Exp $
|
* $PostgreSQL: pgsql/src/backend/access/gin/ginscan.c,v 1.26.6.1 2010/07/31 00:31:04 tgl Exp $
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ fillScanKey(GinState *ginstate, GinScanKey key, OffsetNumber attnum, Datum query
|
|||||||
key->extra_data = extra_data;
|
key->extra_data = extra_data;
|
||||||
key->query = query;
|
key->query = query;
|
||||||
key->firstCall = TRUE;
|
key->firstCall = TRUE;
|
||||||
ItemPointerSet(&(key->curItem), InvalidBlockNumber, InvalidOffsetNumber);
|
ItemPointerSetMin(&key->curItem);
|
||||||
|
|
||||||
for (i = 0; i < nEntryValues; i++)
|
for (i = 0; i < nEntryValues; i++)
|
||||||
{
|
{
|
||||||
@ -59,7 +59,8 @@ fillScanKey(GinState *ginstate, GinScanKey key, OffsetNumber attnum, Datum query
|
|||||||
key->scanEntry[i].entry = entryValues[i];
|
key->scanEntry[i].entry = entryValues[i];
|
||||||
key->scanEntry[i].attnum = attnum;
|
key->scanEntry[i].attnum = attnum;
|
||||||
key->scanEntry[i].extra_data = (extra_data) ? extra_data[i] : NULL;
|
key->scanEntry[i].extra_data = (extra_data) ? extra_data[i] : NULL;
|
||||||
ItemPointerSet(&(key->scanEntry[i].curItem), InvalidBlockNumber, InvalidOffsetNumber);
|
ItemPointerSetMin(&key->scanEntry[i].curItem);
|
||||||
|
key->scanEntry[i].isFinished = FALSE;
|
||||||
key->scanEntry[i].offset = InvalidOffsetNumber;
|
key->scanEntry[i].offset = InvalidOffsetNumber;
|
||||||
key->scanEntry[i].buffer = InvalidBuffer;
|
key->scanEntry[i].buffer = InvalidBuffer;
|
||||||
key->scanEntry[i].partialMatch = NULL;
|
key->scanEntry[i].partialMatch = NULL;
|
||||||
@ -100,14 +101,15 @@ resetScanKeys(GinScanKey keys, uint32 nkeys)
|
|||||||
GinScanKey key = keys + i;
|
GinScanKey key = keys + i;
|
||||||
|
|
||||||
key->firstCall = TRUE;
|
key->firstCall = TRUE;
|
||||||
ItemPointerSet(&(key->curItem), InvalidBlockNumber, InvalidOffsetNumber);
|
ItemPointerSetMin(&key->curItem);
|
||||||
|
|
||||||
for (j = 0; j < key->nentries; j++)
|
for (j = 0; j < key->nentries; j++)
|
||||||
{
|
{
|
||||||
if (key->scanEntry[j].buffer != InvalidBuffer)
|
if (key->scanEntry[j].buffer != InvalidBuffer)
|
||||||
ReleaseBuffer(key->scanEntry[i].buffer);
|
ReleaseBuffer(key->scanEntry[i].buffer);
|
||||||
|
|
||||||
ItemPointerSet(&(key->scanEntry[j].curItem), InvalidBlockNumber, InvalidOffsetNumber);
|
ItemPointerSetMin(&key->scanEntry[j].curItem);
|
||||||
|
key->scanEntry[j].isFinished = FALSE;
|
||||||
key->scanEntry[j].offset = InvalidOffsetNumber;
|
key->scanEntry[j].offset = InvalidOffsetNumber;
|
||||||
key->scanEntry[j].buffer = InvalidBuffer;
|
key->scanEntry[j].buffer = InvalidBuffer;
|
||||||
key->scanEntry[j].list = NULL;
|
key->scanEntry[j].list = NULL;
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
*
|
*
|
||||||
* Copyright (c) 2006-2010, PostgreSQL Global Development Group
|
* Copyright (c) 2006-2010, PostgreSQL Global Development Group
|
||||||
*
|
*
|
||||||
* $PostgreSQL: pgsql/src/include/access/gin.h,v 1.38 2010/02/26 02:01:20 momjian Exp $
|
* $PostgreSQL: pgsql/src/include/access/gin.h,v 1.38.4.1 2010/07/31 00:31:04 tgl Exp $
|
||||||
*--------------------------------------------------------------------------
|
*--------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
#ifndef GIN_H
|
#ifndef GIN_H
|
||||||
@ -520,6 +520,8 @@ typedef struct GinScanKeyData
|
|||||||
OffsetNumber attnum;
|
OffsetNumber attnum;
|
||||||
|
|
||||||
ItemPointerData curItem;
|
ItemPointerData curItem;
|
||||||
|
bool recheckCurItem;
|
||||||
|
|
||||||
bool firstCall;
|
bool firstCall;
|
||||||
bool isFinished;
|
bool isFinished;
|
||||||
} GinScanKeyData;
|
} GinScanKeyData;
|
||||||
|
Reference in New Issue
Block a user