/* remember which buffer we have pinned, if any */
Assert(!BTScanPosIsValid(so->currPos));
so->currPos.buf = buf;
+ so->firstPage = true;
/*
* Now load data from the first page of the scan.
int itemIndex;
bool continuescan;
int indnatts;
+ bool requiredMatchedByPrecheck;
/*
* We must have the buffer pinned and locked, but the usual macro can't be
*/
Assert(BTScanPosIsPinned(so->currPos));
+ /*
+ * Prechecking the page with scan keys required for direction scan. We
+ * check these keys with the last item on the page (according to our scan
+ * direction). If these keys are matched, we can skip checking them with
+ * every item on the page. Scan keys for our scan direction would
+ * necessarily match the previous items. Scan keys required for opposite
+ * direction scan are already matched by the _bt_first() call.
+ *
+ * With the forward scan, we do this check for the last item on the page
+ * instead of the high key. It's relatively likely that the most
+ * significant column in the high key will be different from the
+ * corresponding value from the last item on the page. So checking with
+ * the last item on the page would give a more precise answer.
+ *
+ * We skip this for the first page in the scan to evade the possible
+ * slowdown of the point queries.
+ */
+ if (!so->firstPage && minoff < maxoff)
+ {
+ ItemId iid;
+ IndexTuple itup;
+
+ iid = PageGetItemId(page, ScanDirectionIsForward(dir) ? maxoff : minoff);
+ itup = (IndexTuple) PageGetItem(page, iid);
+
+ /*
+ * Do the precheck. Note that we pass the pointer to
+ * 'requiredMatchedByPrecheck' to 'continuescan' argument. That will
+ * set flag to true if all required keys are satisfied and false
+ * otherwise.
+ */
+ (void) _bt_checkkeys(scan, itup, indnatts, dir,
+ &requiredMatchedByPrecheck, false);
+ }
+ else
+ {
+ so->firstPage = false;
+ requiredMatchedByPrecheck = false;
+ }
+
if (ScanDirectionIsForward(dir))
{
/* load items[] in ascending order */
{
ItemId iid = PageGetItemId(page, offnum);
IndexTuple itup;
+ bool passes_quals;
/*
* If the scan specifies not to return killed tuples, then we
itup = (IndexTuple) PageGetItem(page, iid);
- if (_bt_checkkeys(scan, itup, indnatts, dir, &continuescan))
+ passes_quals = _bt_checkkeys(scan, itup, indnatts, dir,
+ &continuescan, requiredMatchedByPrecheck);
+
+ /*
+ * If the result of prechecking required keys was true, then in
+ * assert-enabled builds we also recheck that _bt_checkkeys()
+ * result is is the same.
+ */
+ Assert(!requiredMatchedByPrecheck ||
+ passes_quals == _bt_checkkeys(scan, itup, indnatts, dir,
+ &continuescan, false));
+ if (passes_quals)
{
/* tuple passes all scan key conditions */
if (!BTreeTupleIsPosting(itup))
int truncatt;
truncatt = BTreeTupleGetNAtts(itup, scan->indexRelation);
- _bt_checkkeys(scan, itup, truncatt, dir, &continuescan);
+ _bt_checkkeys(scan, itup, truncatt, dir, &continuescan, false);
}
if (!continuescan)
itup = (IndexTuple) PageGetItem(page, iid);
passes_quals = _bt_checkkeys(scan, itup, indnatts, dir,
- &continuescan);
+ &continuescan, requiredMatchedByPrecheck);
+
+ /*
+ * If the result of prechecking required keys was true, then in
+ * assert-enabled builds we also recheck that _bt_checkkeys()
+ * result is is the same.
+ */
+ Assert(!requiredMatchedByPrecheck ||
+ passes_quals == _bt_checkkeys(scan, itup, indnatts, dir,
+ &continuescan, false));
if (passes_quals && tuple_alive)
{
/* tuple passes all scan key conditions */
/* remember which buffer we have pinned */
so->currPos.buf = buf;
+ so->firstPage = true;
_bt_initialize_more_data(so, dir);
* tupnatts: number of attributes in tupnatts (high key may be truncated)
* dir: direction we are scanning in
* continuescan: output parameter (will be set correctly in all cases)
+ * requiredMatchedByPrecheck: indicates that scan keys required for
+ * direction scan are already matched
*/
bool
_bt_checkkeys(IndexScanDesc scan, IndexTuple tuple, int tupnatts,
- ScanDirection dir, bool *continuescan)
+ ScanDirection dir, bool *continuescan,
+ bool requiredMatchedByPrecheck)
{
TupleDesc tupdesc;
BTScanOpaque so;
Datum datum;
bool isNull;
Datum test;
+ bool requiredSameDir = false,
+ requiredOppositeDir = false;
+
+ /*
+ * Check if the key is required for ordered scan in the same or
+ * opposite direction. Save as flag variables for future usage.
+ */
+ if (((key->sk_flags & SK_BT_REQFWD) && ScanDirectionIsForward(dir)) ||
+ ((key->sk_flags & SK_BT_REQBKWD) && ScanDirectionIsBackward(dir)))
+ requiredSameDir = true;
+ else if (((key->sk_flags & SK_BT_REQFWD) && ScanDirectionIsBackward(dir)) ||
+ ((key->sk_flags & SK_BT_REQBKWD) && ScanDirectionIsForward(dir)))
+ requiredOppositeDir = true;
+
+ /*
+ * Is the key required for scanning for either forward or backward
+ * direction? If so and called told us that these types of keys are
+ * known to be matched, skip the check. Except for the row keys,
+ * where NULLs could be found in the middle of matching values.
+ */
+ if ((requiredSameDir || requiredOppositeDir) &&
+ !(key->sk_flags & SK_ROW_HEADER) && requiredMatchedByPrecheck)
+ continue;
if (key->sk_attno > tupnatts)
{
* scan direction, then we can conclude no further tuples will
* pass, either.
*/
- if ((key->sk_flags & SK_BT_REQFWD) &&
- ScanDirectionIsForward(dir))
- *continuescan = false;
- else if ((key->sk_flags & SK_BT_REQBKWD) &&
- ScanDirectionIsBackward(dir))
+ if (requiredSameDir)
*continuescan = false;
/*
return false;
}
- test = FunctionCall2Coll(&key->sk_func, key->sk_collation,
- datum, key->sk_argument);
+ /*
+ * Apply the key checking function. When the key is required for
+ * opposite direction scan, it must be already satisfied by
+ * _bt_first() except for the NULLs checking, which have already done
+ * above.
+ */
+ if (!requiredOppositeDir)
+ {
+ test = FunctionCall2Coll(&key->sk_func, key->sk_collation,
+ datum, key->sk_argument);
+ }
+ else
+ {
+ test = true;
+ Assert(test == FunctionCall2Coll(&key->sk_func, key->sk_collation,
+ datum, key->sk_argument));
+ }
if (!DatumGetBool(test))
{
* initial positioning in _bt_first() when they are available. See
* comments in _bt_first().
*/
- if ((key->sk_flags & SK_BT_REQFWD) &&
- ScanDirectionIsForward(dir))
- *continuescan = false;
- else if ((key->sk_flags & SK_BT_REQBKWD) &&
- ScanDirectionIsBackward(dir))
+ if (requiredSameDir)
*continuescan = false;
/*
int *killedItems; /* currPos.items indexes of killed items */
int numKilled; /* number of currently stored items */
+ /* flag indicating the first page in the scan */
+ bool firstPage;
+
/*
* If we are doing an index-only scan, these are the tuple storage
* workspaces for the currPos and markPos respectively. Each is of size
extern void _bt_restore_array_keys(IndexScanDesc scan);
extern void _bt_preprocess_keys(IndexScanDesc scan);
extern bool _bt_checkkeys(IndexScanDesc scan, IndexTuple tuple,
- int tupnatts, ScanDirection dir, bool *continuescan);
+ int tupnatts, ScanDirection dir, bool *continuescan,
+ bool requiredMatchedByPrecheck);
extern void _bt_killitems(IndexScanDesc scan);
extern BTCycleId _bt_vacuum_cycleid(Relation rel);
extern BTCycleId _bt_start_vacuum(Relation rel);