diff options
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/access/gin/ginget.c | 84 | ||||
-rw-r--r-- | src/backend/access/gin/ginscan.c | 127 | ||||
-rw-r--r-- | src/backend/utils/adt/selfuncs.c | 37 |
3 files changed, 173 insertions, 75 deletions
diff --git a/src/backend/access/gin/ginget.c b/src/backend/access/gin/ginget.c index 7ae4ef05c2f..50fe38b711c 100644 --- a/src/backend/access/gin/ginget.c +++ b/src/backend/access/gin/ginget.c @@ -528,8 +528,20 @@ startScanKey(GinState *ginstate, GinScanOpaque so, GinScanKey key) * order, until the consistent function says that none of the remaining * entries can form a match, without any items from the required set. The * rest go to the additional set. + * + * Exclude-only scan keys are known to have no required entries. */ - if (key->nentries > 1) + if (key->excludeOnly) + { + MemoryContextSwitchTo(so->keyCtx); + + key->nrequired = 0; + key->nadditional = key->nentries; + key->additionalEntries = palloc(key->nadditional * sizeof(GinScanEntry)); + for (i = 0; i < key->nadditional; i++) + key->additionalEntries[i] = key->scanEntry[i]; + } + else if (key->nentries > 1) { MemoryContextSwitchTo(so->tempCtx); @@ -1008,37 +1020,52 @@ keyGetItem(GinState *ginstate, MemoryContext tempCtx, GinScanKey key, minItem = entry->curItem; } - if (allFinished) + if (allFinished && !key->excludeOnly) { /* all entries are finished */ key->isFinished = true; return; } - /* - * Ok, we now know that there are no matches < minItem. - * - * If minItem is lossy, it means that there were no exact items on the - * page among requiredEntries, because lossy pointers sort after exact - * items. However, there might be exact items for the same page among - * additionalEntries, so we mustn't advance past them. - */ - if (ItemPointerIsLossyPage(&minItem)) + if (!key->excludeOnly) { - if (GinItemPointerGetBlockNumber(&advancePast) < - GinItemPointerGetBlockNumber(&minItem)) + /* + * For a normal scan key, we now know there are no matches < minItem. + * + * If minItem is lossy, it means that there were no exact items on the + * page among requiredEntries, because lossy pointers sort after exact + * items. However, there might be exact items for the same page among + * additionalEntries, so we mustn't advance past them. + */ + if (ItemPointerIsLossyPage(&minItem)) { + if (GinItemPointerGetBlockNumber(&advancePast) < + GinItemPointerGetBlockNumber(&minItem)) + { + ItemPointerSet(&advancePast, + GinItemPointerGetBlockNumber(&minItem), + InvalidOffsetNumber); + } + } + else + { + Assert(GinItemPointerGetOffsetNumber(&minItem) > 0); ItemPointerSet(&advancePast, GinItemPointerGetBlockNumber(&minItem), - InvalidOffsetNumber); + OffsetNumberPrev(GinItemPointerGetOffsetNumber(&minItem))); } } else { - Assert(GinItemPointerGetOffsetNumber(&minItem) > 0); - ItemPointerSet(&advancePast, - GinItemPointerGetBlockNumber(&minItem), - OffsetNumberPrev(GinItemPointerGetOffsetNumber(&minItem))); + /* + * excludeOnly scan keys don't have any entries that are necessarily + * present in matching items. So, we consider the item just after + * advancePast. + */ + Assert(key->nrequired == 0); + ItemPointerSet(&minItem, + GinItemPointerGetBlockNumber(&advancePast), + OffsetNumberNext(GinItemPointerGetOffsetNumber(&advancePast))); } /* @@ -1266,6 +1293,20 @@ scanGetItem(IndexScanDesc scan, ItemPointerData advancePast, { GinScanKey key = so->keys + i; + /* + * If we're considering a lossy page, skip excludeOnly keys, They + * can't exclude the whole page anyway. + */ + if (ItemPointerIsLossyPage(item) && key->excludeOnly) + { + /* + * ginNewScanKey() should never mark the first key as + * excludeOnly. + */ + Assert(i > 0); + continue; + } + /* Fetch the next item for this key that is > advancePast. */ keyGetItem(&so->ginstate, so->tempCtx, key, advancePast, scan->xs_snapshot); @@ -1736,11 +1777,14 @@ collectMatchesForHeapRow(IndexScanDesc scan, pendingPosition *pos) } /* - * Now return "true" if all scan keys have at least one matching datum + * All scan keys except excludeOnly require at least one entry to match. + * excludeOnly keys are an exception, because their implied + * GIN_CAT_EMPTY_QUERY scanEntry always matches. So return "true" if all + * non-excludeOnly scan keys have at least one match. */ for (i = 0; i < so->nkeys; i++) { - if (pos->hasMatchKey[i] == false) + if (pos->hasMatchKey[i] == false && !so->keys[i].excludeOnly) return false; } diff --git a/src/backend/access/gin/ginscan.c b/src/backend/access/gin/ginscan.c index c15d06ceba4..0a685bdbfc6 100644 --- a/src/backend/access/gin/ginscan.c +++ b/src/backend/access/gin/ginscan.c @@ -126,6 +126,27 @@ ginFillScanEntry(GinScanOpaque so, OffsetNumber attnum, } /* + * Append hidden scan entry of given category to the scan key. + * + * NB: this had better be called at most once per scan key, since + * ginFillScanKey leaves room for only one hidden entry. Currently, + * it seems sufficiently clear that this is true that we don't bother + * with any cross-check logic. + */ +static void +ginScanKeyAddHiddenEntry(GinScanOpaque so, GinScanKey key, + GinNullCategory queryCategory) +{ + int i = key->nentries++; + + /* strategy is of no interest because this is not a partial-match item */ + key->scanEntry[i] = ginFillScanEntry(so, key->attnum, + InvalidStrategy, key->searchMode, + (Datum) 0, queryCategory, + false, NULL); +} + +/* * Initialize the next GinScanKey using the output from the extractQueryFn */ static void @@ -137,17 +158,16 @@ ginFillScanKey(GinScanOpaque so, OffsetNumber attnum, { GinScanKey key = &(so->keys[so->nkeys++]); GinState *ginstate = &so->ginstate; - uint32 nUserQueryValues = nQueryValues; uint32 i; - /* Non-default search modes add one "hidden" entry to each key */ - if (searchMode != GIN_SEARCH_MODE_DEFAULT) - nQueryValues++; key->nentries = nQueryValues; - key->nuserentries = nUserQueryValues; + key->nuserentries = nQueryValues; - key->scanEntry = (GinScanEntry *) palloc(sizeof(GinScanEntry) * nQueryValues); - key->entryRes = (GinTernaryValue *) palloc0(sizeof(GinTernaryValue) * nQueryValues); + /* Allocate one extra array slot for possible "hidden" entry */ + key->scanEntry = (GinScanEntry *) palloc(sizeof(GinScanEntry) * + (nQueryValues + 1)); + key->entryRes = (GinTernaryValue *) palloc0(sizeof(GinTernaryValue) * + (nQueryValues + 1)); key->query = query; key->queryValues = queryValues; @@ -157,6 +177,12 @@ ginFillScanKey(GinScanOpaque so, OffsetNumber attnum, key->searchMode = searchMode; key->attnum = attnum; + /* + * Initially, scan keys of GIN_SEARCH_MODE_ALL mode are marked + * excludeOnly. This might get changed later. + */ + key->excludeOnly = (searchMode == GIN_SEARCH_MODE_ALL); + ItemPointerSetMin(&key->curItem); key->curItemMatches = false; key->recheckCurItem = false; @@ -168,6 +194,7 @@ ginFillScanKey(GinScanOpaque so, OffsetNumber attnum, ginInitConsistentFunction(ginstate, key); + /* Set up normal scan entries using extractQueryFn's outputs */ for (i = 0; i < nQueryValues; i++) { Datum queryKey; @@ -175,54 +202,28 @@ ginFillScanKey(GinScanOpaque so, OffsetNumber attnum, bool isPartialMatch; Pointer this_extra; - if (i < nUserQueryValues) - { - /* 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; - this_extra = (extra_data) ? extra_data[i] : NULL; - } - else - { - /* set up hidden entry */ - queryKey = (Datum) 0; - switch (searchMode) - { - case GIN_SEARCH_MODE_INCLUDE_EMPTY: - queryCategory = GIN_CAT_EMPTY_ITEM; - break; - case GIN_SEARCH_MODE_ALL: - queryCategory = GIN_CAT_EMPTY_QUERY; - break; - case GIN_SEARCH_MODE_EVERYTHING: - queryCategory = GIN_CAT_EMPTY_QUERY; - break; - default: - elog(ERROR, "unexpected searchMode: %d", searchMode); - queryCategory = 0; /* keep compiler quiet */ - break; - } - 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; - } + queryKey = queryValues[i]; + queryCategory = queryCategories[i]; + isPartialMatch = + (ginstate->canPartialMatch[attnum - 1] && partial_matches) + ? partial_matches[i] : false; + this_extra = (extra_data) ? extra_data[i] : NULL; key->scanEntry[i] = ginFillScanEntry(so, attnum, strategy, searchMode, queryKey, queryCategory, isPartialMatch, this_extra); } + + /* + * For GIN_SEARCH_MODE_INCLUDE_EMPTY and GIN_SEARCH_MODE_EVERYTHING search + * modes, we add the "hidden" entry immediately. GIN_SEARCH_MODE_ALL is + * handled later, since we might be able to omit the hidden entry for it. + */ + if (searchMode == GIN_SEARCH_MODE_INCLUDE_EMPTY) + ginScanKeyAddHiddenEntry(so, key, GIN_CAT_EMPTY_ITEM); + else if (searchMode == GIN_SEARCH_MODE_EVERYTHING) + ginScanKeyAddHiddenEntry(so, key, GIN_CAT_EMPTY_QUERY); } /* @@ -265,6 +266,7 @@ ginNewScanKey(IndexScanDesc scan) GinScanOpaque so = (GinScanOpaque) scan->opaque; int i; bool hasNullQuery = false; + bool attrHasNormalScan[INDEX_MAX_KEYS] = {false}; MemoryContext oldCtx; /* @@ -371,6 +373,33 @@ ginNewScanKey(IndexScanDesc scan) skey->sk_argument, nQueryValues, queryValues, categories, partial_matches, extra_data); + + /* Remember if we had any non-excludeOnly keys */ + if (searchMode != GIN_SEARCH_MODE_ALL) + attrHasNormalScan[skey->sk_attno - 1] = true; + } + + /* + * Processing GIN_SEARCH_MODE_ALL scan keys requires us to make a second + * pass over the scan keys. Above we marked each such scan key as + * excludeOnly. If the involved column has any normal (not excludeOnly) + * scan key as well, then we can leave it like that. Otherwise, one + * excludeOnly scan key must receive a GIN_CAT_EMPTY_QUERY hidden entry + * and be set to normal (excludeOnly = false). + */ + for (i = 0; i < so->nkeys; i++) + { + GinScanKey key = &so->keys[i]; + + if (key->searchMode != GIN_SEARCH_MODE_ALL) + continue; + + if (!attrHasNormalScan[key->attnum - 1]) + { + key->excludeOnly = false; + ginScanKeyAddHiddenEntry(so, key, GIN_CAT_EMPTY_QUERY); + attrHasNormalScan[key->attnum - 1] = true; + } } /* diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index 18d77ac0b77..7c6f0574b37 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -6356,7 +6356,8 @@ spgcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, typedef struct { - bool haveFullScan; + bool attHasFullScan[INDEX_MAX_KEYS]; + bool attHasNormalScan[INDEX_MAX_KEYS]; double partialEntries; double exactEntries; double searchEntries; @@ -6452,16 +6453,21 @@ gincost_pattern(IndexOptInfo *index, int indexcol, counts->searchEntries++; } - if (searchMode == GIN_SEARCH_MODE_INCLUDE_EMPTY) + if (searchMode == GIN_SEARCH_MODE_DEFAULT) + { + counts->attHasNormalScan[indexcol] = true; + } + else if (searchMode == GIN_SEARCH_MODE_INCLUDE_EMPTY) { /* Treat "include empty" like an exact-match item */ + counts->attHasNormalScan[indexcol] = true; counts->exactEntries++; counts->searchEntries++; } - else if (searchMode != GIN_SEARCH_MODE_DEFAULT) + else { /* It's GIN_SEARCH_MODE_ALL */ - counts->haveFullScan = true; + counts->attHasFullScan[indexcol] = true; } return true; @@ -6597,7 +6603,8 @@ gincost_scalararrayopexpr(PlannerInfo *root, /* We ignore array elements that are unsatisfiable patterns */ numPossible++; - if (elemcounts.haveFullScan) + if (elemcounts.attHasFullScan[indexcol] && + !elemcounts.attHasNormalScan[indexcol]) { /* * Full index scan will be required. We treat this as if @@ -6654,6 +6661,7 @@ gincostestimate(PlannerInfo *root, IndexPath *path, double loop_count, numEntries; GinQualCounts counts; bool matchPossible; + bool fullIndexScan; double partialScale; double entryPagesFetched, dataPagesFetched, @@ -6665,6 +6673,7 @@ gincostestimate(PlannerInfo *root, IndexPath *path, double loop_count, Relation indexRel; GinStatsData ginStats; ListCell *lc; + int i; /* * Obtain statistical information from the meta page, if possible. Else @@ -6821,7 +6830,23 @@ gincostestimate(PlannerInfo *root, IndexPath *path, double loop_count, return; } - if (counts.haveFullScan || indexQuals == NIL) + /* + * If attribute has a full scan and at the same time doesn't have normal + * scan, then we'll have to scan all non-null entries of that attribute. + * Currently, we don't have per-attribute statistics for GIN. Thus, we + * must assume the whole GIN index has to be scanned in this case. + */ + fullIndexScan = false; + for (i = 0; i < index->nkeycolumns; i++) + { + if (counts.attHasFullScan[i] && !counts.attHasNormalScan[i]) + { + fullIndexScan = true; + break; + } + } + + if (fullIndexScan || indexQuals == NIL) { /* * Full index scan will be required. We treat this as if every key in |