On Sat, 16 Mar 2024 at 01:12, Peter Geoghegan <p...@bowt.ie> wrote:
>
> On Fri, Mar 8, 2024 at 9:00 AM Matthias van de Meent
> <boekewurm+postg...@gmail.com> wrote:
> > I've attached v14, where 0001 is v13, 0002 is a patch with small
> > changes + some large comment suggestions, and 0003 which contains
> > sorted merge join code for _bt_merge_arrays.
>
> This is part of my next revision, v15, which I've attached (along with
> a test case that you might find useful, explained below).
>
> v15 makes the difference between the non-required scan key trigger and
> required scan key trigger cases clearer within _bt_advance_array_keys.

OK, here's a small additional review, with a suggestion for additional
changes to _bt_preprocess:

> @@ -1117,6 +3160,46 @@ _bt_compare_scankey_args(IndexScanDesc scan, ScanKey 
> op,
>      /*
>      * The opfamily we need to worry about is identified by the index column.
>      */
>     Assert(leftarg->sk_attno == rightarg->sk_attno);
>
> +    /*
> +     * If either leftarg or rightarg are equality-type array scankeys, we 
> need
> +     * specialized handling (since by now we know that IS NULL wasn't used)
> +     */
> [...]
> +    }
> +
>     opcintype = rel->rd_opcintype[leftarg->sk_attno - 1];

Here, you insert your code between the comment about which opfamily to
choose and the code assigning the opfamily. I think this needs some
cleanup.

> +             * Don't call _bt_preprocess_array_keys_final in this fast path
> +             * (we'll miss out on the single value array transformation, but
> +             * that's not nearly as important when there's only one scan key)

Why is it OK to ignore it? Or, why don't we apply it here?

---

Attached 2 patches for further optimization of the _bt_preprocess_keys
path (on top of your v15), according to the following idea:

Right now, we do "expensive" processing with xform merging for all
keys when we have more than 1 keys in the scan. However, we only do
per-attribute merging of these keys, so if there is only one key for
any attribute, the many cycles spent in that loop are mostly wasted.
By checking for single-scankey attributes, we can short-track many
multi-column index scans because they usually have only a single scan
key per attribute.
The first implements that idea, the second reduces the scope of
various variables so as to improve compiler optimizability.

I'll try to work a bit more on merging the _bt_preprocess steps into a
single main iterator, but this is about as far as I got with clean
patches. Merging the steps for array preprocessing with per-key
processing and post-processing is proving a bit more complex than I'd
anticipated, so I don't think I'll be able to finish that before the
feature freeze, especially with other things that keep distracting me.

Matthias van de Meent
From 16808e85f5fae7b16fd52d8a2be8437e4cff8640 Mon Sep 17 00:00:00 2001
From: Matthias van de Meent <boekewurm+postg...@gmail.com>
Date: Tue, 19 Mar 2024 16:39:29 +0100
Subject: [PATCH v1 1/2] nbtree: Optimize preprocessing for single ScanKey per
 column

Before, there was only a single fast path: single-ScanKey index scans.
With this optimization, we can fast-track processing of attributes with
only a single scankey, significantly reducing overhead for common scans
like "a = 10 and b < 8".
---
 src/backend/access/nbtree/nbtutils.c | 529 ++++++++++++++-------------
 1 file changed, 278 insertions(+), 251 deletions(-)

diff --git a/src/backend/access/nbtree/nbtutils.c 
b/src/backend/access/nbtree/nbtutils.c
index b401b31191..8cd6270408 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -2511,7 +2511,6 @@ _bt_preprocess_keys(IndexScanDesc scan)
        ScanKey         inkeys;
        ScanKey         outkeys;
        ScanKey         cur;
-       BTScanKeyPreproc xform[BTMaxStrategyNumber];
        bool            test_result;
        int                     i,
                                j;
@@ -2619,327 +2618,355 @@ _bt_preprocess_keys(IndexScanDesc scan)
         * xform[i] points to the currently best scan key of strategy type i+1; 
it
         * is NULL if we haven't yet found such a key for this attr.
         */
-       attno = 1;
-       memset(xform, 0, sizeof(xform));
 
-       /*
-        * Loop iterates from 0 to numberOfKeys inclusive; we use the last pass 
to
-        * handle after-last-key processing.  Actual exit from the loop is at 
the
-        * "break" statement below.
-        */
-       for (i = 0;; cur++, i++)
+       for (i = 0; i < numberOfKeys;)
        {
-               if (i < numberOfKeys)
+               BTScanKeyPreproc xform[BTMaxStrategyNumber];
+               int                     priorNumberOfEqualCols = 
numberOfEqualCols;
+               bool            fast = true;
+
+               /* initialize for this attno */
+               attno = cur->sk_attno;
+
+               /* We'll try to merge keys if there are multiple for this 
attribute */
+               if (i + 1 < numberOfKeys && (cur + 1)->sk_attno == attno)
+                       fast = false;
+
+               /* ... and have special care-taking for arrays and rows */
+               if (cur->sk_flags & (SK_SEARCHARRAY | SK_ROW_HEADER))
+                       fast = false;
+
+               /* no special array/row/multi-key processing */
+               if (fast)
                {
-                       /* Apply indoption to scankey (might change 
sk_strategy!) */
+                       ScanKey outkey;
                        if (!_bt_fix_scankey_strategy(cur, indoption))
                        {
-                               /* NULL can't be matched, so give up */
                                so->qual_ok = false;
                                return;
                        }
+
+                       /* mark key required if needed */
+                       if (numberOfEqualCols == attno - 1)
+                               _bt_mark_scankey_required(cur);
+                       /* ... and update the number of equal keys if needed */
+                       if (cur->sk_strategy == BTEqualStrategyNumber)
+                               numberOfEqualCols++;
+                       if (arrayKeyData)
+                               keyDataMap[new_numberOfKeys] = i;
+
+                       /* Copy the key into the output key data */
+                       outkey = &outkeys[new_numberOfKeys++];
+                       memcpy(outkey, cur, sizeof(ScanKeyData));
+
+                       cur++;
+                       i++;
+                       continue;
                }
 
+               memset(xform, 0, sizeof(xform));
+
                /*
-                * If we are at the end of the keys for a particular attr, 
finish up
-                * processing and emit the cleaned-up keys.
+                * Iterate over all ScanKeys for the current attno, collecting 
the
+                * most restrictive keys into xform.
                 */
-               if (i == numberOfKeys || cur->sk_attno != attno)
+               for (;i < numberOfKeys && cur->sk_attno == attno; cur++, i++)
                {
-                       int                     priorNumberOfEqualCols = 
numberOfEqualCols;
+                       /* Apply indoption to scankey (might change 
sk_strategy!) */
+                       if (!_bt_fix_scankey_strategy(cur, indoption))
+                       {
+                               /* NULL can't be matched, so give up */
+                               so->qual_ok = false;
+                               return;
+                       }
 
-                       /* check input keys are correctly ordered */
-                       if (i < numberOfKeys && cur->sk_attno < attno)
-                               elog(ERROR, "btree index keys must be ordered 
by attribute");
+                       /* check strategy this key's operator corresponds to */
+                       j = cur->sk_strategy - 1;
 
-                       /*
-                        * If = has been specified, all other keys can be 
eliminated as
-                        * redundant.  If we have a case like key = 1 AND key > 
2, we can
-                        * set qual_ok to false and abandon further processing.
-                        *
-                        * We also have to deal with the case of "key IS NULL", 
which is
-                        * unsatisfiable in combination with any other index 
condition. By
-                        * the time we get here, that's been classified as an 
equality
-                        * check, and we've rejected any combination of it with 
a regular
-                        * equality condition; but not with other types of 
conditions.
-                        */
-                       if (xform[BTEqualStrategyNumber - 1].skey)
+                       /* if row comparison, push it directly to the output 
array */
+                       if (cur->sk_flags & SK_ROW_HEADER)
                        {
-                               ScanKey         eq = 
xform[BTEqualStrategyNumber - 1].skey;
-                               BTArrayKeyInfo *array = NULL;
-                               FmgrInfo   *orderproc = NULL;
+                               ScanKey         outkey = 
&outkeys[new_numberOfKeys++];
 
-                               if (arrayKeyData && (eq->sk_flags & 
SK_SEARCHARRAY))
-                               {
-                                       int                     eq_in_ikey,
-                                                               eq_arrayidx;
+                               memcpy(outkey, cur, sizeof(ScanKeyData));
+                               if (arrayKeyData)
+                                       keyDataMap[new_numberOfKeys - 1] = i;
+                               if (numberOfEqualCols == attno - 1)
+                                       _bt_mark_scankey_required(outkey);
 
-                                       eq_in_ikey = 
xform[BTEqualStrategyNumber - 1].ikey;
-                                       eq_arrayidx = 
xform[BTEqualStrategyNumber - 1].arrayidx;
-                                       array = &so->arrayKeys[eq_arrayidx - 1];
-                                       orderproc = so->orderProcs + eq_in_ikey;
+                               /*
+                                * We don't support RowCompare using equality; 
such a qual would
+                                * mess up the numberOfEqualCols tracking.
+                                */
+                               Assert(j != (BTEqualStrategyNumber - 1));
+                               continue;
+                       }
 
-                                       Assert(array->scan_key == eq_in_ikey);
-                                       Assert(OidIsValid(orderproc->fn_oid));
-                               }
+                       /*
+                        * Does this input scan key require further processing 
as an array?
+                        */
+                       if (cur->sk_strategy == InvalidStrategy)
+                       {
+                               /* _bt_preprocess_array_keys marked this array 
key redundant */
+                               Assert(arrayKeyData);
+                               Assert(cur->sk_flags & SK_SEARCHARRAY);
+                               continue;
+                       }
 
-                               for (j = BTMaxStrategyNumber; --j >= 0;)
-                               {
-                                       ScanKey         chk = xform[j].skey;
+                       if (cur->sk_strategy == BTEqualStrategyNumber &&
+                               (cur->sk_flags & SK_SEARCHARRAY))
+                       {
+                               /* _bt_preprocess_array_keys kept this array 
key */
+                               Assert(arrayKeyData);
+                               arrayidx++;
+                       }
 
-                                       if (!chk || j == (BTEqualStrategyNumber 
- 1))
-                                               continue;
+                       /*
+                        * have we seen a scan key for this same attribute and 
using this same
+                        * operator strategy before now?
+                        */
+                       if (xform[j].skey == NULL)
+                       {
+                               /* nope, so this scan key wins by default (at 
least for now) */
+                               xform[j].skey = cur;
+                               xform[j].ikey = i;
+                               xform[j].arrayidx = arrayidx;
+                       }
+                       else
+                       {
+                               FmgrInfo   *orderproc = NULL;
+                               BTArrayKeyInfo *array = NULL;
 
-                                       if (eq->sk_flags & SK_SEARCHNULL)
+                               /*
+                                * Seen one of these before, so keep only the 
more restrictive key
+                                * if possible
+                                */
+                               if (j == (BTEqualStrategyNumber - 1) && 
arrayKeyData)
+                               {
+                                       /*
+                                        * Have to set up array keys
+                                        */
+                                       if ((cur->sk_flags & SK_SEARCHARRAY))
                                        {
-                                               /* IS NULL is contradictory to 
anything else */
-                                               so->qual_ok = false;
-                                               return;
-                                       }
+                                               array = &so->arrayKeys[arrayidx 
- 1];
+                                               orderproc = so->orderProcs + i;
 
-                                       if (_bt_compare_scankey_args(scan, chk, 
eq, chk,
-                                                                               
                 array, orderproc,
-                                                                               
                 &test_result))
-                                       {
-                                               if (!test_result)
-                                               {
-                                                       /* keys proven mutually 
contradictory */
-                                                       so->qual_ok = false;
-                                                       return;
-                                               }
-                                               /* else discard the redundant 
non-equality key */
-                                               Assert(!array || 
array->num_elems > 0);
-                                               xform[j].skey = NULL;
-                                               xform[j].ikey = -1;
+                                               Assert(array->scan_key == i);
+                                               
Assert(OidIsValid(orderproc->fn_oid));
                                        }
-                                       /* else, cannot determine redundancy, 
keep both keys */
-                               }
-                               /* track number of attrs for which we have "=" 
keys */
-                               numberOfEqualCols++;
-                       }
+                                       else if ((xform[j].skey->sk_flags & 
SK_SEARCHARRAY))
+                                       {
+                                               array = 
&so->arrayKeys[xform[j].arrayidx - 1];
+                                               orderproc = so->orderProcs + 
xform[j].ikey;
 
-                       /* try to keep only one of <, <= */
-                       if (xform[BTLessStrategyNumber - 1].skey
-                               && xform[BTLessEqualStrategyNumber - 1].skey)
-                       {
-                               ScanKey         lt = xform[BTLessStrategyNumber 
- 1].skey;
-                               ScanKey         le = 
xform[BTLessEqualStrategyNumber - 1].skey;
+                                               Assert(array->scan_key == 
xform[j].ikey);
+                                               
Assert(OidIsValid(orderproc->fn_oid));
+                                       }
 
-                               if (_bt_compare_scankey_args(scan, le, lt, le, 
NULL, NULL,
-                                                                               
         &test_result))
-                               {
-                                       if (test_result)
-                                               xform[BTLessEqualStrategyNumber 
- 1].skey = NULL;
-                                       else
-                                               xform[BTLessStrategyNumber - 
1].skey = NULL;
+                                       /*
+                                        * Both scan keys might have arrays, in 
which case we'll
+                                        * arbitrarily pass only one of the 
arrays.  That won't
+                                        * matter, since 
_bt_compare_scankey_args is aware that two
+                                        * SEARCHARRAY scan keys mean that 
_bt_preprocess_array_keys
+                                        * failed to eliminate redundant arrays 
through array merging.
+                                        * _bt_compare_scankey_args just 
returns false when it sees
+                                        * this; it won't even try to examine 
either array.
+                                        */
                                }
-                       }
 
-                       /* try to keep only one of >, >= */
-                       if (xform[BTGreaterStrategyNumber - 1].skey
-                               && xform[BTGreaterEqualStrategyNumber - 1].skey)
-                       {
-                               ScanKey         gt = 
xform[BTGreaterStrategyNumber - 1].skey;
-                               ScanKey         ge = 
xform[BTGreaterEqualStrategyNumber - 1].skey;
-
-                               if (_bt_compare_scankey_args(scan, ge, gt, ge, 
NULL, NULL,
-                                                                               
         &test_result))
+                               if (_bt_compare_scankey_args(scan, cur, cur, 
xform[j].skey,
+                                                                               
         array, orderproc, &test_result))
                                {
+                                       /* Have all we need to determine 
redundancy */
                                        if (test_result)
-                                               
xform[BTGreaterEqualStrategyNumber - 1].skey = NULL;
-                                       else
-                                               xform[BTGreaterStrategyNumber - 
1].skey = NULL;
-                               }
-                       }
+                                       {
+                                               Assert(!array || 
array->num_elems > 0);
 
-                       /*
-                        * Emit the cleaned-up keys into the outkeys[] array, 
and then
-                        * mark them if they are required.  They are required 
(possibly
-                        * only in one direction) if all attrs before this one 
had "=".
-                        */
-                       for (j = BTMaxStrategyNumber; --j >= 0;)
-                       {
-                               if (xform[j].skey)
+                                               /*
+                                                * New key is more restrictive, 
and so replaces old key...
+                                                */
+                                               if (j != (BTEqualStrategyNumber 
- 1) ||
+                                                       
!(xform[j].skey->sk_flags & SK_SEARCHARRAY))
+                                               {
+                                                       Assert(!array || 
array->scan_key == i);
+                                                       xform[j].skey = cur;
+                                                       xform[j].ikey = i;
+                                                       xform[j].arrayidx = 
arrayidx;
+                                               }
+                                               else
+                                               {
+                                                       /*
+                                                        * ...unless we have to 
keep the old key because it's
+                                                        * an array that 
rendered the new key redundant.  We
+                                                        * need to make sure 
that we don't throw away an array
+                                                        * scan key.  
_bt_compare_scankey_args expects us to
+                                                        * always keep arrays 
(and discard non-arrays).
+                                                        */
+                                                       Assert(j == 
(BTEqualStrategyNumber - 1));
+                                                       
Assert(xform[j].skey->sk_flags & SK_SEARCHARRAY);
+                                                       Assert(xform[j].ikey == 
array->scan_key);
+                                                       Assert(!(cur->sk_flags 
& SK_SEARCHARRAY));
+                                               }
+                                       }
+                                       else if (j == (BTEqualStrategyNumber - 
1))
+                                       {
+                                               /* key == a && key == b, but a 
!= b */
+                                               so->qual_ok = false;
+                                               return;
+                                       }
+                                       /* else old key is more restrictive, 
keep it */
+                               }
+                               else
                                {
+                                       /*
+                                        * We can't determine which key is more 
restrictive.  Push
+                                        * xform[j] directly to the output 
array, then set xform[j] to
+                                        * the new scan key.
+                                        *
+                                        * Note: We do things this way around 
so that our arrays are
+                                        * always in the same order as their 
corresponding scan keys,
+                                        * even with incomplete opfamilies.  
_bt_advance_array_keys
+                                        * depends on this.
+                                        */
                                        ScanKey         outkey = 
&outkeys[new_numberOfKeys++];
 
                                        memcpy(outkey, xform[j].skey, 
sizeof(ScanKeyData));
                                        if (arrayKeyData)
                                                keyDataMap[new_numberOfKeys - 
1] = xform[j].ikey;
-                                       if (priorNumberOfEqualCols == attno - 1)
+                                       if (numberOfEqualCols == attno - 1)
                                                
_bt_mark_scankey_required(outkey);
+                                       xform[j].skey = cur;
+                                       xform[j].ikey = i;
+                                       xform[j].arrayidx = arrayidx;
                                }
                        }
-
-                       /*
-                        * Exit loop here if done.
-                        */
-                       if (i == numberOfKeys)
-                               break;
-
-                       /* Re-initialize for new attno */
-                       attno = cur->sk_attno;
-                       memset(xform, 0, sizeof(xform));
-               }
-
-               /* check strategy this key's operator corresponds to */
-               j = cur->sk_strategy - 1;
-
-               /* if row comparison, push it directly to the output array */
-               if (cur->sk_flags & SK_ROW_HEADER)
-               {
-                       ScanKey         outkey = &outkeys[new_numberOfKeys++];
-
-                       memcpy(outkey, cur, sizeof(ScanKeyData));
-                       if (arrayKeyData)
-                               keyDataMap[new_numberOfKeys - 1] = i;
-                       if (numberOfEqualCols == attno - 1)
-                               _bt_mark_scankey_required(outkey);
-
-                       /*
-                        * We don't support RowCompare using equality; such a 
qual would
-                        * mess up the numberOfEqualCols tracking.
-                        */
-                       Assert(j != (BTEqualStrategyNumber - 1));
-                       continue;
                }
 
                /*
-                * Does this input scan key require further processing as an 
array?
+                * Now that we are at the end of the keys for a particular attr,
+                * finish up processing and emit the cleaned-up keys.
                 */
-               if (cur->sk_strategy == InvalidStrategy)
-               {
-                       /* _bt_preprocess_array_keys marked this array key 
redundant */
-                       Assert(arrayKeyData);
-                       Assert(cur->sk_flags & SK_SEARCHARRAY);
-                       continue;
-               }
 
-               if (cur->sk_strategy == BTEqualStrategyNumber &&
-                       (cur->sk_flags & SK_SEARCHARRAY))
-               {
-                       /* _bt_preprocess_array_keys kept this array key */
-                       Assert(arrayKeyData);
-                       arrayidx++;
-               }
+               /* check input keys are correctly ordered */
+               if (i < numberOfKeys && cur->sk_attno < attno)
+                       elog(ERROR, "btree index keys must be ordered by 
attribute");
 
                /*
-                * have we seen a scan key for this same attribute and using 
this same
-                * operator strategy before now?
+                * If = has been specified, all other keys can be eliminated as
+                * redundant.  If we have a case like key = 1 AND key > 2, we 
can
+                * set qual_ok to false and abandon further processing.
+                *
+                * We also have to deal with the case of "key IS NULL", which is
+                * unsatisfiable in combination with any other index condition. 
By
+                * the time we get here, that's been classified as an equality
+                * check, and we've rejected any combination of it with a 
regular
+                * equality condition; but not with other types of conditions.
                 */
-               if (xform[j].skey == NULL)
+               if (xform[BTEqualStrategyNumber - 1].skey)
                {
-                       /* nope, so this scan key wins by default (at least for 
now) */
-                       xform[j].skey = cur;
-                       xform[j].ikey = i;
-                       xform[j].arrayidx = arrayidx;
-               }
-               else
-               {
-                       FmgrInfo   *orderproc = NULL;
+                       ScanKey         eq = xform[BTEqualStrategyNumber - 
1].skey;
                        BTArrayKeyInfo *array = NULL;
+                       FmgrInfo   *orderproc = NULL;
 
-                       /*
-                        * Seen one of these before, so keep only the more 
restrictive key
-                        * if possible
-                        */
-                       if (j == (BTEqualStrategyNumber - 1) && arrayKeyData)
+                       if (arrayKeyData && (eq->sk_flags & SK_SEARCHARRAY))
                        {
-                               /*
-                                * Have to set up array keys
-                                */
-                               if ((cur->sk_flags & SK_SEARCHARRAY))
-                               {
-                                       array = &so->arrayKeys[arrayidx - 1];
-                                       orderproc = so->orderProcs + i;
-
-                                       Assert(array->scan_key == i);
-                                       Assert(OidIsValid(orderproc->fn_oid));
-                               }
-                               else if ((xform[j].skey->sk_flags & 
SK_SEARCHARRAY))
-                               {
-                                       array = 
&so->arrayKeys[xform[j].arrayidx - 1];
-                                       orderproc = so->orderProcs + 
xform[j].ikey;
+                               int                     eq_in_ikey,
+                                                       eq_arrayidx;
 
-                                       Assert(array->scan_key == 
xform[j].ikey);
-                                       Assert(OidIsValid(orderproc->fn_oid));
-                               }
+                               eq_in_ikey = xform[BTEqualStrategyNumber - 
1].ikey;
+                               eq_arrayidx = xform[BTEqualStrategyNumber - 
1].arrayidx;
+                               array = &so->arrayKeys[eq_arrayidx - 1];
+                               orderproc = so->orderProcs + eq_in_ikey;
 
-                               /*
-                                * Both scan keys might have arrays, in which 
case we'll
-                                * arbitrarily pass only one of the arrays.  
That won't
-                                * matter, since _bt_compare_scankey_args is 
aware that two
-                                * SEARCHARRAY scan keys mean that 
_bt_preprocess_array_keys
-                                * failed to eliminate redundant arrays through 
array merging.
-                                * _bt_compare_scankey_args just returns false 
when it sees
-                                * this; it won't even try to examine either 
array.
-                                */
+                               Assert(array->scan_key == eq_in_ikey);
+                               Assert(OidIsValid(orderproc->fn_oid));
                        }
 
-                       if (_bt_compare_scankey_args(scan, cur, cur, 
xform[j].skey,
-                                                                               
 array, orderproc, &test_result))
+                       for (j = BTMaxStrategyNumber; --j >= 0;)
                        {
-                               /* Have all we need to determine redundancy */
-                               if (test_result)
-                               {
-                                       Assert(!array || array->num_elems > 0);
+                               ScanKey         chk = xform[j].skey;
 
-                                       /*
-                                        * New key is more restrictive, and so 
replaces old key...
-                                        */
-                                       if (j != (BTEqualStrategyNumber - 1) ||
-                                               !(xform[j].skey->sk_flags & 
SK_SEARCHARRAY))
-                                       {
-                                               Assert(!array || 
array->scan_key == i);
-                                               xform[j].skey = cur;
-                                               xform[j].ikey = i;
-                                               xform[j].arrayidx = arrayidx;
-                                       }
-                                       else
-                                       {
-                                               /*
-                                                * ...unless we have to keep 
the old key because it's
-                                                * an array that rendered the 
new key redundant.  We
-                                                * need to make sure that we 
don't throw away an array
-                                                * scan key.  
_bt_compare_scankey_args expects us to
-                                                * always keep arrays (and 
discard non-arrays).
-                                                */
-                                               Assert(j == 
(BTEqualStrategyNumber - 1));
-                                               Assert(xform[j].skey->sk_flags 
& SK_SEARCHARRAY);
-                                               Assert(xform[j].ikey == 
array->scan_key);
-                                               Assert(!(cur->sk_flags & 
SK_SEARCHARRAY));
-                                       }
-                               }
-                               else if (j == (BTEqualStrategyNumber - 1))
+                               if (!chk || j == (BTEqualStrategyNumber - 1))
+                                       continue;
+
+                               if (eq->sk_flags & SK_SEARCHNULL)
                                {
-                                       /* key == a && key == b, but a != b */
+                                       /* IS NULL is contradictory to anything 
else */
                                        so->qual_ok = false;
                                        return;
                                }
-                               /* else old key is more restrictive, keep it */
+
+                               if (_bt_compare_scankey_args(scan, chk, eq, chk,
+                                                                               
         array, orderproc,
+                                                                               
         &test_result))
+                               {
+                                       if (!test_result)
+                                       {
+                                               /* keys proven mutually 
contradictory */
+                                               so->qual_ok = false;
+                                               return;
+                                       }
+                                       /* else discard the redundant 
non-equality key */
+                                       Assert(!array || array->num_elems > 0);
+                                       xform[j].skey = NULL;
+                                       xform[j].ikey = -1;
+                               }
+                               /* else, cannot determine redundancy, keep both 
keys */
                        }
-                       else
+                       /* track number of attrs for which we have "=" keys */
+                       numberOfEqualCols++;
+               }
+
+               /* try to keep only one of <, <= */
+               if (xform[BTLessStrategyNumber - 1].skey
+                       && xform[BTLessEqualStrategyNumber - 1].skey)
+               {
+                       ScanKey         lt = xform[BTLessStrategyNumber - 
1].skey;
+                       ScanKey         le = xform[BTLessEqualStrategyNumber - 
1].skey;
+
+                       if (_bt_compare_scankey_args(scan, le, lt, le, NULL, 
NULL,
+                                                                               
 &test_result))
+                       {
+                               if (test_result)
+                                       xform[BTLessEqualStrategyNumber - 
1].skey = NULL;
+                               else
+                                       xform[BTLessStrategyNumber - 1].skey = 
NULL;
+                       }
+               }
+
+               /* try to keep only one of >, >= */
+               if (xform[BTGreaterStrategyNumber - 1].skey
+                       && xform[BTGreaterEqualStrategyNumber - 1].skey)
+               {
+                       ScanKey         gt = xform[BTGreaterStrategyNumber - 
1].skey;
+                       ScanKey         ge = xform[BTGreaterEqualStrategyNumber 
- 1].skey;
+
+                       if (_bt_compare_scankey_args(scan, ge, gt, ge, NULL, 
NULL,
+                                                                               
 &test_result))
+                       {
+                               if (test_result)
+                                       xform[BTGreaterEqualStrategyNumber - 
1].skey = NULL;
+                               else
+                                       xform[BTGreaterStrategyNumber - 1].skey 
= NULL;
+                       }
+               }
+
+               /*
+                * Emit the cleaned-up keys into the outkeys[] array, and then
+                * mark them if they are required.  They are required (possibly
+                * only in one direction) if all attrs before this one had "=".
+                */
+               for (j = BTMaxStrategyNumber; --j >= 0;)
+               {
+                       if (xform[j].skey)
                        {
-                               /*
-                                * We can't determine which key is more 
restrictive.  Push
-                                * xform[j] directly to the output array, then 
set xform[j] to
-                                * the new scan key.
-                                *
-                                * Note: We do things this way around so that 
our arrays are
-                                * always in the same order as their 
corresponding scan keys,
-                                * even with incomplete opfamilies.  
_bt_advance_array_keys
-                                * depends on this.
-                                */
                                ScanKey         outkey = 
&outkeys[new_numberOfKeys++];
 
                                memcpy(outkey, xform[j].skey, 
sizeof(ScanKeyData));
                                if (arrayKeyData)
                                        keyDataMap[new_numberOfKeys - 1] = 
xform[j].ikey;
-                               if (numberOfEqualCols == attno - 1)
+                               if (priorNumberOfEqualCols == attno - 1)
                                        _bt_mark_scankey_required(outkey);
-                               xform[j].skey = cur;
-                               xform[j].ikey = i;
-                               xform[j].arrayidx = arrayidx;
                        }
                }
        }
-- 
2.40.1

From 1208e00a45076acc028c5435be7ad56e5e031051 Mon Sep 17 00:00:00 2001
From: Matthias van de Meent <boekewurm+postg...@gmail.com>
Date: Tue, 19 Mar 2024 20:22:42 +0100
Subject: [PATCH v1 2/2] nbtree: Limit scope of variables in
 _bt_preprocess_keys

Many variables were used and rewritten across various loops in the code,
but we didn't actually depend on their values across loops. By limiting
their scope, we show this to the compiler, too, so that it can better
optimize this code.
---
 src/backend/access/nbtree/nbtutils.c | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)

diff --git a/src/backend/access/nbtree/nbtutils.c 
b/src/backend/access/nbtree/nbtutils.c
index 8cd6270408..d7b30f8b9b 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -2511,10 +2511,6 @@ _bt_preprocess_keys(IndexScanDesc scan)
        ScanKey         inkeys;
        ScanKey         outkeys;
        ScanKey         cur;
-       bool            test_result;
-       int                     i,
-                               j;
-       AttrNumber      attno;
        ScanKey         arrayKeyData;
        int                *keyDataMap = NULL;
        int                     arrayidx = 0;
@@ -2619,11 +2615,12 @@ _bt_preprocess_keys(IndexScanDesc scan)
         * is NULL if we haven't yet found such a key for this attr.
         */
 
-       for (i = 0; i < numberOfKeys;)
+       for (int i = 0; i < numberOfKeys;)
        {
                BTScanKeyPreproc xform[BTMaxStrategyNumber];
                int                     priorNumberOfEqualCols = 
numberOfEqualCols;
                bool            fast = true;
+               AttrNumber      attno;
 
                /* initialize for this attno */
                attno = cur->sk_attno;
@@ -2672,6 +2669,8 @@ _bt_preprocess_keys(IndexScanDesc scan)
                 */
                for (;i < numberOfKeys && cur->sk_attno == attno; cur++, i++)
                {
+                       bool            test_result;
+                       int                     j;
                        /* Apply indoption to scankey (might change 
sk_strategy!) */
                        if (!_bt_fix_scankey_strategy(cur, indoption))
                        {
@@ -2882,9 +2881,10 @@ _bt_preprocess_keys(IndexScanDesc scan)
                                Assert(OidIsValid(orderproc->fn_oid));
                        }
 
-                       for (j = BTMaxStrategyNumber; --j >= 0;)
+                       for (int j = BTMaxStrategyNumber; --j >= 0;)
                        {
                                ScanKey         chk = xform[j].skey;
+                               bool            test_result;
 
                                if (!chk || j == (BTEqualStrategyNumber - 1))
                                        continue;
@@ -2923,6 +2923,7 @@ _bt_preprocess_keys(IndexScanDesc scan)
                {
                        ScanKey         lt = xform[BTLessStrategyNumber - 
1].skey;
                        ScanKey         le = xform[BTLessEqualStrategyNumber - 
1].skey;
+                       bool            test_result;
 
                        if (_bt_compare_scankey_args(scan, le, lt, le, NULL, 
NULL,
                                                                                
 &test_result))
@@ -2940,6 +2941,7 @@ _bt_preprocess_keys(IndexScanDesc scan)
                {
                        ScanKey         gt = xform[BTGreaterStrategyNumber - 
1].skey;
                        ScanKey         ge = xform[BTGreaterEqualStrategyNumber 
- 1].skey;
+                       bool            test_result;
 
                        if (_bt_compare_scankey_args(scan, ge, gt, ge, NULL, 
NULL,
                                                                                
 &test_result))
@@ -2956,7 +2958,7 @@ _bt_preprocess_keys(IndexScanDesc scan)
                 * mark them if they are required.  They are required (possibly
                 * only in one direction) if all attrs before this one had "=".
                 */
-               for (j = BTMaxStrategyNumber; --j >= 0;)
+               for (int j = BTMaxStrategyNumber; --j >= 0;)
                {
                        if (xform[j].skey)
                        {
-- 
2.40.1

Reply via email to