diff options
Diffstat (limited to 'src/backend/access/nbtree/nbtutils.c')
-rw-r--r-- | src/backend/access/nbtree/nbtutils.c | 282 |
1 files changed, 237 insertions, 45 deletions
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c index b715383871a..90f2f64c81f 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/nbtree/nbtutils.c,v 1.69 2006/01/23 22:31:40 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/access/nbtree/nbtutils.c,v 1.70 2006/01/25 20:29:23 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -21,6 +21,12 @@ #include "executor/execdebug.h" +static void _bt_mark_scankey_required(ScanKey skey); +static bool _bt_check_rowcompare(ScanKey skey, + IndexTuple tuple, TupleDesc tupdesc, + ScanDirection dir, bool *continuescan); + + /* * _bt_mkscankey * Build an insertion scan key that contains comparison data from itup @@ -218,6 +224,17 @@ _bt_formitem(IndexTuple itup) * equality quals for all columns. In this case there can be at most one * (visible) matching tuple. index_getnext uses this to avoid uselessly * continuing the scan after finding one match. + * + * Row comparison keys are treated the same as comparisons to nondefault + * datatypes: we just transfer them into the preprocessed array without any + * editorialization. We can treat them the same as an ordinary inequality + * comparison on the row's first index column, for the purposes of the logic + * about required keys. + * + * Note: the reason we have to copy the preprocessed scan keys into private + * storage is that we are modifying the array based on comparisons of the + * key argument values, which could change on a rescan. Therefore we can't + * overwrite the caller's data structure. *---------- */ void @@ -273,26 +290,8 @@ _bt_preprocess_keys(IndexScanDesc scan) memcpy(outkeys, inkeys, sizeof(ScanKeyData)); so->numberOfKeys = 1; /* We can mark the qual as required if it's for first index col */ - if (outkeys->sk_attno == 1) - { - switch (outkeys->sk_strategy) - { - case BTLessStrategyNumber: - case BTLessEqualStrategyNumber: - outkeys->sk_flags |= SK_BT_REQFWD; - break; - case BTEqualStrategyNumber: - outkeys->sk_flags |= (SK_BT_REQFWD | SK_BT_REQBKWD); - break; - case BTGreaterEqualStrategyNumber: - case BTGreaterStrategyNumber: - outkeys->sk_flags |= SK_BT_REQBKWD; - break; - default: - elog(ERROR, "unrecognized StrategyNumber: %d", - (int) outkeys->sk_strategy); - } - } + if (cur->sk_attno == 1) + _bt_mark_scankey_required(outkeys); return; } @@ -325,6 +324,7 @@ _bt_preprocess_keys(IndexScanDesc scan) if (i < numberOfKeys) { /* See comments above: any NULL implies cannot match qual */ + /* Note: we assume SK_ISNULL is never set in a row header key */ if (cur->sk_flags & SK_ISNULL) { so->qual_ok = false; @@ -432,26 +432,7 @@ _bt_preprocess_keys(IndexScanDesc scan) memcpy(outkey, xform[j], sizeof(ScanKeyData)); if (priorNumberOfEqualCols == attno - 1) - { - switch (outkey->sk_strategy) - { - case BTLessStrategyNumber: - case BTLessEqualStrategyNumber: - outkey->sk_flags |= SK_BT_REQFWD; - break; - case BTEqualStrategyNumber: - outkey->sk_flags |= (SK_BT_REQFWD | - SK_BT_REQBKWD); - break; - case BTGreaterEqualStrategyNumber: - case BTGreaterStrategyNumber: - outkey->sk_flags |= SK_BT_REQBKWD; - break; - default: - elog(ERROR, "unrecognized StrategyNumber: %d", - (int) outkey->sk_strategy); - } - } + _bt_mark_scankey_required(outkey); } } @@ -470,11 +451,14 @@ _bt_preprocess_keys(IndexScanDesc scan) /* check strategy this key's operator corresponds to */ j = cur->sk_strategy - 1; - /* if wrong RHS data type, punt */ - if (cur->sk_subtype != InvalidOid) + /* if row comparison or wrong RHS data type, punt */ + if ((cur->sk_flags & SK_ROW_HEADER) || cur->sk_subtype != InvalidOid) { - memcpy(&outkeys[new_numberOfKeys++], cur, - sizeof(ScanKeyData)); + ScanKey outkey = &outkeys[new_numberOfKeys++]; + + memcpy(outkey, cur, sizeof(ScanKeyData)); + if (numberOfEqualCols == attno - 1) + _bt_mark_scankey_required(outkey); if (j == (BTEqualStrategyNumber - 1)) hasOtherTypeEqual = true; continue; @@ -515,6 +499,73 @@ _bt_preprocess_keys(IndexScanDesc scan) } /* + * Mark a scankey as "required to continue the scan". + * + * Depending on the operator type, the key may be required for both scan + * directions or just one. Also, if the key is a row comparison header, + * we have to mark the appropriate subsidiary ScanKeys as required. In + * such cases, the first subsidiary key is required, but subsequent ones + * are required only as long as they correspond to successive index columns. + * Otherwise the row comparison ordering is different from the index ordering + * and so we can't stop the scan on the basis of those lower-order columns. + * + * Note: when we set required-key flag bits in a subsidiary scankey, we are + * scribbling on a data structure belonging to the index AM's caller, not on + * our private copy. This should be OK because the marking will not change + * from scan to scan within a query, and so we'd just re-mark the same way + * anyway on a rescan. Something to keep an eye on though. + */ +static void +_bt_mark_scankey_required(ScanKey skey) +{ + int addflags; + + switch (skey->sk_strategy) + { + case BTLessStrategyNumber: + case BTLessEqualStrategyNumber: + addflags = SK_BT_REQFWD; + break; + case BTEqualStrategyNumber: + addflags = SK_BT_REQFWD | SK_BT_REQBKWD; + break; + case BTGreaterEqualStrategyNumber: + case BTGreaterStrategyNumber: + addflags = SK_BT_REQBKWD; + break; + default: + elog(ERROR, "unrecognized StrategyNumber: %d", + (int) skey->sk_strategy); + addflags = 0; /* keep compiler quiet */ + break; + } + + skey->sk_flags |= addflags; + + if (skey->sk_flags & SK_ROW_HEADER) + { + ScanKey subkey = (ScanKey) DatumGetPointer(skey->sk_argument); + AttrNumber attno = skey->sk_attno; + + /* First subkey should be same as the header says */ + Assert(subkey->sk_attno == attno); + + for (;;) + { + Assert(subkey->sk_flags & SK_ROW_MEMBER); + Assert(subkey->sk_strategy == skey->sk_strategy); + if (subkey->sk_attno != attno) + break; /* non-adjacent key, so not required */ + subkey->sk_flags |= addflags; + if (subkey->sk_flags & SK_ROW_END) + break; + subkey++; + attno++; + } + } +} + +/* * Test whether an indextuple satisfies all the scankey conditions. * * If so, copy its TID into scan->xs_ctup.t_self, and return TRUE. @@ -595,6 +646,14 @@ _bt_checkkeys(IndexScanDesc scan, bool isNull; Datum test; + /* row-comparison keys need special processing */ + if (key->sk_flags & SK_ROW_HEADER) + { + if (_bt_check_rowcompare(key, tuple, tupdesc, dir, continuescan)) + continue; + return false; + } + datum = index_getattr(tuple, key->sk_attno, tupdesc, @@ -660,3 +719,136 @@ _bt_checkkeys(IndexScanDesc scan, return tuple_valid; } + +/* + * Test whether an indextuple satisfies a row-comparison scan condition. + * + * Return true if so, false if not. If not, also clear *continuescan if + * it's not possible for any future tuples in the current scan direction + * to pass the qual. + * + * This is a subroutine for _bt_checkkeys, which see for more info. + */ +static bool +_bt_check_rowcompare(ScanKey skey, IndexTuple tuple, TupleDesc tupdesc, + ScanDirection dir, bool *continuescan) +{ + ScanKey subkey = (ScanKey) DatumGetPointer(skey->sk_argument); + int32 cmpresult = 0; + bool result; + + /* First subkey should be same as the header says */ + Assert(subkey->sk_attno == skey->sk_attno); + + /* Loop over columns of the row condition */ + for (;;) + { + Datum datum; + bool isNull; + + Assert(subkey->sk_flags & SK_ROW_MEMBER); + Assert(subkey->sk_strategy == skey->sk_strategy); + + datum = index_getattr(tuple, + subkey->sk_attno, + tupdesc, + &isNull); + + if (isNull) + { + /* + * Since NULLs are sorted after non-NULLs, we know we have reached + * the upper limit of the range of values for this index attr. On + * a forward scan, we can stop if this qual is one of the "must + * match" subset. On a backward scan, however, we should keep + * going. + */ + if ((subkey->sk_flags & SK_BT_REQFWD) && + ScanDirectionIsForward(dir)) + *continuescan = false; + + /* + * In any case, this indextuple doesn't match the qual. + */ + return false; + } + + if (subkey->sk_flags & SK_ISNULL) + { + /* + * Unlike the simple-scankey case, this isn't a disallowed case. + * But it can never match. If all the earlier row comparison + * columns are required for the scan direction, we can stop + * the scan, because there can't be another tuple that will + * succeed. + */ + if (subkey != (ScanKey) DatumGetPointer(skey->sk_argument)) + subkey--; + if ((subkey->sk_flags & SK_BT_REQFWD) && + ScanDirectionIsForward(dir)) + *continuescan = false; + else if ((subkey->sk_flags & SK_BT_REQBKWD) && + ScanDirectionIsBackward(dir)) + *continuescan = false; + return false; + } + + /* Perform the test --- three-way comparison not bool operator */ + cmpresult = DatumGetInt32(FunctionCall2(&subkey->sk_func, + datum, + subkey->sk_argument)); + + /* Done comparing if unequal, else advance to next column */ + if (cmpresult != 0) + break; + + if (subkey->sk_flags & SK_ROW_END) + break; + subkey++; + } + + /* + * At this point cmpresult indicates the overall result of the row + * comparison, and subkey points to the deciding column (or the last + * column if the result is "="). + */ + switch (subkey->sk_strategy) + { + /* EQ and NE cases aren't allowed here */ + case BTLessStrategyNumber: + result = (cmpresult < 0); + break; + case BTLessEqualStrategyNumber: + result = (cmpresult <= 0); + break; + case BTGreaterEqualStrategyNumber: + result = (cmpresult >= 0); + break; + case BTGreaterStrategyNumber: + result = (cmpresult > 0); + break; + default: + elog(ERROR, "unrecognized RowCompareType: %d", + (int) subkey->sk_strategy); + result = 0; /* keep compiler quiet */ + break; + } + + if (!result) + { + /* + * Tuple fails this qual. If it's a required qual for the current + * scan direction, then we can conclude no further tuples will + * pass, either. Note we have to look at the deciding column, not + * necessarily the first or last column of the row condition. + */ + if ((subkey->sk_flags & SK_BT_REQFWD) && + ScanDirectionIsForward(dir)) + *continuescan = false; + else if ((subkey->sk_flags & SK_BT_REQBKWD) && + ScanDirectionIsBackward(dir)) + *continuescan = false; + } + + return result; +} |