Fix bug in nbtree array primitive scan scheduling.
authorPeter Geoghegan <[email protected]>
Wed, 30 Oct 2024 14:57:19 +0000 (10:57 -0400)
committerPeter Geoghegan <[email protected]>
Wed, 30 Oct 2024 14:57:19 +0000 (10:57 -0400)
A bug in nbtree's handling of primitive index scan scheduling could lead
to wrong answers when a scrollable cursor was used with an index scan
that had a SAOP index qual.  Wrong answers were only possible when the
scan direction changed after a primitive scan was scheduled, but before
_bt_next was asked to fetch the next tuple in line (i.e. for things to
break, _bt_next had to be denied the opportunity to step off the page in
the same direction as the one used when the primscan was scheduled).
Furthermore, the issue only occurred when the page in question happened
to be the first page to be visited by the entire top-level scan; the
issue hinged upon the cursor backing up to the absolute beginning of the
key space that it returns tuples from (fetching in the opposite scan
direction across a "primitive scan boundary" always worked correctly).

To fix, make _bt_next unset the "needs primitive index scan" flag when
it detects that the current scan direction is not the one that was used
by _bt_readpage back when the primitive scan in question was scheduled.
This fixes the cases that are known to be faulty, and also seems like a
good idea on general robustness grounds.

Affected scrollable cursor cases now avoid a spurious primitive index
scan when they fetch backwards to the absolute start of the key space to
be visited by their cursor.  Fetching backwards now only returns those
tuples at the start of the scan, as expected.  It'll also be okay to
once again fetch forwards from the start at that point, since the scan
will be left in a state that's exactly consistent with the state it was
in before any tuples were ever fetched, as expected.

Oversight in commit 5bf748b8, which enhanced nbtree ScalarArrayOp
execution.

Author: Peter Geoghegan <[email protected]>
Discussion: https://postgr.es/m/CAH2-Wznv49bFsE2jkt4GuZ0tU2C91dEST=50egzjY2FeOcHL4Q@mail.gmail.com
Back: 17-, where commit 5bf748b8 first appears.

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

index c33438c4bbd28ec69eff769388f18a7392b2c524..b082cc80f6c854c2c13a6dc309a9d6e75b64f95a 100644 (file)
@@ -1568,6 +1568,7 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
 
    Assert(!P_IGNORE(opaque));
    Assert(BTScanPosIsPinned(so->currPos));
+   Assert(!so->needPrimScan);
 
    if (scan->parallel_scan)
    {
@@ -1594,7 +1595,6 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
    maxoff = PageGetMaxOffsetNumber(page);
 
    /* initialize page-level state that we'll pass to _bt_checkkeys */
-   pstate.dir = dir;
    pstate.minoff = minoff;
    pstate.maxoff = maxoff;
    pstate.finaltup = NULL;
@@ -2088,7 +2088,7 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
         */
        if (so->needPrimScan)
        {
-           if (ScanDirectionIsForward(dir))
+           if (ScanDirectionIsForward(so->currPos.dir))
                so->markPos.moreRight = true;
            else
                so->markPos.moreLeft = true;
@@ -2109,6 +2109,15 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
        else
            blkno = so->currPos.prevPage;
        lastcurrblkno = so->currPos.currPage;
+
+       /*
+        * Cancel primitive index scans that were scheduled when the call to
+        * _bt_readpage for currPos happened to use the opposite direction to
+        * the one that we're stepping in now.  (It's okay to leave the scan's
+        * array keys as-is, since the next _bt_readpage will advance them.)
+        */
+       if (so->currPos.dir != dir)
+           so->needPrimScan = false;
    }
    else
    {
@@ -2118,6 +2127,8 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
         */
        if (!_bt_parallel_seize(scan, &blkno, &lastcurrblkno, false))
            return false;
+
+       Assert(!so->needPrimScan);
    }
 
    return _bt_readnextpage(scan, blkno, lastcurrblkno, dir);
index 76be65123c8d1720246a6f2922605f2285368c5c..20227989c6ddccd090f1cbdf77029da052fc10cf 100644 (file)
@@ -1800,7 +1800,7 @@ _bt_advance_array_keys(IndexScanDesc scan, BTReadPageState *pstate,
 {
    BTScanOpaque so = (BTScanOpaque) scan->opaque;
    Relation    rel = scan->indexRelation;
-   ScanDirection dir = pstate ? pstate->dir : ForwardScanDirection;
+   ScanDirection dir = so->currPos.dir;
    int         arrayidx = 0;
    bool        beyond_end_advance = false,
                has_required_opposite_direction_only = false,
@@ -2400,8 +2400,10 @@ new_prim_scan:
    /*
     * End this primitive index scan, but schedule another.
     *
-    * Note: If the scan direction happens to change, this scheduled primitive
-    * index scan won't go ahead after all.
+    * Note: We make a soft assumption that the current scan direction will
+    * also be used within _bt_next, when it is asked to step off this page.
+    * It is up to _bt_next to cancel this scheduled primitive index scan
+    * whenever it steps to a page in the direction opposite currPos.dir.
     */
    pstate->continuescan = false;   /* Tell _bt_readpage we're done... */
    so->needPrimScan = true;    /* ...but call _bt_first again */
@@ -3458,7 +3460,7 @@ _bt_checkkeys(IndexScanDesc scan, BTReadPageState *pstate, bool arrayKeys,
 {
    TupleDesc   tupdesc = RelationGetDescr(scan->indexRelation);
    BTScanOpaque so = (BTScanOpaque) scan->opaque;
-   ScanDirection dir = pstate->dir;
+   ScanDirection dir = so->currPos.dir;
    int         ikey = 0;
    bool        res;
 
@@ -4062,7 +4064,8 @@ static void
 _bt_checkkeys_look_ahead(IndexScanDesc scan, BTReadPageState *pstate,
                         int tupnatts, TupleDesc tupdesc)
 {
-   ScanDirection dir = pstate->dir;
+   BTScanOpaque so = (BTScanOpaque) scan->opaque;
+   ScanDirection dir = so->currPos.dir;
    OffsetNumber aheadoffnum;
    IndexTuple  ahead;
 
index 5fb523ecec3d5ee5e2a214394b437db4498cad06..123fba624db0641b0fadb8b207234d88aef3319d 100644 (file)
@@ -1078,7 +1078,6 @@ typedef BTScanOpaqueData *BTScanOpaque;
 typedef struct BTReadPageState
 {
    /* Input parameters, set by _bt_readpage for _bt_checkkeys */
-   ScanDirection dir;          /* current scan direction */
    OffsetNumber minoff;        /* Lowest non-pivot tuple's offset */
    OffsetNumber maxoff;        /* Highest non-pivot tuple's offset */
    IndexTuple  finaltup;       /* Needed by scans with array keys */