]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Fix btmarkpos/btrestrpos array key wraparound bug.
authorPeter Geoghegan <pg@bowt.ie>
Thu, 28 Sep 2023 23:29:24 +0000 (16:29 -0700)
committerPeter Geoghegan <pg@bowt.ie>
Thu, 28 Sep 2023 23:29:24 +0000 (16:29 -0700)
nbtree's mark/restore processing failed to correctly handle an edge case
involving array key advancement and related search-type scan key state.
Scans with ScalarArrayScalarArrayOpExpr quals requiring mark/restore
processing (for a merge join) could incorrectly conclude that an
affected array/scan key must not have advanced during the time between
marking and restoring the scan's position.

As a result of all this, array key handling within btrestrpos could skip
a required call to _bt_preprocess_keys().  This confusion allowed later
primitive index scans to overlook tuples matching the true current array
keys.  The scan's search-type scan keys would still have spurious values
corresponding to the final array element(s) -- not values matching the
first/now-current array element(s).

To fix, remember that "array key wraparound" has taken place during the
ongoing btrescan in a flag variable stored in the scan's state, and use
that information at the point where btrestrpos decides if another call
to _bt_preprocess_keys is required.

Oversight in commit 70bc5833, which taught nbtree to handle array keys
during mark/restore processing, but missed this subtlety.  That commit
was itself a bug fix for an issue in commit 9e8da0f7, which taught
nbtree to handle ScalarArrayOpExpr quals natively.

Author: Peter Geoghegan <pg@bowt.ie>
Discussion: https://postgr.es/m/CAH2-WzkgP3DDRJxw6DgjCxo-cu-DKrvjEv_ArkP2ctBJatDCYg@mail.gmail.com
Backpatch: 11- (all supported branches).

src/backend/access/nbtree/nbtree.c
src/backend/access/nbtree/nbtutils.c
src/include/access/nbtree.h

index c2e83d3ee5e95f38733ea175c379f35278fe75e9..cfc0ad48fcd47e9906d4e9799f4e43be38d456a3 100644 (file)
@@ -366,6 +366,7 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
                so->keyData = NULL;
 
        so->arrayKeyData = NULL;        /* assume no array keys for now */
+       so->arraysStarted = false;
        so->numArrayKeys = 0;
        so->arrayKeys = NULL;
        so->arrayContext = NULL;
index ff6d3330fa24ae18cdd9c5de56fc9ed262a81625..0e9cc574df488cd44aee8d7d3cc883d1ede84e07 100644 (file)
@@ -534,6 +534,8 @@ _bt_start_array_keys(IndexScanDesc scan, ScanDirection dir)
                        curArrayKey->cur_elem = 0;
                skey->sk_argument = curArrayKey->elem_values[curArrayKey->cur_elem];
        }
+
+       so->arraysStarted = true;
 }
 
 /*
@@ -593,6 +595,14 @@ _bt_advance_array_keys(IndexScanDesc scan, ScanDirection dir)
        if (scan->parallel_scan != NULL)
                _bt_parallel_advance_array_keys(scan);
 
+       /*
+        * When no new array keys were found, the scan is "past the end" of the
+        * array keys.  _bt_start_array_keys can still "restart" the array keys if
+        * a rescan is required.
+        */
+       if (!found)
+               so->arraysStarted = false;
+
        return found;
 }
 
@@ -646,8 +656,13 @@ _bt_restore_array_keys(IndexScanDesc scan)
         * If we changed any keys, we must redo _bt_preprocess_keys.  That might
         * sound like overkill, but in cases with multiple keys per index column
         * it seems necessary to do the full set of pushups.
+        *
+        * Also do this whenever the scan's set of array keys "wrapped around" at
+        * the end of the last primitive index scan.  There won't have been a call
+        * to _bt_preprocess_keys from some other place following wrap around, so
+        * we do it for ourselves.
         */
-       if (changed)
+       if (changed || !so->arraysStarted)
        {
                _bt_preprocess_keys(scan);
                /* The mark should have been set on a consistent set of keys... */
index 8f81663e5c0aac0e1209be4fc7e54f5475c30332..883c7dc3cfc404c9a1736243fc451292fd597ac6 100644 (file)
@@ -628,8 +628,10 @@ typedef struct BTArrayKeyInfo
 
 typedef struct BTScanOpaqueData
 {
-       /* these fields are set by _bt_preprocess_keys(): */
+       /* all fields (except arraysStarted) are set by _bt_preprocess_keys(): */
        bool            qual_ok;                /* false if qual can never be satisfied */
+       bool            arraysStarted;  /* Started array keys, but have yet to "reach
+                                                                * past the end" of all arrays? */
        int                     numberOfKeys;   /* number of preprocessed scan keys */
        ScanKey         keyData;                /* array of preprocessed scan keys */