From 1d45c5da3a0ad22b8e172da352ed25bd70a2253a Mon Sep 17 00:00:00 2001 From: Peter Geoghegan Date: Wed, 14 Oct 2026 17:32:32 -0400 Subject: [PATCH] Unset SK_SEARCHARRAY when inequality arrays degenerate. Initial array-specific preprocessing in _bt_preprocess_array_keys transforms inequality strategy SK_SEARCHARRAY scan keys into simple scalar scan keys, whose sk_argument contains the array's most restrictive element. The transformed keys nevertheless retained their SK_SEARCHARRAY markings. This approach left it up to everybody else to determine whether any given SK_SEARCHARRAY-marked key was an equality strategy key (which actually has an array) or an inequality key (which no longer has one). Simplify things by teaching preprocessing to unset SK_SEARCHARRAY from the flags for inequality scan keys, at the point where it already sets the key's sk_argument to a simple scalar datum. This makes it safe for code that runs after array preprocessing to assume that any key marked SK_SEARCHARRAY must be an equality key. This also fixes a minor inconsistency: _bt_advance_array_keys's precheck of the least significant key was skipped when that key came from a SAOP inequality, even though we were willing to apply that optimization given an inequality that had a scalar argument before preprocessing. Author: Peter Geoghegan Discussion: https://postgr.es/m/CAH2-WznnuUH-fH84d60UHmDo4rO0Mio4_8DGvhxBo1yL0EwPng@mail.gmail.com Discussion: https://postgr.es/m/CAH2-Wznvj74CAwVydAs_Lh1hR3PGi_aN1s7BiRKE0iJYNdoixQ%40mail.gmail.com --- src/backend/access/nbtree/nbtpreprocesskeys.c | 28 ++++++------ src/backend/access/nbtree/nbtreadpage.c | 45 +++++++++---------- 2 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/backend/access/nbtree/nbtpreprocesskeys.c b/src/backend/access/nbtree/nbtpreprocesskeys.c index 287e1a8e3fa..56e50c9a854 100644 --- a/src/backend/access/nbtree/nbtpreprocesskeys.c +++ b/src/backend/access/nbtree/nbtpreprocesskeys.c @@ -286,9 +286,11 @@ _bt_preprocess_keys(IndexScanDesc scan) * (we'll miss out on the single value array transformation, but * that's not nearly as important when there's only one scan key) */ - Assert(so->keyData[0].sk_flags & SK_SEARCHARRAY); + Assert(so->keyData[0].sk_strategy == BTEqualStrategyNumber || + !(so->keyData[0].sk_flags & SK_SEARCHARRAY)); Assert(so->keyData[0].sk_strategy != BTEqualStrategyNumber || - (so->arrayKeys[0].scan_key == 0 && + ((so->keyData[0].sk_flags & SK_SEARCHARRAY) && + so->arrayKeys[0].scan_key == 0 && !(so->keyData[0].sk_flags & SK_BT_SKIP) && OidIsValid(so->orderProcs[0].fn_oid))); } @@ -493,10 +495,10 @@ _bt_preprocess_keys(IndexScanDesc scan) /* check strategy this key's operator corresponds to */ j = inkey->sk_strategy - 1; - if (inkey->sk_strategy == BTEqualStrategyNumber && - (inkey->sk_flags & SK_SEARCHARRAY)) + if (inkey->sk_flags & SK_SEARCHARRAY) { /* must track how input scan keys map to arrays */ + Assert(inkey->sk_strategy == BTEqualStrategyNumber); Assert(arrayKeyData); arrayidx++; } @@ -565,8 +567,7 @@ _bt_preprocess_keys(IndexScanDesc scan) /* * New key is more restrictive, and so replaces old key... */ - if (j != (BTEqualStrategyNumber - 1) || - !(xform[j].inkey->sk_flags & SK_SEARCHARRAY)) + if (!(xform[j].inkey->sk_flags & SK_SEARCHARRAY)) { xform[j].inkey = inkey; xform[j].inkeyi = i; @@ -975,10 +976,8 @@ _bt_compare_scankey_args(IndexScanDesc scan, ScanKey op, bool leftarray, rightarray; - leftarray = ((leftarg->sk_flags & SK_SEARCHARRAY) && - leftarg->sk_strategy == BTEqualStrategyNumber); - rightarray = ((rightarg->sk_flags & SK_SEARCHARRAY) && - rightarg->sk_strategy == BTEqualStrategyNumber); + leftarray = (leftarg->sk_flags & SK_SEARCHARRAY) != 0; + rightarray = (rightarg->sk_flags & SK_SEARCHARRAY) != 0; /* * _bt_preprocess_array_keys is responsible for merging together array @@ -1102,10 +1101,9 @@ _bt_compare_array_scankey_args(IndexScanDesc scan, ScanKey arraysk, ScanKey skey Assert(!(arraysk->sk_flags & (SK_ISNULL | SK_ROW_HEADER | SK_ROW_MEMBER))); Assert((arraysk->sk_flags & SK_SEARCHARRAY) && arraysk->sk_strategy == BTEqualStrategyNumber); - /* don't expect to have to deal with NULLs/row comparison scan keys */ - Assert(!(skey->sk_flags & (SK_ISNULL | SK_ROW_HEADER | SK_ROW_MEMBER))); - Assert(!(skey->sk_flags & SK_SEARCHARRAY) || - skey->sk_strategy != BTEqualStrategyNumber); + /* don't expect to have to deal with array/IS NULL/row compare skey */ + Assert(!(skey->sk_flags & + (SK_SEARCHARRAY | SK_ISNULL | SK_ROW_HEADER | SK_ROW_MEMBER))); /* * Just call the appropriate helper function based on whether it's a SAOP @@ -2078,6 +2076,7 @@ _bt_preprocess_array_keys(IndexScanDesc scan, int *new_numberOfKeys) _bt_find_extreme_element(scan, cur, elemtype, BTGreaterStrategyNumber, elem_values, num_nonnulls); + cur->sk_flags &= ~SK_SEARCHARRAY; numArrayKeyData++; /* keep this transformed scan key */ continue; case BTEqualStrategyNumber: @@ -2089,6 +2088,7 @@ _bt_preprocess_array_keys(IndexScanDesc scan, int *new_numberOfKeys) _bt_find_extreme_element(scan, cur, elemtype, BTLessStrategyNumber, elem_values, num_nonnulls); + cur->sk_flags &= ~SK_SEARCHARRAY; numArrayKeyData++; /* keep this transformed scan key */ continue; default: diff --git a/src/backend/access/nbtree/nbtreadpage.c b/src/backend/access/nbtree/nbtreadpage.c index 448a5141244..55722fd8791 100644 --- a/src/backend/access/nbtree/nbtreadpage.c +++ b/src/backend/access/nbtree/nbtreadpage.c @@ -548,6 +548,7 @@ _bt_start_array_keys(IndexScanDesc scan, ScanDirection dir) ScanKey skey = &so->keyData[array->scan_key]; Assert(skey->sk_flags & SK_SEARCHARRAY); + Assert(skey->sk_strategy == BTEqualStrategyNumber); _bt_array_set_low_or_high(rel, skey, array, ScanDirectionIsForward(dir)); @@ -1196,18 +1197,17 @@ _bt_checkkeys(IndexScanDesc scan, BTReadPageState *pstate, bool arrayKeys, #endif /* - * Only one _bt_check_compare call is required in the common case where - * there are no equality strategy array scan keys. With array keys, we - * can only accept _bt_check_compare's answer unreservedly when it set - * pstate.continuescan=true. + * Only one _bt_check_compare call is required when there are no array + * scan keys. Otherwise, we can only accept _bt_check_compare's answer + * unreservedly when it set pstate.continuescan=true. */ if (!arrayKeys || pstate->continuescan) return res; /* - * _bt_check_compare call set continuescan=false in the presence of - * equality type array keys. This could mean that the tuple is just past - * the end of matches for the current array keys. + * _bt_check_compare call set continuescan=false in the presence of array + * keys. This could mean that the tuple is just past the end of matches + * for the current array keys. * * It's also possible that the scan is still _before_ the _start_ of * tuples matching the current set of array keys. Check for that first. @@ -1501,14 +1501,12 @@ _bt_check_compare(IndexScanDesc scan, ScanDirection dir, *continuescan = false; /* - * If this is a non-required equality-type array key, the tuple - * needs to be checked against every possible array key. Handle - * this by "advancing" the scan key's array to a matching value - * (if we're successful then the tuple might match the qual). + * If this is a non-required array key, the tuple needs to be + * checked against every possible array key. Handle this by + * "advancing" the scan key's array to a matching value (if we're + * successful then the tuple might match the qual). */ - else if (advancenonrequired && - key->sk_strategy == BTEqualStrategyNumber && - (key->sk_flags & SK_SEARCHARRAY)) + else if (advancenonrequired && (key->sk_flags & SK_SEARCHARRAY)) return _bt_advance_array_keys(scan, NULL, tuple, tupnatts, tupdesc, *ikey, false); @@ -2242,16 +2240,15 @@ _bt_advance_array_keys(IndexScanDesc scan, BTReadPageState *pstate, int32 result; int set_elem = 0; - if (cur->sk_strategy == BTEqualStrategyNumber) + if (cur->sk_flags & SK_SEARCHARRAY) { /* Manage array state */ - if (cur->sk_flags & SK_SEARCHARRAY) - { - array = &so->arrayKeys[arrayidx++]; - Assert(array->scan_key == ikey); - } + array = &so->arrayKeys[arrayidx++]; + + Assert(array->scan_key == ikey); + Assert(cur->sk_strategy == BTEqualStrategyNumber); } - else + else if (cur->sk_strategy != BTEqualStrategyNumber) { /* * Are any inequalities required in the opposite direction only @@ -3683,10 +3680,12 @@ _bt_verify_keys_with_arraykeys(IndexScanDesc scan) ScanKey cur = so->keyData + ikey; BTArrayKeyInfo *array; - if (cur->sk_strategy != BTEqualStrategyNumber || - !(cur->sk_flags & SK_SEARCHARRAY)) + if (!(cur->sk_flags & SK_SEARCHARRAY)) continue; + if (cur->sk_strategy != BTEqualStrategyNumber) + return false; + array = &so->arrayKeys[arrayidx++]; if (array->scan_key != ikey) return false;