aboutsummaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/access/gin/ginget.c84
-rw-r--r--src/backend/access/gin/ginscan.c127
-rw-r--r--src/backend/utils/adt/selfuncs.c37
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