Skip to content

Commit b8f1c62

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 <[email protected]> Reviewed-By: Victor Yegorov <[email protected]> Reviewed-By: Chao Li <[email protected]> Discussion: https://postgr.es/m/CAH2-Wznwkak_K7pcAdv9uH8ZfNo8QO7+tHXOaCUddMeTfaCCFw@mail.gmail.com Backpatch-through: 18
1 parent 4f08586 commit b8f1c62

File tree

2 files changed

+60
-11
lines changed

2 files changed

+60
-11
lines changed

src/backend/access/nbtree/nbtsearch.c

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

12931295
/*
@@ -1344,9 +1346,9 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
13441346
Assert(subkey->sk_strategy == bkey->sk_strategy);
13451347
Assert(keysz < INDEX_MAX_KEYS);
13461348

1347-
memcpy(inskey.scankeys + keysz, subkey,
1348-
sizeof(ScanKeyData));
1349+
memcpy(inskey.scankeys + keysz, subkey, sizeof(ScanKeyData));
13491350
keysz++;
1351+
13501352
if (subkey->sk_flags & SK_ROW_END)
13511353
break;
13521354
}
@@ -1378,7 +1380,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
13781380
}
13791381
}
13801382

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

src/backend/access/nbtree/nbtutils.c

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ static bool _bt_check_compare(IndexScanDesc scan, ScanDirection dir,
6363
bool advancenonrequired, bool forcenonrequired,
6464
bool *continuescan, int *ikey);
6565
static bool _bt_rowcompare_cmpresult(ScanKey subkey, int cmpresult);
66-
static bool _bt_check_rowcompare(ScanKey skey,
66+
static bool _bt_check_rowcompare(ScanKey header,
6767
IndexTuple tuple, int tupnatts, TupleDesc tupdesc,
6868
ScanDirection dir, bool forcenonrequired, bool *continuescan);
6969
static void _bt_checkkeys_look_ahead(IndexScanDesc scan, BTReadPageState *pstate,
@@ -3008,6 +3008,8 @@ _bt_rowcompare_cmpresult(ScanKey subkey, int cmpresult)
30083008
{
30093009
bool satisfied;
30103010

3011+
Assert(subkey->sk_flags & SK_ROW_MEMBER);
3012+
30113013
switch (subkey->sk_strategy)
30123014
{
30133015
case BTLessStrategyNumber:
@@ -3039,19 +3041,64 @@ _bt_rowcompare_cmpresult(ScanKey subkey, int cmpresult)
30393041
* it's not possible for any future tuples in the current scan direction
30403042
* to pass the qual.
30413043
*
3042-
* This is a subroutine for _bt_checkkeys/_bt_check_compare.
3044+
* This is a subroutine for _bt_checkkeys/_bt_check_compare. Caller passes us
3045+
* a row compare header key taken from so->keyData[].
3046+
*
3047+
* Row value comparisons can be described in terms of logical expansions that
3048+
* use only scalar operators. Consider the following example row comparison:
3049+
*
3050+
* "(a, b, c) > (7, 'bar', 62)"
3051+
*
3052+
* This can be evaluated as:
3053+
*
3054+
* "(a = 7 AND b = 'bar' AND c > 62) OR (a = 7 AND b > 'bar') OR (a > 7)".
3055+
*
3056+
* Notice that this condition is satisfied by _all_ rows that satisfy "a > 7",
3057+
* and by a subset of all rows that satisfy "a >= 7" (possibly all such rows).
3058+
* It _can't_ be satisfied by other rows (where "a < 7" or where "a IS NULL").
3059+
* A row comparison header key can therefore often be treated as if it was a
3060+
* simple scalar inequality on the row compare's most significant column.
3061+
* (For example, _bt_advance_array_keys and most preprocessing routines treat
3062+
* row compares like any other same-strategy inequality on the same column.)
3063+
*
3064+
* Things get more complicated for our row compare given a row where "a = 7".
3065+
* Note that a row compare isn't necessarily satisfied by _every_ tuple that
3066+
* appears between the first and last satisfied tuple returned by the scan,
3067+
* due to the way that its lower-order subkeys are only conditionally applied.
3068+
* A forwards scan that uses our example qual might initially return a tuple
3069+
* "(a, b, c) = (7, 'zebra', 54)". But it won't subsequently return a tuple
3070+
* "(a, b, c) = (7, NULL, 1)" located to the right of the first matching tuple
3071+
* (assume that "b" was declared NULLS LAST here). The scan will only return
3072+
* additional matches upon reaching tuples where "a > 7". If you rereview our
3073+
* example row comparison's logical expansion, you'll understand why this is.
3074+
* (Here we assume that all subkeys could be marked required, guaranteeing
3075+
* that row comparison order matches index order. This is the common case.)
3076+
*
3077+
* Note that a row comparison header key behaves _exactly_ the same as a
3078+
* similar scalar inequality key on the row's most significant column once the
3079+
* scan reaches the point where it no longer needs to evaluate lower-order
3080+
* subkeys (or before the point where it starts needing to evaluate them).
3081+
* For example, once a forwards scan that uses our example qual reaches the
3082+
* first tuple "a > 7", we'll behave in just the same way as our caller would
3083+
* behave with a similar scalar inequality "a > 7" for the remainder of the
3084+
* scan (assuming that the scan never changes direction/never goes backwards).
3085+
* We'll even set continuescan=false according to exactly the same rules as
3086+
* the ones our caller applies with simple scalar inequalities, including the
3087+
* rules it applies when NULL tuple values don't satisfy an inequality qual.
30433088
*/
30443089
static bool
3045-
_bt_check_rowcompare(ScanKey skey, IndexTuple tuple, int tupnatts,
3090+
_bt_check_rowcompare(ScanKey header, IndexTuple tuple, int tupnatts,
30463091
TupleDesc tupdesc, ScanDirection dir,
30473092
bool forcenonrequired, bool *continuescan)
30483093
{
3049-
ScanKey subkey = (ScanKey) DatumGetPointer(skey->sk_argument);
3094+
ScanKey subkey = (ScanKey) DatumGetPointer(header->sk_argument);
30503095
int32 cmpresult = 0;
30513096
bool result;
30523097

30533098
/* First subkey should be same as the header says */
3054-
Assert(subkey->sk_attno == skey->sk_attno);
3099+
Assert(header->sk_flags & SK_ROW_HEADER);
3100+
Assert(subkey->sk_attno == header->sk_attno);
3101+
Assert(subkey->sk_strategy == header->sk_strategy);
30553102

30563103
/* Loop over columns of the row condition */
30573104
for (;;)
@@ -3071,7 +3118,7 @@ _bt_check_rowcompare(ScanKey skey, IndexTuple tuple, int tupnatts,
30713118
* columns are required for the scan direction, we can stop the
30723119
* scan, because there can't be another tuple that will succeed.
30733120
*/
3074-
Assert(subkey != (ScanKey) DatumGetPointer(skey->sk_argument));
3121+
Assert(subkey != (ScanKey) DatumGetPointer(header->sk_argument));
30753122
subkey--;
30763123
if (forcenonrequired)
30773124
{
@@ -3142,7 +3189,7 @@ _bt_check_rowcompare(ScanKey skey, IndexTuple tuple, int tupnatts,
31423189
* can only happen with an "a" NULL some time after the scan
31433190
* completely stops needing to use its "b" and "c" members.)
31443191
*/
3145-
if (subkey == (ScanKey) DatumGetPointer(skey->sk_argument))
3192+
if (subkey == (ScanKey) DatumGetPointer(header->sk_argument))
31463193
reqflags |= SK_BT_REQFWD; /* safe, first row member */
31473194

31483195
if ((subkey->sk_flags & reqflags) &&
@@ -3180,7 +3227,7 @@ _bt_check_rowcompare(ScanKey skey, IndexTuple tuple, int tupnatts,
31803227
* happen with an "a" NULL some time after the scan completely
31813228
* stops needing to use its "b" and "c" members.)
31823229
*/
3183-
if (subkey == (ScanKey) DatumGetPointer(skey->sk_argument))
3230+
if (subkey == (ScanKey) DatumGetPointer(header->sk_argument))
31843231
reqflags |= SK_BT_REQBKWD; /* safe, first row member */
31853232

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

0 commit comments

Comments
 (0)