Commit 56a57473 authored by Tom Lane's avatar Tom Lane

Refactor GIN's handling of duplicate search entries.

The original coding could combine duplicate entries only when they
originated from the same qual condition.  In particular it could not
combine cases where multiple qual conditions all give rise to full-index
scan requests, which is an expensive case well worth optimizing.  Refactor
so that duplicates are recognized across all the quals.
parent 002c105a
...@@ -371,6 +371,7 @@ startScanEntry(GinState *ginstate, GinScanEntry entry) ...@@ -371,6 +371,7 @@ startScanEntry(GinState *ginstate, GinScanEntry entry)
restartScanEntry: restartScanEntry:
entry->buffer = InvalidBuffer; entry->buffer = InvalidBuffer;
ItemPointerSetMin(&entry->curItem);
entry->offset = InvalidOffsetNumber; entry->offset = InvalidOffsetNumber;
entry->list = NULL; entry->list = NULL;
entry->nlist = 0; entry->nlist = 0;
...@@ -379,12 +380,6 @@ restartScanEntry: ...@@ -379,12 +380,6 @@ restartScanEntry:
entry->reduceResult = FALSE; entry->reduceResult = FALSE;
entry->predictNumberResult = 0; entry->predictNumberResult = 0;
if (entry->master != NULL)
{
entry->isFinished = entry->master->isFinished;
return;
}
/* /*
* we should find entry, and begin scan of posting tree or just store * we should find entry, and begin scan of posting tree or just store
* posting list in memory * posting list in memory
...@@ -499,16 +494,21 @@ restartScanEntry: ...@@ -499,16 +494,21 @@ restartScanEntry:
static void static void
startScanKey(GinState *ginstate, GinScanKey key) startScanKey(GinState *ginstate, GinScanKey key)
{ {
uint32 i; ItemPointerSetMin(&key->curItem);
key->curItemMatches = false;
if (!key->firstCall) key->recheckCurItem = false;
return; key->isFinished = false;
}
for (i = 0; i < key->nentries; i++) static void
startScanEntry(ginstate, key->scanEntry + i); startScan(IndexScanDesc scan)
{
GinScanOpaque so = (GinScanOpaque) scan->opaque;
GinState *ginstate = &so->ginstate;
uint32 i;
key->isFinished = FALSE; for (i = 0; i < so->totalentries; i++)
key->firstCall = FALSE; startScanEntry(ginstate, so->entries[i]);
if (GinFuzzySearchLimit > 0) if (GinFuzzySearchLimit > 0)
{ {
...@@ -519,27 +519,20 @@ startScanKey(GinState *ginstate, GinScanKey key) ...@@ -519,27 +519,20 @@ startScanKey(GinState *ginstate, GinScanKey key)
* minimal predictNumberResult. * minimal predictNumberResult.
*/ */
for (i = 0; i < key->nentries; i++) for (i = 0; i < so->totalentries; i++)
if (key->scanEntry[i].predictNumberResult <= key->nentries * GinFuzzySearchLimit) if (so->entries[i]->predictNumberResult <= so->totalentries * GinFuzzySearchLimit)
return; return;
for (i = 0; i < key->nentries; i++) for (i = 0; i < so->totalentries; i++)
if (key->scanEntry[i].predictNumberResult > key->nentries * GinFuzzySearchLimit) if (so->entries[i]->predictNumberResult > so->totalentries * GinFuzzySearchLimit)
{ {
key->scanEntry[i].predictNumberResult /= key->nentries; so->entries[i]->predictNumberResult /= so->totalentries;
key->scanEntry[i].reduceResult = TRUE; so->entries[i]->reduceResult = TRUE;
} }
} }
}
static void
startScan(IndexScanDesc scan)
{
uint32 i;
GinScanOpaque so = (GinScanOpaque) scan->opaque;
for (i = 0; i < so->nkeys; i++) for (i = 0; i < so->nkeys; i++)
startScanKey(&so->ginstate, so->keys + i); startScanKey(ginstate, so->keys + i);
} }
/* /*
...@@ -644,12 +637,7 @@ entryGetItem(GinState *ginstate, GinScanEntry entry) ...@@ -644,12 +637,7 @@ entryGetItem(GinState *ginstate, GinScanEntry entry)
{ {
Assert(!entry->isFinished); Assert(!entry->isFinished);
if (entry->master) if (entry->matchBitmap)
{
entry->isFinished = entry->master->isFinished;
entry->curItem = entry->master->curItem;
}
else if (entry->matchBitmap)
{ {
do do
{ {
...@@ -721,13 +709,16 @@ entryGetItem(GinState *ginstate, GinScanEntry entry) ...@@ -721,13 +709,16 @@ entryGetItem(GinState *ginstate, GinScanEntry entry)
} }
/* /*
* Sets key->curItem to next heap item pointer for one scan key, advancing * Identify the "current" item among the input entry streams for this scan key,
* past any item pointers <= advancePast. * and test whether it passes the scan key qual condition.
* Sets key->isFinished to TRUE if there are no more. *
* The current item is the smallest curItem among the inputs. key->curItem
* is set to that value. key->curItemMatches is set to indicate whether that
* TID passes the consistentFn test. If so, 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).
* *
* On success, key->recheckCurItem is set true iff recheck is needed for this * If all entry streams are exhausted, sets key->isFinished to TRUE.
* item pointer (including the case where the item pointer is a lossy page
* pointer).
* *
* Item pointers must be returned in ascending order. * Item pointers must be returned in ascending order.
* *
...@@ -738,10 +729,9 @@ entryGetItem(GinState *ginstate, GinScanEntry entry) ...@@ -738,10 +729,9 @@ entryGetItem(GinState *ginstate, GinScanEntry entry)
* logic in scanGetItem.) * logic in scanGetItem.)
*/ */
static void static void
keyGetItem(GinState *ginstate, MemoryContext tempCtx, keyGetItem(GinState *ginstate, MemoryContext tempCtx, GinScanKey key)
GinScanKey key, ItemPointer advancePast)
{ {
ItemPointerData myAdvancePast = *advancePast; ItemPointerData minItem;
ItemPointerData curPageLossy; ItemPointerData curPageLossy;
uint32 i; uint32 i;
uint32 lossyEntry; uint32 lossyEntry;
...@@ -752,167 +742,159 @@ keyGetItem(GinState *ginstate, MemoryContext tempCtx, ...@@ -752,167 +742,159 @@ keyGetItem(GinState *ginstate, MemoryContext tempCtx,
Assert(!key->isFinished); Assert(!key->isFinished);
do /*
{ * Find the minimum of the active entry curItems.
/* *
* Advance any entries that are <= myAdvancePast. In particular, * Note: a lossy-page entry is encoded by a ItemPointer with max value
* since entry->curItem was initialized with ItemPointerSetMin, this * for offset (0xffff), so that it will sort after any exact entries
* ensures we fetch the first item for each entry on the first call. * for the same page. So we'll prefer to return exact pointers not
* Then set key->curItem to the minimum of the valid entry curItems. * lossy pointers, which is good.
* */
* Note: a lossy-page entry is encoded by a ItemPointer with max value ItemPointerSetMax(&minItem);
* 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;
while (entry->isFinished == FALSE &&
ginCompareItemPointers(&entry->curItem, &myAdvancePast) <= 0)
entryGetItem(ginstate, entry);
if (entry->isFinished == FALSE &&
ginCompareItemPointers(&entry->curItem, &key->curItem) < 0)
key->curItem = entry->curItem;
}
if (ItemPointerIsMax(&key->curItem)) for (i = 0; i < key->nentries; i++)
{ {
/* all entries are finished */ entry = key->scanEntry[i];
key->isFinished = TRUE; if (entry->isFinished == FALSE &&
return; ginCompareItemPointers(&entry->curItem, &minItem) < 0)
} minItem = entry->curItem;
}
/* if (ItemPointerIsMax(&minItem))
* Now key->curItem contains first ItemPointer after previous result. {
* Advance myAdvancePast to this value, so that if the consistentFn /* all entries are finished */
* rejects the entry and we loop around again, we will advance to the key->isFinished = TRUE;
* next available item pointer. return;
*/ }
myAdvancePast = key->curItem;
/* /*
* Lossy-page entries pose a problem, since we don't know the correct * We might have already tested this item; if so, no need to repeat work.
* entryRes state to pass to the consistentFn, and we also don't know * (Note: the ">" case can happen, if minItem is exact but we previously
* what its combining logic will be (could be AND, OR, or even NOT). * had to set curItem to a lossy-page pointer.)
* If the logic is OR then the consistentFn might succeed for all */
* items in the lossy page even when none of the other entries match. if (ginCompareItemPointers(&key->curItem, &minItem) >= 0)
* return;
* If we have a single lossy-page entry then we check to see if the
* consistentFn will succeed with only that entry TRUE. If so,
* we return a lossy-page pointer to indicate that the whole heap
* page must be checked. (On the next call, we'll advance past all
* regular and lossy entries for this page before resuming search,
* thus ensuring that we never return both regular and lossy pointers
* for the same page.)
*
* 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 at once. So it
* doesn't seem worth the extra complexity to optimize that case.
* If we do find more than one, we just punt and return a lossy-page
* pointer always.
*
* Note that only lossy-page entries pointing to the current item's
* page should trigger this processing; we might have future lossy
* pages in the entry array, but they aren't relevant yet.
*/
ItemPointerSetLossyPage(&curPageLossy,
GinItemPointerGetBlockNumber(&key->curItem));
lossyEntry = 0; /*
haveLossyEntry = false; * OK, advance key->curItem and perform consistentFn test.
for (i = 0; i < key->nentries; i++) */
{ key->curItem = minItem;
entry = key->scanEntry + i;
if (entry->isFinished == FALSE &&
ginCompareItemPointers(&entry->curItem, &curPageLossy) == 0)
{
if (haveLossyEntry)
{
/* Multiple lossy entries, punt */
key->curItem = curPageLossy;
key->recheckCurItem = true;
return;
}
lossyEntry = i;
haveLossyEntry = true;
}
}
/* prepare for calling consistentFn in temp context */ /*
oldCtx = MemoryContextSwitchTo(tempCtx); * 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).
* If the logic is OR then the consistentFn might succeed for all
* items in the lossy page even when none of the other entries match.
*
* If we have a single lossy-page entry then we check to see if the
* consistentFn will succeed with only that entry TRUE. If so,
* we return a lossy-page pointer to indicate that the whole heap
* page must be checked. (On subsequent calls, we'll do nothing until
* minItem is past the page altogether, thus ensuring that we never return
* both regular and lossy pointers for the same page.)
*
* 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 at once. So it
* doesn't seem worth the extra complexity to optimize that case.
* If we do find more than one, we just punt and return a lossy-page
* pointer always.
*
* Note that only lossy-page entries pointing to the current item's
* page should trigger this processing; we might have future lossy
* pages in the entry array, but they aren't relevant yet.
*/
ItemPointerSetLossyPage(&curPageLossy,
GinItemPointerGetBlockNumber(&key->curItem));
if (haveLossyEntry) lossyEntry = 0;
haveLossyEntry = false;
for (i = 0; i < key->nentries; i++)
{
entry = key->scanEntry[i];
if (entry->isFinished == FALSE &&
ginCompareItemPointers(&entry->curItem, &curPageLossy) == 0)
{ {
/* Single lossy-page entry, so see if whole page matches */ if (haveLossyEntry)
memset(key->entryRes, FALSE, key->nentries);
key->entryRes[lossyEntry] = TRUE;
if (callConsistentFn(ginstate, key))
{ {
/* Yes, so clean up ... */ /* Multiple lossy entries, punt */
MemoryContextSwitchTo(oldCtx);
MemoryContextReset(tempCtx);
/* and return lossy pointer for whole page */
key->curItem = curPageLossy; key->curItem = curPageLossy;
key->curItemMatches = true;
key->recheckCurItem = true; key->recheckCurItem = true;
return; return;
} }
lossyEntry = i;
haveLossyEntry = true;
} }
}
/* /* prepare for calling consistentFn in temp context */
* At this point we know that we don't need to return a lossy oldCtx = MemoryContextSwitchTo(tempCtx);
* whole-page pointer, but we might have matches for individual exact
* item pointers, possibly in combination with a lossy pointer. Our
* strategy if there's a lossy pointer 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). An exception is that
* we don't need to try it both ways if the lossy pointer is in a
* "hidden" entry, because the consistentFn's result can't depend on
* that.
*
* Prepare entryRes array to be passed to consistentFn.
*/
for (i = 0; i < key->nentries; i++)
{
entry = key->scanEntry + i;
if (entry->isFinished == FALSE &&
ginCompareItemPointers(&entry->curItem, &key->curItem) == 0)
key->entryRes[i] = TRUE;
else
key->entryRes[i] = FALSE;
}
if (haveLossyEntry)
key->entryRes[lossyEntry] = TRUE;
res = callConsistentFn(ginstate, key); if (haveLossyEntry)
{
/* Single lossy-page entry, so see if whole page matches */
memset(key->entryRes, FALSE, key->nentries);
key->entryRes[lossyEntry] = TRUE;
if (!res && haveLossyEntry && lossyEntry < key->nuserentries) if (callConsistentFn(ginstate, key))
{ {
/* try the other way for the lossy item */ /* Yes, so clean up ... */
key->entryRes[lossyEntry] = FALSE; MemoryContextSwitchTo(oldCtx);
MemoryContextReset(tempCtx);
res = callConsistentFn(ginstate, key); /* and return lossy pointer for whole page */
key->curItem = curPageLossy;
key->curItemMatches = true;
key->recheckCurItem = true;
return;
} }
}
/* clean up after consistentFn calls */ /*
MemoryContextSwitchTo(oldCtx); * At this point we know that we don't need to return a lossy
MemoryContextReset(tempCtx); * whole-page pointer, but we might have matches for individual exact
* item pointers, possibly in combination with a lossy pointer. Our
* strategy if there's a lossy pointer 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). An exception is that
* we don't need to try it both ways if the lossy pointer is in a
* "hidden" entry, because the consistentFn's result can't depend on
* that.
*
* Prepare entryRes array to be passed to consistentFn.
*/
for (i = 0; i < key->nentries; i++)
{
entry = key->scanEntry[i];
if (entry->isFinished == FALSE &&
ginCompareItemPointers(&entry->curItem, &key->curItem) == 0)
key->entryRes[i] = TRUE;
else
key->entryRes[i] = FALSE;
}
if (haveLossyEntry)
key->entryRes[lossyEntry] = TRUE;
/* If we matched a lossy entry, force recheckCurItem = true */ res = callConsistentFn(ginstate, key);
if (haveLossyEntry)
key->recheckCurItem = true; if (!res && haveLossyEntry && lossyEntry < key->nuserentries)
} while (!res); {
/* try the other way for the lossy item */
key->entryRes[lossyEntry] = FALSE;
res = callConsistentFn(ginstate, key);
}
key->curItemMatches = res;
/* If we matched a lossy entry, force recheckCurItem = true */
if (haveLossyEntry)
key->recheckCurItem = true;
/* clean up after consistentFn calls */
MemoryContextSwitchTo(oldCtx);
MemoryContextReset(tempCtx);
} }
/* /*
...@@ -929,26 +911,45 @@ scanGetItem(IndexScanDesc scan, ItemPointer advancePast, ...@@ -929,26 +911,45 @@ scanGetItem(IndexScanDesc scan, ItemPointer advancePast,
ItemPointerData *item, bool *recheck) ItemPointerData *item, bool *recheck)
{ {
GinScanOpaque so = (GinScanOpaque) scan->opaque; GinScanOpaque so = (GinScanOpaque) scan->opaque;
GinState *ginstate = &so->ginstate;
ItemPointerData myAdvancePast = *advancePast; ItemPointerData myAdvancePast = *advancePast;
uint32 i; uint32 i;
bool allFinished;
bool match; bool match;
for (;;) for (;;)
{ {
/* /*
* Advance any keys that are <= myAdvancePast. In particular, * Advance any entries that are <= myAdvancePast. In particular,
* since key->curItem was initialized with ItemPointerSetMin, this * since entry->curItem was initialized with ItemPointerSetMin, this
* ensures we fetch the first item for each key on the first call. * ensures we fetch the first item for each entry on the first call.
* Then set *item to the minimum of the key curItems. */
* allFinished = TRUE;
* 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 (i = 0; i < so->totalentries; i++)
* 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 GinScanEntry entry = so->entries[i];
* entry after processing it, we will not advance past lossy entries
* for the same page in other keys, which is NECESSARY for correct while (entry->isFinished == FALSE &&
* results (since we might have additional entries for the same page ginCompareItemPointers(&entry->curItem,
* in the first key). &myAdvancePast) <= 0)
entryGetItem(ginstate, entry);
if (entry->isFinished == FALSE)
allFinished = FALSE;
}
if (allFinished)
{
/* all entries exhausted, so we're done */
return false;
}
/*
* Perform the consistentFn test for each scan key. If any key
* reports isFinished, meaning its subset of the entries is exhausted,
* we can stop. Otherwise, set *item to the minimum of the key
* curItems.
*/ */
ItemPointerSetMax(item); ItemPointerSetMax(item);
...@@ -956,13 +957,10 @@ scanGetItem(IndexScanDesc scan, ItemPointer advancePast, ...@@ -956,13 +957,10 @@ scanGetItem(IndexScanDesc scan, ItemPointer advancePast,
{ {
GinScanKey key = so->keys + i; GinScanKey key = so->keys + i;
while (key->isFinished == FALSE && keyGetItem(&so->ginstate, so->tempCtx, key);
ginCompareItemPointers(&key->curItem, &myAdvancePast) <= 0)
keyGetItem(&so->ginstate, so->tempCtx,
key, &myAdvancePast);
if (key->isFinished) if (key->isFinished)
return FALSE; /* finished one of keys */ return false; /* finished one of keys */
if (ginCompareItemPointers(&key->curItem, item) < 0) if (ginCompareItemPointers(&key->curItem, item) < 0)
*item = key->curItem; *item = key->curItem;
...@@ -973,7 +971,7 @@ scanGetItem(IndexScanDesc scan, ItemPointer advancePast, ...@@ -973,7 +971,7 @@ scanGetItem(IndexScanDesc scan, ItemPointer advancePast,
/*---------- /*----------
* Now *item contains first ItemPointer after previous result. * Now *item contains first ItemPointer after previous result.
* *
* The item is a valid hit only if all the keys returned either * The item is a valid hit only if all the keys succeeded for either
* that exact TID, or a lossy reference to the same page. * that exact TID, or a lossy reference to the same page.
* *
* This logic works only if a keyGetItem stream can never contain both * This logic works only if a keyGetItem stream can never contain both
...@@ -996,12 +994,15 @@ scanGetItem(IndexScanDesc scan, ItemPointer advancePast, ...@@ -996,12 +994,15 @@ scanGetItem(IndexScanDesc scan, ItemPointer advancePast,
{ {
GinScanKey key = so->keys + i; GinScanKey key = so->keys + i;
if (ginCompareItemPointers(item, &key->curItem) == 0) if (key->curItemMatches)
continue; {
if (ItemPointerIsLossyPage(&key->curItem) && if (ginCompareItemPointers(item, &key->curItem) == 0)
GinItemPointerGetBlockNumber(&key->curItem) == continue;
GinItemPointerGetBlockNumber(item)) if (ItemPointerIsLossyPage(&key->curItem) &&
continue; GinItemPointerGetBlockNumber(&key->curItem) ==
GinItemPointerGetBlockNumber(item))
continue;
}
match = false; match = false;
break; break;
} }
...@@ -1247,7 +1248,7 @@ collectMatchesForHeapRow(IndexScanDesc scan, pendingPosition *pos) ...@@ -1247,7 +1248,7 @@ collectMatchesForHeapRow(IndexScanDesc scan, pendingPosition *pos)
for (j = 0; j < key->nentries; j++) for (j = 0; j < key->nentries; j++)
{ {
GinScanEntry entry = key->scanEntry + j; GinScanEntry entry = key->scanEntry[j];
OffsetNumber StopLow = pos->firstOffset, OffsetNumber StopLow = pos->firstOffset,
StopHigh = pos->lastOffset, StopHigh = pos->lastOffset,
StopMiddle; StopMiddle;
......
...@@ -53,19 +53,95 @@ ginbeginscan(PG_FUNCTION_ARGS) ...@@ -53,19 +53,95 @@ ginbeginscan(PG_FUNCTION_ARGS)
} }
/* /*
* Initialize a GinScanKey using the output from the extractQueryFn * Create a new GinScanEntry, unless an equivalent one already exists,
* in which case just return it
*/
static GinScanEntry
ginFillScanEntry(GinScanOpaque so, OffsetNumber attnum,
StrategyNumber strategy, int32 searchMode,
Datum queryKey, GinNullCategory queryCategory,
bool isPartialMatch, Pointer extra_data)
{
GinState *ginstate = &so->ginstate;
GinScanEntry scanEntry;
uint32 i;
/*
* Look for an existing equivalent entry.
*
* Entries with non-null extra_data are never considered identical, since
* we can't know exactly what the opclass might be doing with that.
*/
if (extra_data == NULL)
{
for (i = 0; i < so->totalentries; i++)
{
GinScanEntry prevEntry = so->entries[i];
if (prevEntry->extra_data == NULL &&
prevEntry->isPartialMatch == isPartialMatch &&
prevEntry->strategy == strategy &&
prevEntry->searchMode == searchMode &&
prevEntry->attnum == attnum &&
ginCompareEntries(ginstate, attnum,
prevEntry->queryKey,
prevEntry->queryCategory,
queryKey,
queryCategory) == 0)
{
/* Successful match */
return prevEntry;
}
}
}
/* Nope, create a new entry */
scanEntry = (GinScanEntry) palloc(sizeof(GinScanEntryData));
scanEntry->queryKey = queryKey;
scanEntry->queryCategory = queryCategory;
scanEntry->isPartialMatch = isPartialMatch;
scanEntry->extra_data = extra_data;
scanEntry->strategy = strategy;
scanEntry->searchMode = searchMode;
scanEntry->attnum = attnum;
scanEntry->buffer = InvalidBuffer;
ItemPointerSetMin(&scanEntry->curItem);
scanEntry->matchBitmap = NULL;
scanEntry->matchIterator = NULL;
scanEntry->matchResult = NULL;
scanEntry->list = NULL;
scanEntry->nlist = 0;
scanEntry->offset = InvalidOffsetNumber;
scanEntry->isFinished = false;
scanEntry->reduceResult = false;
/* Add it to so's array */
if (so->totalentries >= so->allocentries)
{
so->allocentries *= 2;
so->entries = (GinScanEntry *)
repalloc(so->entries, so->allocentries * sizeof(GinScanEntry));
}
so->entries[so->totalentries++] = scanEntry;
return scanEntry;
}
/*
* Initialize the next GinScanKey using the output from the extractQueryFn
*/ */
static void static void
ginFillScanKey(GinState *ginstate, GinScanKey key, ginFillScanKey(GinScanOpaque so, OffsetNumber attnum,
OffsetNumber attnum, Datum query, StrategyNumber strategy, int32 searchMode,
Datum query, uint32 nQueryValues,
Datum *queryValues, GinNullCategory *queryCategories, Datum *queryValues, GinNullCategory *queryCategories,
bool *partial_matches, uint32 nQueryValues, bool *partial_matches, Pointer *extra_data)
StrategyNumber strategy, Pointer *extra_data,
int32 searchMode)
{ {
GinScanKey key = &(so->keys[so->nkeys++]);
GinState *ginstate = &so->ginstate;
uint32 nUserQueryValues = nQueryValues; uint32 nUserQueryValues = nQueryValues;
uint32 i, uint32 i;
j;
/* Non-default search modes add one "hidden" entry to each key */ /* Non-default search modes add one "hidden" entry to each key */
if (searchMode != GIN_SEARCH_MODE_DEFAULT) if (searchMode != GIN_SEARCH_MODE_DEFAULT)
...@@ -73,8 +149,9 @@ ginFillScanKey(GinState *ginstate, GinScanKey key, ...@@ -73,8 +149,9 @@ ginFillScanKey(GinState *ginstate, GinScanKey key,
key->nentries = nQueryValues; key->nentries = nQueryValues;
key->nuserentries = nUserQueryValues; key->nuserentries = nUserQueryValues;
key->scanEntry = (GinScanEntry) palloc(sizeof(GinScanEntryData) * nQueryValues); key->scanEntry = (GinScanEntry *) palloc(sizeof(GinScanEntry) * nQueryValues);
key->entryRes = (bool *) palloc0(sizeof(bool) * nQueryValues); key->entryRes = (bool *) palloc0(sizeof(bool) * nQueryValues);
key->query = query; key->query = query;
key->queryValues = queryValues; key->queryValues = queryValues;
key->queryCategories = queryCategories; key->queryCategories = queryCategories;
...@@ -83,156 +160,106 @@ ginFillScanKey(GinState *ginstate, GinScanKey key, ...@@ -83,156 +160,106 @@ ginFillScanKey(GinState *ginstate, GinScanKey key,
key->searchMode = searchMode; key->searchMode = searchMode;
key->attnum = attnum; key->attnum = attnum;
key->firstCall = TRUE;
ItemPointerSetMin(&key->curItem); ItemPointerSetMin(&key->curItem);
key->curItemMatches = false;
key->recheckCurItem = false;
key->isFinished = false;
for (i = 0; i < nQueryValues; i++) for (i = 0; i < nQueryValues; i++)
{ {
GinScanEntry scanEntry = key->scanEntry + i; Datum queryKey;
GinNullCategory queryCategory;
bool isPartialMatch;
Pointer this_extra;
scanEntry->pval = key->entryRes + i;
if (i < nUserQueryValues) if (i < nUserQueryValues)
{ {
scanEntry->queryKey = queryValues[i]; /* set up normal entry using extractQueryFn's outputs */
scanEntry->queryCategory = queryCategories[i]; queryKey = queryValues[i];
scanEntry->isPartialMatch = queryCategory = queryCategories[i];
isPartialMatch =
(ginstate->canPartialMatch[attnum - 1] && partial_matches) (ginstate->canPartialMatch[attnum - 1] && partial_matches)
? partial_matches[i] : false; ? partial_matches[i] : false;
scanEntry->extra_data = (extra_data) ? extra_data[i] : NULL; this_extra = (extra_data) ? extra_data[i] : NULL;
} }
else else
{ {
/* set up hidden entry */ /* set up hidden entry */
scanEntry->queryKey = (Datum) 0; queryKey = (Datum) 0;
switch (searchMode) switch (searchMode)
{ {
case GIN_SEARCH_MODE_INCLUDE_EMPTY: case GIN_SEARCH_MODE_INCLUDE_EMPTY:
scanEntry->queryCategory = GIN_CAT_EMPTY_ITEM; queryCategory = GIN_CAT_EMPTY_ITEM;
break; break;
case GIN_SEARCH_MODE_ALL: case GIN_SEARCH_MODE_ALL:
scanEntry->queryCategory = GIN_CAT_EMPTY_QUERY; queryCategory = GIN_CAT_EMPTY_QUERY;
break; break;
case GIN_SEARCH_MODE_EVERYTHING: case GIN_SEARCH_MODE_EVERYTHING:
scanEntry->queryCategory = GIN_CAT_EMPTY_QUERY; queryCategory = GIN_CAT_EMPTY_QUERY;
break; break;
default: default:
elog(ERROR, "unexpected searchMode: %d", searchMode); elog(ERROR, "unexpected searchMode: %d", searchMode);
queryCategory = 0; /* keep compiler quiet */
break; break;
} }
scanEntry->isPartialMatch = false; isPartialMatch = false;
scanEntry->extra_data = NULL; this_extra = NULL;
/*
* We set the strategy to a fixed value so that ginFillScanEntry
* can combine these entries for different scan keys. This is
* safe because the strategy value in the entry struct is only
* used for partial-match cases. It's OK to overwrite our local
* variable here because this is the last loop iteration.
*/
strategy = InvalidStrategy;
} }
scanEntry->strategy = strategy;
scanEntry->searchMode = searchMode;
scanEntry->attnum = attnum;
ItemPointerSetMin(&scanEntry->curItem);
scanEntry->isFinished = FALSE;
scanEntry->offset = InvalidOffsetNumber;
scanEntry->buffer = InvalidBuffer;
scanEntry->list = NULL;
scanEntry->nlist = 0;
scanEntry->matchBitmap = NULL;
scanEntry->matchIterator = NULL;
scanEntry->matchResult = NULL;
/* key->scanEntry[i] = ginFillScanEntry(so, attnum,
* Link to any preceding identical entry in current scan key. strategy, searchMode,
* queryKey, queryCategory,
* Entries with non-null extra_data are never considered identical, isPartialMatch, this_extra);
* since we can't know exactly what the opclass might be doing with
* that.
*/
scanEntry->master = NULL;
if (scanEntry->extra_data == NULL)
{
for (j = 0; j < i; j++)
{
GinScanEntry prevEntry = key->scanEntry + j;
if (prevEntry->extra_data == NULL &&
scanEntry->isPartialMatch == prevEntry->isPartialMatch &&
ginCompareEntries(ginstate, attnum,
scanEntry->queryKey,
scanEntry->queryCategory,
prevEntry->queryKey,
prevEntry->queryCategory) == 0)
{
scanEntry->master = prevEntry;
break;
}
}
}
} }
} }
#ifdef NOT_USED
static void static void
resetScanKeys(GinScanKey keys, uint32 nkeys) freeScanKeys(GinScanOpaque so)
{ {
uint32 i, uint32 i;
j;
if (keys == NULL) if (so->keys == NULL)
return; return;
for (i = 0; i < nkeys; i++) for (i = 0; i < so->nkeys; i++)
{ {
GinScanKey key = keys + i; GinScanKey key = so->keys + i;
key->firstCall = TRUE;
ItemPointerSetMin(&key->curItem);
for (j = 0; j < key->nentries; j++) pfree(key->scanEntry);
{ pfree(key->entryRes);
if (key->scanEntry[j].buffer != InvalidBuffer)
ReleaseBuffer(key->scanEntry[i].buffer);
ItemPointerSetMin(&key->scanEntry[j].curItem);
key->scanEntry[j].isFinished = FALSE;
key->scanEntry[j].offset = InvalidOffsetNumber;
key->scanEntry[j].buffer = InvalidBuffer;
key->scanEntry[j].list = NULL;
key->scanEntry[j].nlist = 0;
key->scanEntry[j].matchBitmap = NULL;
key->scanEntry[j].matchIterator = NULL;
key->scanEntry[j].matchResult = NULL;
}
} }
}
#endif
static void
freeScanKeys(GinScanKey keys, uint32 nkeys)
{
uint32 i,
j;
if (keys == NULL) pfree(so->keys);
return; so->keys = NULL;
so->nkeys = 0;
for (i = 0; i < nkeys; i++) for (i = 0; i < so->totalentries; i++)
{ {
GinScanKey key = keys + i; GinScanEntry entry = so->entries[i];
for (j = 0; j < key->nentries; j++) if (entry->buffer != InvalidBuffer)
{ ReleaseBuffer(entry->buffer);
if (key->scanEntry[j].buffer != InvalidBuffer) if (entry->list)
ReleaseBuffer(key->scanEntry[j].buffer); pfree(entry->list);
if (key->scanEntry[j].list) if (entry->matchIterator)
pfree(key->scanEntry[j].list); tbm_end_iterate(entry->matchIterator);
if (key->scanEntry[j].matchIterator) if (entry->matchBitmap)
tbm_end_iterate(key->scanEntry[j].matchIterator); tbm_free(entry->matchBitmap);
if (key->scanEntry[j].matchBitmap) pfree(entry);
tbm_free(key->scanEntry[j].matchBitmap);
}
pfree(key->entryRes);
pfree(key->scanEntry);
} }
pfree(keys); pfree(so->entries);
so->entries = NULL;
so->totalentries = 0;
} }
void void
...@@ -241,12 +268,18 @@ ginNewScanKey(IndexScanDesc scan) ...@@ -241,12 +268,18 @@ ginNewScanKey(IndexScanDesc scan)
ScanKey scankey = scan->keyData; ScanKey scankey = scan->keyData;
GinScanOpaque so = (GinScanOpaque) scan->opaque; GinScanOpaque so = (GinScanOpaque) scan->opaque;
int i; int i;
uint32 nkeys = 0;
bool hasNullQuery = false; bool hasNullQuery = false;
/* if no scan keys provided, allocate extra EVERYTHING GinScanKey */ /* if no scan keys provided, allocate extra EVERYTHING GinScanKey */
so->keys = (GinScanKey) so->keys = (GinScanKey)
palloc(Max(scan->numberOfKeys, 1) * sizeof(GinScanKeyData)); palloc(Max(scan->numberOfKeys, 1) * sizeof(GinScanKeyData));
so->nkeys = 0;
/* initialize expansible array of GinScanEntry pointers */
so->totalentries = 0;
so->allocentries = 32;
so->entries = (GinScanEntry *)
palloc0(so->allocentries * sizeof(GinScanEntry));
so->isVoidRes = false; so->isVoidRes = false;
...@@ -331,26 +364,24 @@ ginNewScanKey(IndexScanDesc scan) ...@@ -331,26 +364,24 @@ ginNewScanKey(IndexScanDesc scan)
} }
/* now we can use the nullFlags as category codes */ /* now we can use the nullFlags as category codes */
ginFillScanKey(&so->ginstate, &(so->keys[nkeys]), ginFillScanKey(so, skey->sk_attno,
skey->sk_attno, skey->sk_argument, skey->sk_strategy, searchMode,
skey->sk_argument, nQueryValues,
queryValues, (GinNullCategory *) nullFlags, queryValues, (GinNullCategory *) nullFlags,
partial_matches, nQueryValues, partial_matches, extra_data);
skey->sk_strategy, extra_data, searchMode);
nkeys++;
} }
/* /*
* If there are no regular scan keys, generate an EVERYTHING scankey to * If there are no regular scan keys, generate an EVERYTHING scankey to
* drive a full-index scan. * drive a full-index scan.
*/ */
if (nkeys == 0 && !so->isVoidRes) if (so->nkeys == 0 && !so->isVoidRes)
{ {
hasNullQuery = true; hasNullQuery = true;
ginFillScanKey(&so->ginstate, &(so->keys[nkeys]), ginFillScanKey(so, FirstOffsetNumber,
FirstOffsetNumber, (Datum) 0, InvalidStrategy, GIN_SEARCH_MODE_EVERYTHING,
NULL, NULL, NULL, 0, (Datum) 0, 0,
InvalidStrategy, NULL, GIN_SEARCH_MODE_EVERYTHING); NULL, NULL, NULL, NULL);
nkeys++;
} }
/* /*
...@@ -371,8 +402,6 @@ ginNewScanKey(IndexScanDesc scan) ...@@ -371,8 +402,6 @@ ginNewScanKey(IndexScanDesc scan)
RelationGetRelationName(scan->indexRelation)))); RelationGetRelationName(scan->indexRelation))));
} }
so->nkeys = nkeys;
pgstat_count_index_scan(scan->indexRelation); pgstat_count_index_scan(scan->indexRelation);
} }
...@@ -384,8 +413,7 @@ ginrescan(PG_FUNCTION_ARGS) ...@@ -384,8 +413,7 @@ ginrescan(PG_FUNCTION_ARGS)
/* remaining arguments are ignored */ /* remaining arguments are ignored */
GinScanOpaque so = (GinScanOpaque) scan->opaque; GinScanOpaque so = (GinScanOpaque) scan->opaque;
freeScanKeys(so->keys, so->nkeys); freeScanKeys(so);
so->keys = NULL;
if (scankey && scan->numberOfKeys > 0) if (scankey && scan->numberOfKeys > 0)
{ {
...@@ -403,7 +431,7 @@ ginendscan(PG_FUNCTION_ARGS) ...@@ -403,7 +431,7 @@ ginendscan(PG_FUNCTION_ARGS)
IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0); IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
GinScanOpaque so = (GinScanOpaque) scan->opaque; GinScanOpaque so = (GinScanOpaque) scan->opaque;
freeScanKeys(so->keys, so->nkeys); freeScanKeys(so);
MemoryContextDelete(so->tempCtx); MemoryContextDelete(so->tempCtx);
......
...@@ -549,12 +549,19 @@ extern void ginPrepareDataScan(GinBtree btree, Relation index); ...@@ -549,12 +549,19 @@ extern void ginPrepareDataScan(GinBtree btree, Relation index);
/* ginscan.c */ /* ginscan.c */
/* /*
* GinScanKeyData describes a single GIN index qualification condition. * GinScanKeyData describes a single GIN index qualifier expression.
* It contains one GinScanEntryData for each key datum extracted from *
* the qual by the extractQueryFn or added implicitly by ginFillScanKey. * From each qual expression, we extract one or more specific index search
* nentries is the true number of entries, nuserentries is the number * conditions, which are represented by GinScanEntryData. It's quite
* that extractQueryFn returned (which is what we report to consistentFn). * possible for identical search conditions to be requested by more than
* The "user" entries must come first. * one qual expression, in which case we merge such conditions to have just
* one unique GinScanEntry --- this is particularly important for efficiency
* when dealing with full-index-scan entries. So there can be multiple
* GinScanKeyData.scanEntry pointers to the same GinScanEntryData.
*
* In each GinScanKeyData, nentries is the true number of entries, while
* nuserentries is the number that extractQueryFn returned (which is what
* we report to consistentFn). The "user" entries must come first.
*/ */
typedef struct GinScanKeyData *GinScanKey; typedef struct GinScanKeyData *GinScanKey;
...@@ -567,10 +574,10 @@ typedef struct GinScanKeyData ...@@ -567,10 +574,10 @@ typedef struct GinScanKeyData
/* Number of entries that extractQueryFn and consistentFn know about */ /* Number of entries that extractQueryFn and consistentFn know about */
uint32 nuserentries; uint32 nuserentries;
/* array of GinScanEntryData, one per key datum */ /* array of GinScanEntry pointers, one per extracted search condition */
GinScanEntry scanEntry; GinScanEntry *scanEntry;
/* array of ItemPointer result, reported to consistentFn */ /* array of check flags, reported to consistentFn */
bool *entryRes; bool *entryRes;
/* other data needed for calling consistentFn */ /* other data needed for calling consistentFn */
...@@ -583,22 +590,21 @@ typedef struct GinScanKeyData ...@@ -583,22 +590,21 @@ typedef struct GinScanKeyData
int32 searchMode; int32 searchMode;
OffsetNumber attnum; OffsetNumber attnum;
/* scan status data */ /*
* Match status data. curItem is the TID most recently tested (could be
* a lossy-page pointer). curItemMatches is TRUE if it passes the
* consistentFn test; if so, recheckCurItem is the recheck flag.
* isFinished means that all the input entry streams are finished, so
* this key cannot succeed for any later TIDs.
*/
ItemPointerData curItem; ItemPointerData curItem;
bool curItemMatches;
bool recheckCurItem; bool recheckCurItem;
bool firstCall;
bool isFinished; bool isFinished;
} GinScanKeyData; } GinScanKeyData;
typedef struct GinScanEntryData typedef struct GinScanEntryData
{ {
/* link to any preceding identical entry in current scan key */
GinScanEntry master;
/* ptr to value reported to consistentFn, points to parent->entryRes[i] */
bool *pval;
/* query key and other information from extractQueryFn */ /* query key and other information from extractQueryFn */
Datum queryKey; Datum queryKey;
GinNullCategory queryCategory; GinNullCategory queryCategory;
...@@ -634,10 +640,14 @@ typedef struct GinScanOpaqueData ...@@ -634,10 +640,14 @@ typedef struct GinScanOpaqueData
MemoryContext tempCtx; MemoryContext tempCtx;
GinState ginstate; GinState ginstate;
GinScanKey keys; GinScanKey keys; /* one per scan qualifier expr */
uint32 nkeys; uint32 nkeys;
bool isVoidRes; /* true if ginstate.extractQueryFn guarantees
* that nothing will be found */ GinScanEntry *entries; /* one per index search condition */
uint32 totalentries;
uint32 allocentries; /* allocated length of entries[] */
bool isVoidRes; /* true if query is unsatisfiable */
} GinScanOpaqueData; } GinScanOpaqueData;
typedef GinScanOpaqueData *GinScanOpaque; typedef GinScanOpaqueData *GinScanOpaque;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment