postgresql/src/backend/access/gin/ginscan.c

308 lines
7.3 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* ginscan.c
2006-10-04 02:30:14 +02:00
* routines to manage scans inverted index relations
*
*
2010-01-02 17:58:17 +01:00
* Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
2010-09-20 22:08:53 +02:00
* src/backend/access/gin/ginscan.c
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/gin.h"
#include "access/relscan.h"
#include "pgstat.h"
#include "storage/bufmgr.h"
#include "utils/memutils.h"
#include "utils/rel.h"
2006-10-04 02:30:14 +02:00
Datum
ginbeginscan(PG_FUNCTION_ARGS)
{
Relation rel = (Relation) PG_GETARG_POINTER(0);
int keysz = PG_GETARG_INT32(1);
ScanKey scankey = (ScanKey) PG_GETARG_POINTER(2);
IndexScanDesc scan;
scan = RelationGetIndexScan(rel, keysz, scankey);
PG_RETURN_POINTER(scan);
}
static void
fillScanKey(GinState *ginstate, GinScanKey key, OffsetNumber attnum, Datum query,
Datum *entryValues, bool *partial_matches, uint32 nEntryValues,
StrategyNumber strategy, Pointer *extra_data)
2006-10-04 02:30:14 +02:00
{
uint32 i,
j;
key->nentries = nEntryValues;
2006-10-04 02:30:14 +02:00
key->entryRes = (bool *) palloc0(sizeof(bool) * nEntryValues);
key->scanEntry = (GinScanEntry) palloc(sizeof(GinScanEntryData) * nEntryValues);
key->strategy = strategy;
key->attnum = attnum;
key->extra_data = extra_data;
key->query = query;
2006-10-04 02:30:14 +02:00
key->firstCall = TRUE;
Rewrite the key-combination logic in GIN's keyGetItem() and scanGetItem() routines to make them behave better in the presence of "lossy" index pointers. The previous coding was outright incorrect for some cases, as recently reported by Artur Dabrowski: scanGetItem would fail to return index entries in cases where one index key had multiple exact pointers on the same page as another key had a lossy pointer. Also, keyGetItem was extremely inefficient for cases where a single index key generates multiple "entry" streams, such as an @@ operator with a multiple-clause tsquery. The presence of a lossy page pointer in any one stream defeated its ability to use the opclass consistentFn, resulting in probing many heap pages that didn't really need to be visited. In Artur's example case, a query like WHERE tsvector @@ to_tsquery('a & b') was about 50X slower than the theoretically equivalent WHERE tsvector @@ to_tsquery('a') AND tsvector @@ to_tsquery('b') The way that I chose to fix this was to have GIN call the consistentFn twice with both TRUE and FALSE values for the in-doubt entry stream, returning a hit if either call produces TRUE, but not if they both return FALSE. The code handles this for the case of a single in-doubt entry stream, but punts (falling back to the stupid behavior) if there's more than one lossy reference to the same page. The idea could be scaled up to deal with multiple lossy references, but I think that would probably be wasted complexity. At least to judge by Artur's example, such cases don't occur often enough to be worth trying to optimize. Back-patch to 8.4. 8.3 did not have lossy GIN index pointers, so not subject to these problems.
2010-07-31 02:30:54 +02:00
ItemPointerSetMin(&key->curItem);
2006-10-04 02:30:14 +02:00
for (i = 0; i < nEntryValues; i++)
{
key->scanEntry[i].pval = key->entryRes + i;
key->scanEntry[i].entry = entryValues[i];
key->scanEntry[i].attnum = attnum;
key->scanEntry[i].extra_data = (extra_data) ? extra_data[i] : NULL;
Rewrite the key-combination logic in GIN's keyGetItem() and scanGetItem() routines to make them behave better in the presence of "lossy" index pointers. The previous coding was outright incorrect for some cases, as recently reported by Artur Dabrowski: scanGetItem would fail to return index entries in cases where one index key had multiple exact pointers on the same page as another key had a lossy pointer. Also, keyGetItem was extremely inefficient for cases where a single index key generates multiple "entry" streams, such as an @@ operator with a multiple-clause tsquery. The presence of a lossy page pointer in any one stream defeated its ability to use the opclass consistentFn, resulting in probing many heap pages that didn't really need to be visited. In Artur's example case, a query like WHERE tsvector @@ to_tsquery('a & b') was about 50X slower than the theoretically equivalent WHERE tsvector @@ to_tsquery('a') AND tsvector @@ to_tsquery('b') The way that I chose to fix this was to have GIN call the consistentFn twice with both TRUE and FALSE values for the in-doubt entry stream, returning a hit if either call produces TRUE, but not if they both return FALSE. The code handles this for the case of a single in-doubt entry stream, but punts (falling back to the stupid behavior) if there's more than one lossy reference to the same page. The idea could be scaled up to deal with multiple lossy references, but I think that would probably be wasted complexity. At least to judge by Artur's example, such cases don't occur often enough to be worth trying to optimize. Back-patch to 8.4. 8.3 did not have lossy GIN index pointers, so not subject to these problems.
2010-07-31 02:30:54 +02:00
ItemPointerSetMin(&key->scanEntry[i].curItem);
key->scanEntry[i].isFinished = FALSE;
key->scanEntry[i].offset = InvalidOffsetNumber;
key->scanEntry[i].buffer = InvalidBuffer;
key->scanEntry[i].partialMatch = NULL;
key->scanEntry[i].partialMatchIterator = NULL;
key->scanEntry[i].partialMatchResult = NULL;
key->scanEntry[i].strategy = strategy;
key->scanEntry[i].list = NULL;
key->scanEntry[i].nlist = 0;
key->scanEntry[i].isPartialMatch = (ginstate->canPartialMatch[attnum - 1] && partial_matches)
? partial_matches[i] : false;
/* link to the equals entry in current scan key */
key->scanEntry[i].master = NULL;
2006-10-04 02:30:14 +02:00
for (j = 0; j < i; j++)
if (ginCompareEntries(ginstate, attnum,
entryValues[i], entryValues[j]) == 0 &&
key->scanEntry[i].isPartialMatch == key->scanEntry[j].isPartialMatch &&
key->scanEntry[i].strategy == key->scanEntry[j].strategy)
2006-10-04 02:30:14 +02:00
{
key->scanEntry[i].master = key->scanEntry + j;
break;
}
}
}
2006-05-02 17:48:11 +02:00
#ifdef NOT_USED
static void
2006-10-04 02:30:14 +02:00
resetScanKeys(GinScanKey keys, uint32 nkeys)
{
uint32 i,
j;
2006-10-04 02:30:14 +02:00
if (keys == NULL)
return;
2006-10-04 02:30:14 +02:00
for (i = 0; i < nkeys; i++)
{
GinScanKey key = keys + i;
key->firstCall = TRUE;
Rewrite the key-combination logic in GIN's keyGetItem() and scanGetItem() routines to make them behave better in the presence of "lossy" index pointers. The previous coding was outright incorrect for some cases, as recently reported by Artur Dabrowski: scanGetItem would fail to return index entries in cases where one index key had multiple exact pointers on the same page as another key had a lossy pointer. Also, keyGetItem was extremely inefficient for cases where a single index key generates multiple "entry" streams, such as an @@ operator with a multiple-clause tsquery. The presence of a lossy page pointer in any one stream defeated its ability to use the opclass consistentFn, resulting in probing many heap pages that didn't really need to be visited. In Artur's example case, a query like WHERE tsvector @@ to_tsquery('a & b') was about 50X slower than the theoretically equivalent WHERE tsvector @@ to_tsquery('a') AND tsvector @@ to_tsquery('b') The way that I chose to fix this was to have GIN call the consistentFn twice with both TRUE and FALSE values for the in-doubt entry stream, returning a hit if either call produces TRUE, but not if they both return FALSE. The code handles this for the case of a single in-doubt entry stream, but punts (falling back to the stupid behavior) if there's more than one lossy reference to the same page. The idea could be scaled up to deal with multiple lossy references, but I think that would probably be wasted complexity. At least to judge by Artur's example, such cases don't occur often enough to be worth trying to optimize. Back-patch to 8.4. 8.3 did not have lossy GIN index pointers, so not subject to these problems.
2010-07-31 02:30:54 +02:00
ItemPointerSetMin(&key->curItem);
2006-10-04 02:30:14 +02:00
for (j = 0; j < key->nentries; j++)
{
if (key->scanEntry[j].buffer != InvalidBuffer)
ReleaseBuffer(key->scanEntry[i].buffer);
Rewrite the key-combination logic in GIN's keyGetItem() and scanGetItem() routines to make them behave better in the presence of "lossy" index pointers. The previous coding was outright incorrect for some cases, as recently reported by Artur Dabrowski: scanGetItem would fail to return index entries in cases where one index key had multiple exact pointers on the same page as another key had a lossy pointer. Also, keyGetItem was extremely inefficient for cases where a single index key generates multiple "entry" streams, such as an @@ operator with a multiple-clause tsquery. The presence of a lossy page pointer in any one stream defeated its ability to use the opclass consistentFn, resulting in probing many heap pages that didn't really need to be visited. In Artur's example case, a query like WHERE tsvector @@ to_tsquery('a & b') was about 50X slower than the theoretically equivalent WHERE tsvector @@ to_tsquery('a') AND tsvector @@ to_tsquery('b') The way that I chose to fix this was to have GIN call the consistentFn twice with both TRUE and FALSE values for the in-doubt entry stream, returning a hit if either call produces TRUE, but not if they both return FALSE. The code handles this for the case of a single in-doubt entry stream, but punts (falling back to the stupid behavior) if there's more than one lossy reference to the same page. The idea could be scaled up to deal with multiple lossy references, but I think that would probably be wasted complexity. At least to judge by Artur's example, such cases don't occur often enough to be worth trying to optimize. Back-patch to 8.4. 8.3 did not have lossy GIN index pointers, so not subject to these problems.
2010-07-31 02:30:54 +02:00
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].partialMatch = NULL;
key->scanEntry[j].partialMatchIterator = NULL;
key->scanEntry[j].partialMatchResult = NULL;
}
}
}
2006-05-02 17:48:11 +02:00
#endif
static void
freeScanKeys(GinScanKey keys, uint32 nkeys)
2006-10-04 02:30:14 +02:00
{
uint32 i,
j;
2006-10-04 02:30:14 +02:00
if (keys == NULL)
return;
2006-10-04 02:30:14 +02:00
for (i = 0; i < nkeys; i++)
{
GinScanKey key = keys + i;
2006-10-04 02:30:14 +02:00
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].partialMatchIterator)
tbm_end_iterate(key->scanEntry[j].partialMatchIterator);
if (key->scanEntry[j].partialMatch)
tbm_free(key->scanEntry[j].partialMatch);
}
pfree(key->entryRes);
pfree(key->scanEntry);
}
2006-10-04 02:30:14 +02:00
pfree(keys);
}
void
ginNewScanKey(IndexScanDesc scan)
2006-10-04 02:30:14 +02:00
{
ScanKey scankey = scan->keyData;
GinScanOpaque so = (GinScanOpaque) scan->opaque;
2006-10-04 02:30:14 +02:00
int i;
uint32 nkeys = 0;
if (scan->numberOfKeys < 1)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2006-10-04 02:30:14 +02:00
errmsg("GIN indexes do not support whole-index scans")));
so->keys = (GinScanKey) palloc(scan->numberOfKeys * sizeof(GinScanKeyData));
so->isVoidRes = false;
2006-10-04 02:30:14 +02:00
for (i = 0; i < scan->numberOfKeys; i++)
{
ScanKey skey = &scankey[i];
2006-10-04 02:30:14 +02:00
Datum *entryValues;
int32 nEntryValues = 0;
bool *partial_matches = NULL;
Pointer *extra_data = NULL;
/*
* Assume, that GIN-indexable operators are strict, so nothing could
* be found
*/
if (skey->sk_flags & SK_ISNULL)
{
so->isVoidRes = true;
break;
}
entryValues = (Datum *)
DatumGetPointer(FunctionCall5(&so->ginstate.extractQueryFn[skey->sk_attno - 1],
skey->sk_argument,
PointerGetDatum(&nEntryValues),
UInt16GetDatum(skey->sk_strategy),
PointerGetDatum(&partial_matches),
PointerGetDatum(&extra_data)));
2007-11-15 22:14:46 +01:00
if (nEntryValues < 0)
{
/*
* extractQueryFn signals that nothing can match, so we can just
* set isVoidRes flag. No need to examine any more keys.
*/
2007-11-15 22:14:46 +01:00
so->isVoidRes = true;
break;
}
2006-10-04 02:30:14 +02:00
if (entryValues == NULL || nEntryValues == 0)
{
/*
* extractQueryFn signals that everything matches. This would
* require a full scan, which we can't do, but perhaps there is
* another scankey that provides a restriction to use. So we keep
* going and check only at the end.
*/
continue;
}
fillScanKey(&so->ginstate, &(so->keys[nkeys]),
skey->sk_attno, skey->sk_argument,
entryValues, partial_matches, nEntryValues,
skey->sk_strategy, extra_data);
nkeys++;
}
if (nkeys == 0 && !so->isVoidRes)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("GIN indexes do not support whole-index scans")));
so->nkeys = nkeys;
pgstat_count_index_scan(scan->indexRelation);
}
Datum
2006-10-04 02:30:14 +02:00
ginrescan(PG_FUNCTION_ARGS)
{
IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
2006-10-04 02:30:14 +02:00
ScanKey scankey = (ScanKey) PG_GETARG_POINTER(1);
GinScanOpaque so;
so = (GinScanOpaque) scan->opaque;
2006-10-04 02:30:14 +02:00
if (so == NULL)
{
/* if called from ginbeginscan */
2006-10-04 02:30:14 +02:00
so = (GinScanOpaque) palloc(sizeof(GinScanOpaqueData));
so->tempCtx = AllocSetContextCreate(CurrentMemoryContext,
2006-10-04 02:30:14 +02:00
"Gin scan temporary context",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
initGinState(&so->ginstate, scan->indexRelation);
scan->opaque = so;
2006-10-04 02:30:14 +02:00
}
else
{
freeScanKeys(so->keys, so->nkeys);
}
so->keys = NULL;
2006-10-04 02:30:14 +02:00
if (scankey && scan->numberOfKeys > 0)
{
memmove(scan->keyData, scankey,
2006-10-04 02:30:14 +02:00
scan->numberOfKeys * sizeof(ScanKeyData));
}
PG_RETURN_VOID();
}
Datum
2006-10-04 02:30:14 +02:00
ginendscan(PG_FUNCTION_ARGS)
{
IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
2006-10-04 02:30:14 +02:00
GinScanOpaque so = (GinScanOpaque) scan->opaque;
2006-10-04 02:30:14 +02:00
if (so != NULL)
{
freeScanKeys(so->keys, so->nkeys);
MemoryContextDelete(so->tempCtx);
pfree(so);
}
PG_RETURN_VOID();
}
2006-10-04 02:30:14 +02:00
Datum
ginmarkpos(PG_FUNCTION_ARGS)
{
elog(ERROR, "GIN does not support mark/restore");
PG_RETURN_VOID();
}
2006-10-04 02:30:14 +02:00
Datum
ginrestrpos(PG_FUNCTION_ARGS)
{
elog(ERROR, "GIN does not support mark/restore");
PG_RETURN_VOID();
}