diff --git a/src/backend/access/gin/ginget.c b/src/backend/access/gin/ginget.c index aaef6efbb50a18f3c23dbde8b5e66c4f0e3bcbee..e07dc0a6ce06454215cd7d6f6b585e968ace03fa 100644 --- a/src/backend/access/gin/ginget.c +++ b/src/backend/access/gin/ginget.c @@ -371,6 +371,7 @@ startScanEntry(GinState *ginstate, GinScanEntry entry) restartScanEntry: entry->buffer = InvalidBuffer; + ItemPointerSetMin(&entry->curItem); entry->offset = InvalidOffsetNumber; entry->list = NULL; entry->nlist = 0; @@ -379,12 +380,6 @@ restartScanEntry: entry->reduceResult = FALSE; 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 * posting list in memory @@ -499,16 +494,21 @@ restartScanEntry: static void startScanKey(GinState *ginstate, GinScanKey key) { - uint32 i; - - if (!key->firstCall) - return; + ItemPointerSetMin(&key->curItem); + key->curItemMatches = false; + key->recheckCurItem = false; + key->isFinished = false; +} - for (i = 0; i < key->nentries; i++) - startScanEntry(ginstate, key->scanEntry + i); +static void +startScan(IndexScanDesc scan) +{ + GinScanOpaque so = (GinScanOpaque) scan->opaque; + GinState *ginstate = &so->ginstate; + uint32 i; - key->isFinished = FALSE; - key->firstCall = FALSE; + for (i = 0; i < so->totalentries; i++) + startScanEntry(ginstate, so->entries[i]); if (GinFuzzySearchLimit > 0) { @@ -519,27 +519,20 @@ startScanKey(GinState *ginstate, GinScanKey key) * minimal predictNumberResult. */ - for (i = 0; i < key->nentries; i++) - if (key->scanEntry[i].predictNumberResult <= key->nentries * GinFuzzySearchLimit) + for (i = 0; i < so->totalentries; i++) + if (so->entries[i]->predictNumberResult <= so->totalentries * GinFuzzySearchLimit) return; - for (i = 0; i < key->nentries; i++) - if (key->scanEntry[i].predictNumberResult > key->nentries * GinFuzzySearchLimit) + for (i = 0; i < so->totalentries; i++) + if (so->entries[i]->predictNumberResult > so->totalentries * GinFuzzySearchLimit) { - key->scanEntry[i].predictNumberResult /= key->nentries; - key->scanEntry[i].reduceResult = TRUE; + so->entries[i]->predictNumberResult /= so->totalentries; + so->entries[i]->reduceResult = TRUE; } } -} - -static void -startScan(IndexScanDesc scan) -{ - uint32 i; - GinScanOpaque so = (GinScanOpaque) scan->opaque; 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) { Assert(!entry->isFinished); - if (entry->master) - { - entry->isFinished = entry->master->isFinished; - entry->curItem = entry->master->curItem; - } - else if (entry->matchBitmap) + if (entry->matchBitmap) { do { @@ -721,13 +709,16 @@ entryGetItem(GinState *ginstate, GinScanEntry entry) } /* - * 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. + * Identify the "current" item among the input entry streams for this scan key, + * and test whether it passes the scan key qual condition. + * + * 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 - * item pointer (including the case where the item pointer is a lossy page - * pointer). + * If all entry streams are exhausted, sets key->isFinished to TRUE. * * Item pointers must be returned in ascending order. * @@ -738,10 +729,9 @@ entryGetItem(GinState *ginstate, GinScanEntry entry) * logic in scanGetItem.) */ static void -keyGetItem(GinState *ginstate, MemoryContext tempCtx, - GinScanKey key, ItemPointer advancePast) +keyGetItem(GinState *ginstate, MemoryContext tempCtx, GinScanKey key) { - ItemPointerData myAdvancePast = *advancePast; + ItemPointerData minItem; ItemPointerData curPageLossy; uint32 i; uint32 lossyEntry; @@ -752,167 +742,159 @@ keyGetItem(GinState *ginstate, MemoryContext tempCtx, Assert(!key->isFinished); - do - { - /* - * 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; - - 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; - } + /* + * Find the minimum of the active 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. + */ + ItemPointerSetMax(&minItem); - if (ItemPointerIsMax(&key->curItem)) - { - /* all entries are finished */ - key->isFinished = TRUE; - return; - } + for (i = 0; i < key->nentries; i++) + { + entry = key->scanEntry[i]; + if (entry->isFinished == FALSE && + ginCompareItemPointers(&entry->curItem, &minItem) < 0) + minItem = entry->curItem; + } - /* - * 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; + if (ItemPointerIsMax(&minItem)) + { + /* all entries are finished */ + key->isFinished = TRUE; + return; + } - /* - * 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 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)); + /* + * We might have already tested this item; if so, no need to repeat work. + * (Note: the ">" case can happen, if minItem is exact but we previously + * had to set curItem to a lossy-page pointer.) + */ + if (ginCompareItemPointers(&key->curItem, &minItem) >= 0) + return; - lossyEntry = 0; - haveLossyEntry = false; - for (i = 0; i < key->nentries; i++) - { - 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; - } - } + /* + * OK, advance key->curItem and perform consistentFn test. + */ + key->curItem = minItem; - /* 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 */ - memset(key->entryRes, FALSE, key->nentries); - key->entryRes[lossyEntry] = TRUE; - - if (callConsistentFn(ginstate, key)) + if (haveLossyEntry) { - /* Yes, so clean up ... */ - MemoryContextSwitchTo(oldCtx); - MemoryContextReset(tempCtx); - - /* and return lossy pointer for whole page */ + /* Multiple lossy entries, punt */ key->curItem = curPageLossy; + key->curItemMatches = true; key->recheckCurItem = true; return; } + lossyEntry = i; + haveLossyEntry = true; } + } - /* - * At this point we know that we don't need to return a lossy - * 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; + /* prepare for calling consistentFn in temp context */ + oldCtx = MemoryContextSwitchTo(tempCtx); - 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 */ - key->entryRes[lossyEntry] = FALSE; + /* Yes, so clean up ... */ + 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); - MemoryContextReset(tempCtx); + /* + * At this point we know that we don't need to return a lossy + * 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 */ - if (haveLossyEntry) - key->recheckCurItem = true; - } while (!res); + res = callConsistentFn(ginstate, key); + + if (!res && haveLossyEntry && lossyEntry < key->nuserentries) + { + /* 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, ItemPointerData *item, bool *recheck) { GinScanOpaque so = (GinScanOpaque) scan->opaque; + GinState *ginstate = &so->ginstate; ItemPointerData myAdvancePast = *advancePast; uint32 i; + bool allFinished; 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). + * 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. + */ + allFinished = TRUE; + + for (i = 0; i < so->totalentries; i++) + { + GinScanEntry entry = so->entries[i]; + + while (entry->isFinished == FALSE && + ginCompareItemPointers(&entry->curItem, + &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); @@ -956,13 +957,10 @@ scanGetItem(IndexScanDesc scan, ItemPointer advancePast, { GinScanKey key = so->keys + i; - while (key->isFinished == FALSE && - ginCompareItemPointers(&key->curItem, &myAdvancePast) <= 0) - keyGetItem(&so->ginstate, so->tempCtx, - key, &myAdvancePast); + keyGetItem(&so->ginstate, so->tempCtx, key); if (key->isFinished) - return FALSE; /* finished one of keys */ + return false; /* finished one of keys */ if (ginCompareItemPointers(&key->curItem, item) < 0) *item = key->curItem; @@ -973,7 +971,7 @@ scanGetItem(IndexScanDesc scan, ItemPointer advancePast, /*---------- * 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. * * This logic works only if a keyGetItem stream can never contain both @@ -996,12 +994,15 @@ scanGetItem(IndexScanDesc scan, ItemPointer advancePast, { GinScanKey key = so->keys + i; - if (ginCompareItemPointers(item, &key->curItem) == 0) - continue; - if (ItemPointerIsLossyPage(&key->curItem) && - GinItemPointerGetBlockNumber(&key->curItem) == - GinItemPointerGetBlockNumber(item)) - continue; + if (key->curItemMatches) + { + if (ginCompareItemPointers(item, &key->curItem) == 0) + continue; + if (ItemPointerIsLossyPage(&key->curItem) && + GinItemPointerGetBlockNumber(&key->curItem) == + GinItemPointerGetBlockNumber(item)) + continue; + } match = false; break; } @@ -1247,7 +1248,7 @@ collectMatchesForHeapRow(IndexScanDesc scan, pendingPosition *pos) for (j = 0; j < key->nentries; j++) { - GinScanEntry entry = key->scanEntry + j; + GinScanEntry entry = key->scanEntry[j]; OffsetNumber StopLow = pos->firstOffset, StopHigh = pos->lastOffset, StopMiddle; diff --git a/src/backend/access/gin/ginscan.c b/src/backend/access/gin/ginscan.c index c9cf77514a1853cb8aec6581fe17c5087ec2d459..25f60e15a0d438a55eea244ba145cd17b044b54e 100644 --- a/src/backend/access/gin/ginscan.c +++ b/src/backend/access/gin/ginscan.c @@ -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 -ginFillScanKey(GinState *ginstate, GinScanKey key, - OffsetNumber attnum, Datum query, +ginFillScanKey(GinScanOpaque so, OffsetNumber attnum, + StrategyNumber strategy, int32 searchMode, + Datum query, uint32 nQueryValues, Datum *queryValues, GinNullCategory *queryCategories, - bool *partial_matches, uint32 nQueryValues, - StrategyNumber strategy, Pointer *extra_data, - int32 searchMode) + bool *partial_matches, Pointer *extra_data) { + GinScanKey key = &(so->keys[so->nkeys++]); + GinState *ginstate = &so->ginstate; uint32 nUserQueryValues = nQueryValues; - uint32 i, - j; + uint32 i; /* Non-default search modes add one "hidden" entry to each key */ if (searchMode != GIN_SEARCH_MODE_DEFAULT) @@ -73,8 +149,9 @@ ginFillScanKey(GinState *ginstate, GinScanKey key, key->nentries = nQueryValues; 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->query = query; key->queryValues = queryValues; key->queryCategories = queryCategories; @@ -83,156 +160,106 @@ ginFillScanKey(GinState *ginstate, GinScanKey key, key->searchMode = searchMode; key->attnum = attnum; - key->firstCall = TRUE; ItemPointerSetMin(&key->curItem); + key->curItemMatches = false; + key->recheckCurItem = false; + key->isFinished = false; 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) { - scanEntry->queryKey = queryValues[i]; - scanEntry->queryCategory = queryCategories[i]; - scanEntry->isPartialMatch = + /* set up normal entry using extractQueryFn's outputs */ + queryKey = queryValues[i]; + queryCategory = queryCategories[i]; + isPartialMatch = (ginstate->canPartialMatch[attnum - 1] && partial_matches) ? partial_matches[i] : false; - scanEntry->extra_data = (extra_data) ? extra_data[i] : NULL; + this_extra = (extra_data) ? extra_data[i] : NULL; } else { /* set up hidden entry */ - scanEntry->queryKey = (Datum) 0; + queryKey = (Datum) 0; switch (searchMode) { case GIN_SEARCH_MODE_INCLUDE_EMPTY: - scanEntry->queryCategory = GIN_CAT_EMPTY_ITEM; + queryCategory = GIN_CAT_EMPTY_ITEM; break; case GIN_SEARCH_MODE_ALL: - scanEntry->queryCategory = GIN_CAT_EMPTY_QUERY; + queryCategory = GIN_CAT_EMPTY_QUERY; break; case GIN_SEARCH_MODE_EVERYTHING: - scanEntry->queryCategory = GIN_CAT_EMPTY_QUERY; + queryCategory = GIN_CAT_EMPTY_QUERY; break; default: elog(ERROR, "unexpected searchMode: %d", searchMode); + queryCategory = 0; /* keep compiler quiet */ break; } - scanEntry->isPartialMatch = false; - scanEntry->extra_data = NULL; + isPartialMatch = false; + 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; - /* - * Link to any preceding identical entry in current scan key. - * - * Entries with non-null extra_data are never considered identical, - * 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; - } - } - } + key->scanEntry[i] = ginFillScanEntry(so, attnum, + strategy, searchMode, + queryKey, queryCategory, + isPartialMatch, this_extra); } } -#ifdef NOT_USED - static void -resetScanKeys(GinScanKey keys, uint32 nkeys) +freeScanKeys(GinScanOpaque so) { - uint32 i, - j; + uint32 i; - if (keys == NULL) + if (so->keys == NULL) return; - for (i = 0; i < nkeys; i++) + for (i = 0; i < so->nkeys; i++) { - GinScanKey key = keys + i; - - key->firstCall = TRUE; - ItemPointerSetMin(&key->curItem); + GinScanKey key = so->keys + i; - for (j = 0; j < key->nentries; j++) - { - 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; - } + pfree(key->scanEntry); + pfree(key->entryRes); } -} -#endif - -static void -freeScanKeys(GinScanKey keys, uint32 nkeys) -{ - uint32 i, - j; - if (keys == NULL) - return; + pfree(so->keys); + so->keys = NULL; + so->nkeys = 0; - for (i = 0; i < nkeys; i++) + for (i = 0; i < so->totalentries; i++) { - GinScanKey key = keys + i; - - for (j = 0; j < key->nentries; j++) - { - if (key->scanEntry[j].buffer != InvalidBuffer) - ReleaseBuffer(key->scanEntry[j].buffer); - if (key->scanEntry[j].list) - pfree(key->scanEntry[j].list); - if (key->scanEntry[j].matchIterator) - tbm_end_iterate(key->scanEntry[j].matchIterator); - if (key->scanEntry[j].matchBitmap) - tbm_free(key->scanEntry[j].matchBitmap); - } - - pfree(key->entryRes); - pfree(key->scanEntry); + GinScanEntry entry = so->entries[i]; + + if (entry->buffer != InvalidBuffer) + ReleaseBuffer(entry->buffer); + if (entry->list) + pfree(entry->list); + if (entry->matchIterator) + tbm_end_iterate(entry->matchIterator); + if (entry->matchBitmap) + tbm_free(entry->matchBitmap); + pfree(entry); } - pfree(keys); + pfree(so->entries); + so->entries = NULL; + so->totalentries = 0; } void @@ -241,12 +268,18 @@ ginNewScanKey(IndexScanDesc scan) ScanKey scankey = scan->keyData; GinScanOpaque so = (GinScanOpaque) scan->opaque; int i; - uint32 nkeys = 0; bool hasNullQuery = false; /* if no scan keys provided, allocate extra EVERYTHING GinScanKey */ so->keys = (GinScanKey) 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; @@ -331,26 +364,24 @@ ginNewScanKey(IndexScanDesc scan) } /* now we can use the nullFlags as category codes */ - ginFillScanKey(&so->ginstate, &(so->keys[nkeys]), - skey->sk_attno, skey->sk_argument, + ginFillScanKey(so, skey->sk_attno, + skey->sk_strategy, searchMode, + skey->sk_argument, nQueryValues, queryValues, (GinNullCategory *) nullFlags, - partial_matches, nQueryValues, - skey->sk_strategy, extra_data, searchMode); - nkeys++; + partial_matches, extra_data); } /* * If there are no regular scan keys, generate an EVERYTHING scankey to * drive a full-index scan. */ - if (nkeys == 0 && !so->isVoidRes) + if (so->nkeys == 0 && !so->isVoidRes) { hasNullQuery = true; - ginFillScanKey(&so->ginstate, &(so->keys[nkeys]), - FirstOffsetNumber, (Datum) 0, - NULL, NULL, NULL, 0, - InvalidStrategy, NULL, GIN_SEARCH_MODE_EVERYTHING); - nkeys++; + ginFillScanKey(so, FirstOffsetNumber, + InvalidStrategy, GIN_SEARCH_MODE_EVERYTHING, + (Datum) 0, 0, + NULL, NULL, NULL, NULL); } /* @@ -371,8 +402,6 @@ ginNewScanKey(IndexScanDesc scan) RelationGetRelationName(scan->indexRelation)))); } - so->nkeys = nkeys; - pgstat_count_index_scan(scan->indexRelation); } @@ -384,8 +413,7 @@ ginrescan(PG_FUNCTION_ARGS) /* remaining arguments are ignored */ GinScanOpaque so = (GinScanOpaque) scan->opaque; - freeScanKeys(so->keys, so->nkeys); - so->keys = NULL; + freeScanKeys(so); if (scankey && scan->numberOfKeys > 0) { @@ -403,7 +431,7 @@ ginendscan(PG_FUNCTION_ARGS) IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0); GinScanOpaque so = (GinScanOpaque) scan->opaque; - freeScanKeys(so->keys, so->nkeys); + freeScanKeys(so); MemoryContextDelete(so->tempCtx); diff --git a/src/include/access/gin_private.h b/src/include/access/gin_private.h index 48531845e0a1a6959ea3ad3ed0be99dd76ecf236..6381bffb21617b306a12cfb0046a28bc18015f68 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -549,12 +549,19 @@ extern void ginPrepareDataScan(GinBtree btree, Relation index); /* ginscan.c */ /* - * GinScanKeyData describes a single GIN index qualification condition. - * It contains one GinScanEntryData for each key datum extracted from - * the qual by the extractQueryFn or added implicitly by ginFillScanKey. - * nentries is the true number of entries, nuserentries is the number - * that extractQueryFn returned (which is what we report to consistentFn). - * The "user" entries must come first. + * GinScanKeyData describes a single GIN index qualifier expression. + * + * From each qual expression, we extract one or more specific index search + * conditions, which are represented by GinScanEntryData. It's quite + * possible for identical search conditions to be requested by more than + * 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; @@ -567,10 +574,10 @@ typedef struct GinScanKeyData /* Number of entries that extractQueryFn and consistentFn know about */ uint32 nuserentries; - /* array of GinScanEntryData, one per key datum */ - GinScanEntry scanEntry; + /* array of GinScanEntry pointers, one per extracted search condition */ + GinScanEntry *scanEntry; - /* array of ItemPointer result, reported to consistentFn */ + /* array of check flags, reported to consistentFn */ bool *entryRes; /* other data needed for calling consistentFn */ @@ -583,22 +590,21 @@ typedef struct GinScanKeyData int32 searchMode; 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; + bool curItemMatches; bool recheckCurItem; - - bool firstCall; bool isFinished; } GinScanKeyData; 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 */ Datum queryKey; GinNullCategory queryCategory; @@ -634,10 +640,14 @@ typedef struct GinScanOpaqueData MemoryContext tempCtx; GinState ginstate; - GinScanKey keys; + GinScanKey keys; /* one per scan qualifier expr */ 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; typedef GinScanOpaqueData *GinScanOpaque;