Skip to content

Commit 6c3b1df

Browse files
Document nbtree row comparison design.
Add comments explaining when and where it is safe for nbtree to treat row compare keys as if they were simple scalar inequality keys on the row's most significant column. This is particularly important within _bt_advance_array_keys, which deals with required inequality keys in a general and uniform way, without any special handling for row compares. Also spell out the implications of _bt_check_rowcompare's approach of _conditionally_ evaluating lower-order row compare subkeys, particularly when one of its lower-order subkeys might see NULL index tuple values (these may or may not affect whether the qual as a whole is satisfied). The behavior in this area isn't particularly intuitive, so these issues seem worth going into. In passing, add a few more defensive/documenting row comparison related assertions to _bt_first and _bt_check_rowcompare. Follow-up to commits bd3f59f and ec98602. Author: Peter Geoghegan <pg@bowt.ie> Reviewed-By: Victor Yegorov <vyegorov@gmail.com> Reviewed-By: Chao Li <li.evan.chao@gmail.com> Discussion: https://postgr.es/m/CAH2-Wznwkak_K7pcAdv9uH8ZfNo8QO7+tHXOaCUddMeTfaCCFw@mail.gmail.com Backpatch-through: 18
1 parent 742bd91 commit 6c3b1df

File tree

2 files changed

+58
-11
lines changed

2 files changed

+58
-11
lines changed

src/backend/access/nbtree/nbtsearch.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1290,6 +1290,8 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
12901290
* our row compare header key must be the final startKeys[] entry.
12911291
*/
12921292
Assert(subkey->sk_flags & (SK_BT_REQFWD | SK_BT_REQBKWD));
1293+
Assert(subkey->sk_strategy == bkey->sk_strategy);
1294+
Assert(subkey->sk_strategy == strat_total);
12931295
Assert(i == keysz - 1);
12941296

12951297
/*
@@ -1346,9 +1348,9 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
13461348
Assert(subkey->sk_strategy == bkey->sk_strategy);
13471349
Assert(keysz < INDEX_MAX_KEYS);
13481350

1349-
memcpy(inskey.scankeys + keysz, subkey,
1350-
sizeof(ScanKeyData));
1351+
memcpy(inskey.scankeys + keysz, subkey, sizeof(ScanKeyData));
13511352
keysz++;
1353+
13521354
if (subkey->sk_flags & SK_ROW_END)
13531355
break;
13541356
}
@@ -1380,7 +1382,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
13801382
}
13811383
}
13821384

1383-
/* done adding to inskey (row comparison keys always come last) */
1385+
/* Done (row compare header key is always last startKeys[] key) */
13841386
break;
13851387
}
13861388

src/backend/access/nbtree/nbtutils.c

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ static bool _bt_check_compare(IndexScanDesc scan, ScanDirection dir,
5959
IndexTuple tuple, int tupnatts, TupleDesc tupdesc,
6060
bool advancenonrequired, bool forcenonrequired,
6161
bool *continuescan, int *ikey);
62-
static bool _bt_check_rowcompare(ScanKey skey,
62+
static bool _bt_check_rowcompare(ScanKey header,
6363
IndexTuple tuple, int tupnatts, TupleDesc tupdesc,
6464
ScanDirection dir, bool forcenonrequired, bool *continuescan);
6565
static void _bt_checkkeys_look_ahead(IndexScanDesc scan, BTReadPageState *pstate,
@@ -2911,19 +2911,64 @@ _bt_check_compare(IndexScanDesc scan, ScanDirection dir,
29112911
* it's not possible for any future tuples in the current scan direction
29122912
* to pass the qual.
29132913
*
2914-
* This is a subroutine for _bt_checkkeys/_bt_check_compare.
2914+
* This is a subroutine for _bt_checkkeys/_bt_check_compare. Caller passes us
2915+
* a row compare header key taken from so->keyData[].
2916+
*
2917+
* Row value comparisons can be described in terms of logical expansions that
2918+
* use only scalar operators. Consider the following example row comparison:
2919+
*
2920+
* "(a, b, c) > (7, 'bar', 62)"
2921+
*
2922+
* This can be evaluated as:
2923+
*
2924+
* "(a = 7 AND b = 'bar' AND c > 62) OR (a = 7 AND b > 'bar') OR (a > 7)".
2925+
*
2926+
* Notice that this condition is satisfied by _all_ rows that satisfy "a > 7",
2927+
* and by a subset of all rows that satisfy "a >= 7" (possibly all such rows).
2928+
* It _can't_ be satisfied by other rows (where "a < 7" or where "a IS NULL").
2929+
* A row comparison header key can therefore often be treated as if it was a
2930+
* simple scalar inequality on the row compare's most significant column.
2931+
* (For example, _bt_advance_array_keys and most preprocessing routines treat
2932+
* row compares like any other same-strategy inequality on the same column.)
2933+
*
2934+
* Things get more complicated for our row compare given a row where "a = 7".
2935+
* Note that a row compare isn't necessarily satisfied by _every_ tuple that
2936+
* appears between the first and last satisfied tuple returned by the scan,
2937+
* due to the way that its lower-order subkeys are only conditionally applied.
2938+
* A forwards scan that uses our example qual might initially return a tuple
2939+
* "(a, b, c) = (7, 'zebra', 54)". But it won't subsequently return a tuple
2940+
* "(a, b, c) = (7, NULL, 1)" located to the right of the first matching tuple
2941+
* (assume that "b" was declared NULLS LAST here). The scan will only return
2942+
* additional matches upon reaching tuples where "a > 7". If you rereview our
2943+
* example row comparison's logical expansion, you'll understand why this is.
2944+
* (Here we assume that all subkeys could be marked required, guaranteeing
2945+
* that row comparison order matches index order. This is the common case.)
2946+
*
2947+
* Note that a row comparison header key behaves _exactly_ the same as a
2948+
* similar scalar inequality key on the row's most significant column once the
2949+
* scan reaches the point where it no longer needs to evaluate lower-order
2950+
* subkeys (or before the point where it starts needing to evaluate them).
2951+
* For example, once a forwards scan that uses our example qual reaches the
2952+
* first tuple "a > 7", we'll behave in just the same way as our caller would
2953+
* behave with a similar scalar inequality "a > 7" for the remainder of the
2954+
* scan (assuming that the scan never changes direction/never goes backwards).
2955+
* We'll even set continuescan=false according to exactly the same rules as
2956+
* the ones our caller applies with simple scalar inequalities, including the
2957+
* rules it applies when NULL tuple values don't satisfy an inequality qual.
29152958
*/
29162959
static bool
2917-
_bt_check_rowcompare(ScanKey skey, IndexTuple tuple, int tupnatts,
2960+
_bt_check_rowcompare(ScanKey header, IndexTuple tuple, int tupnatts,
29182961
TupleDesc tupdesc, ScanDirection dir,
29192962
bool forcenonrequired, bool *continuescan)
29202963
{
2921-
ScanKey subkey = (ScanKey) DatumGetPointer(skey->sk_argument);
2964+
ScanKey subkey = (ScanKey) DatumGetPointer(header->sk_argument);
29222965
int32 cmpresult = 0;
29232966
bool result;
29242967

29252968
/* First subkey should be same as the header says */
2926-
Assert(subkey->sk_attno == skey->sk_attno);
2969+
Assert(header->sk_flags & SK_ROW_HEADER);
2970+
Assert(subkey->sk_attno == header->sk_attno);
2971+
Assert(subkey->sk_strategy == header->sk_strategy);
29272972

29282973
/* Loop over columns of the row condition */
29292974
for (;;)
@@ -2943,7 +2988,7 @@ _bt_check_rowcompare(ScanKey skey, IndexTuple tuple, int tupnatts,
29432988
* columns are required for the scan direction, we can stop the
29442989
* scan, because there can't be another tuple that will succeed.
29452990
*/
2946-
Assert(subkey != (ScanKey) DatumGetPointer(skey->sk_argument));
2991+
Assert(subkey != (ScanKey) DatumGetPointer(header->sk_argument));
29472992
subkey--;
29482993
if (forcenonrequired)
29492994
{
@@ -3014,7 +3059,7 @@ _bt_check_rowcompare(ScanKey skey, IndexTuple tuple, int tupnatts,
30143059
* can only happen with an "a" NULL some time after the scan
30153060
* completely stops needing to use its "b" and "c" members.)
30163061
*/
3017-
if (subkey == (ScanKey) DatumGetPointer(skey->sk_argument))
3062+
if (subkey == (ScanKey) DatumGetPointer(header->sk_argument))
30183063
reqflags |= SK_BT_REQFWD; /* safe, first row member */
30193064

30203065
if ((subkey->sk_flags & reqflags) &&
@@ -3052,7 +3097,7 @@ _bt_check_rowcompare(ScanKey skey, IndexTuple tuple, int tupnatts,
30523097
* happen with an "a" NULL some time after the scan completely
30533098
* stops needing to use its "b" and "c" members.)
30543099
*/
3055-
if (subkey == (ScanKey) DatumGetPointer(skey->sk_argument))
3100+
if (subkey == (ScanKey) DatumGetPointer(header->sk_argument))
30563101
reqflags |= SK_BT_REQBKWD; /* safe, first row member */
30573102

30583103
if ((subkey->sk_flags & reqflags) &&

0 commit comments

Comments
 (0)