]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Further simplifications to window-function code.
authordan <dan@noemail.net>
Wed, 25 Sep 2019 16:41:44 +0000 (16:41 +0000)
committerdan <dan@noemail.net>
Wed, 25 Sep 2019 16:41:44 +0000 (16:41 +0000)
FossilOrigin-Name: 5fe15c1d8383989257e11d1806e6b035acacd1b504ae385ba58d20db10f26eee

manifest
manifest.uuid
src/window.c
test/windowB.test

index a1e24119e520ec88a6e071ed3078338b53bb48d9..8d1aedc4077647875c503a83b2753a342e019225 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C In\sthe\sprevious\scheck-in,\sthe\svariable\sshould\sbe\sopenMode,\snot\sopenFlags.
-D 2019-09-25T11:49:36.048
+C Further\ssimplifications\sto\swindow-function\scode.
+D 2019-09-25T16:41:44.326
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -615,7 +615,7 @@ F src/where.c 9f3f23efc45934e7b7ea6c0c1042420b73053e7c3264feef6faf9ce6fbd5df61
 F src/whereInt.h 2c6bae136a7c0be6ff75dc36950d1968c67d005c8e51d7a9d77cb996bb4843d9
 F src/wherecode.c 535c8e228478fd971b9a5b6cb6773995b0fbf7020d5989508a5094ce5b8cd95b
 F src/whereexpr.c 05c283d26aa9c3f5d1bf13a5f6a573b43295b9db280eff18e26f97d7d7f119b4
-F src/window.c 49e97e329954f9a4d45b800c9abda9d3f0fd4c9ed6818d8f2032ee282ca99ffb
+F src/window.c e8ef31a0b36ef988c6c65dd5c52a9339d8af1b93b2eb70205be61524b28a95ef
 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
 F test/affinity2.test da465d3d490ab24ef64f7715b5953343a4967762b9350b29eb1462879ff3fb9e
 F test/affinity3.test 6a101af2fc945ce2912f6fe54dd646018551710d
@@ -1721,7 +1721,7 @@ F test/window8.tcl f2711aa3571e4e6b0dad98db8d95fd6cb8d9db0c92bbdf535f153b07606a1
 F test/window8.test c4331b27a6f66d69fa8f8bab10cc731db1a81d293ae108a68f7c3487fa94e65b
 F test/window9.test 20a6b590be718b6bc98a5356d4396d6cdf19329c547da084fa225b92d68e1693
 F test/windowA.test 6d63dc1260daa17141a55007600581778523a8b420629f1282d2acfc36af23be
-F test/windowB.test f9a79e1bd669d513c6cb2a7b448773f7954b8e35e2b843b6c371475402b412b0
+F test/windowB.test 7a983ea1cc1cf72be7f378e4b32f6cb2d73014c5cd8b25aaee825164cd4269e5
 F test/windowerr.tcl f5acd6fbc210d7b5546c0e879d157888455cd4a17a1d3f28f07c1c8a387019e0
 F test/windowerr.test a8b752402109c15aa1c5efe1b93ccb0ce1ef84fa964ae1cd6684dd0b3cc1819b
 F test/windowfault.test a90b397837209f15e54afa62e8be39b2759a0101fae04e05a08bcc50e243a452
@@ -1845,7 +1845,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P ab853724a7e01ca32167d294c3c80d6632e805bdf39b6d56db82226a00ad72dc
-R 7ebf56a73c5a22a97e2a870cc95fd90d
-U drh
-Z d397f8051e45872877e380eaa24c2a78
+P 77b0db22d6c7a031f332bfcf1c8fcd94e464de9f2396f0327ee761a30ebc8b60
+R c1a9d42f9e5546f18659ba3e28a2d3d8
+U dan
+Z 255d26b221f56372f1d8aed4fd8d1b44
index f50d6ab792cd9741c07a261ad8ed45a281809d87..cec9cf3dd933704ca9041a1dd6eeaf1fbce8ae0a 100644 (file)
@@ -1 +1 @@
-77b0db22d6c7a031f332bfcf1c8fcd94e464de9f2396f0327ee761a30ebc8b60
\ No newline at end of file
+5fe15c1d8383989257e11d1806e6b035acacd1b504ae385ba58d20db10f26eee
\ No newline at end of file
index 161b5e3f343770f1c23364d26a4075e5b7f4d17b..1dea375b7c649fd99837ac454482a6f1b2c998da 100644 (file)
@@ -1482,8 +1482,6 @@ struct WindowCodeArg {
   int regGosub;              /* Register used with OP_Gosub(addrGosub) */
   int regArg;                /* First in array of accumulator registers */
   int eDelete;               /* See above */
-  int regStart;              /* Value of <expr> PRECEDING */
-  int regEnd;                /* Value of <expr> FOLLOWING */
 
   WindowCsrAndReg start;
   WindowCsrAndReg current;
@@ -1599,38 +1597,7 @@ static void windowAggStep(
         VdbeCoverage(v);
         sqlite3ReleaseTempReg(pParse, regTmp);
       }
-
       
-      /* If this is a (RANGE BETWEEN a PRECEDING AND b PRECEDING) or a
-      ** (RANGE BETWEEN b FOLLOWING AND a FOLLOWING) frame and (b > a), 
-      ** omit the OP_AggStep or OP_AggInverse if the peer value is numeric.
-      ** A numeric peer value is one for which the following is true: 
-      **
-      **    (peer IS NOT NULL AND peer < '')
-      */
-      if( pWin->eFrmType==TK_RANGE 
-       && pWin->eStart==pWin->eEnd 
-       && pWin->eStart==TK_PRECEDING
-      ){
-        int regPeer = sqlite3GetTempReg(pParse);
-        int regString = sqlite3GetTempReg(pParse);
-        int lbl = sqlite3VdbeMakeLabel(pParse);
-        VdbeModuleComment((v, "windowAggStep \"peer is numeric?\" test"));
-        assert( pMWin->eStart==TK_PRECEDING ); /* because pWin same as pMWin */
-        sqlite3VdbeAddOp3(v, OP_Le, p->regStart, lbl, p->regEnd);
-        VdbeCoverageNeverNull(v); /* because <expr> values previously checked */
-        windowReadPeerValues(p, csr, regPeer);
-        sqlite3VdbeAddOp2(v, OP_IsNull, regPeer, lbl);
-        sqlite3VdbeAddOp4(v, OP_String8, 0, regString, 0, "", P4_STATIC);
-        addrIf2 = sqlite3VdbeAddOp3(v, OP_Lt, regString, 0, regPeer);
-        sqlite3ReleaseTempReg(pParse, regPeer);
-        sqlite3ReleaseTempReg(pParse, regString);
-        sqlite3VdbeResolveLabel(v, lbl);
-        VdbeModuleComment((v, "windowAggStep end \"peer is numeric?\""));
-        assert( pWin->eStart==TK_PRECEDING || pWin->eStart==TK_FOLLOWING );
-        assert( pMWin->pOrderBy && pMWin->pOrderBy->nExpr==1 );
-      }
-
       if( pWin->bExprArgs ){
         int iStart = sqlite3VdbeCurrentAddr(v);
         VdbeOp *pOp, *pEnd;
@@ -2180,6 +2147,24 @@ static int windowCodeOp(
     windowAggFinal(p, 0);
   }
   addrContinue = sqlite3VdbeCurrentAddr(v);
+
+  /* If this is a (RANGE BETWEEN a FOLLOWING AND b FOLLOWING) or
+  ** (RANGE BETWEEN b PRECEDING AND a PRECEDING) frame, ensure the 
+  ** start cursor does not advance past the end cursor within the 
+  ** temporary table. It otherwise might, if (a>b).  */
+  if( pMWin->eStart==pMWin->eEnd && regCountdown
+   && pMWin->eFrmType==TK_RANGE && op==WINDOW_AGGINVERSE
+  ){
+    int regRowid1 = sqlite3GetTempReg(pParse);
+    int regRowid2 = sqlite3GetTempReg(pParse);
+    sqlite3VdbeAddOp2(v, OP_Rowid, p->start.csr, regRowid1);
+    sqlite3VdbeAddOp2(v, OP_Rowid, p->end.csr, regRowid2);
+    sqlite3VdbeAddOp3(v, OP_Ge, regRowid2, lblDone, regRowid1);
+    sqlite3ReleaseTempReg(pParse, regRowid1);
+    sqlite3ReleaseTempReg(pParse, regRowid2);
+    assert( pMWin->eStart==TK_PRECEDING || pMWin->eStart==TK_FOLLOWING );
+  }
+
   switch( op ){
     case WINDOW_RETURN_ROW:
       csr = p->current.csr;
@@ -2230,23 +2215,7 @@ static int windowCodeOp(
 
   if( bPeer ){
     int nReg = (pMWin->pOrderBy ? pMWin->pOrderBy->nExpr : 0);
-    int regTmp;
-
-    /* If this is a (RANGE BETWEEN a FOLLOWING AND b FOLLOWING), ensure
-    ** the start cursor does not advance past the end cursor within the
-    ** temporary table. It otherwise might, if (a>b).  */
-    if( pMWin->eStart==TK_FOLLOWING && pMWin->eEnd==TK_FOLLOWING
-     && pMWin->eFrmType==TK_RANGE && op==WINDOW_AGGINVERSE
-    ){
-      int regRowid1 = sqlite3GetTempReg(pParse);
-      int regRowid2 = sqlite3GetTempReg(pParse);
-      sqlite3VdbeAddOp2(v, OP_Rowid, p->start.csr, regRowid1);
-      sqlite3VdbeAddOp2(v, OP_Rowid, p->end.csr, regRowid2);
-      sqlite3VdbeAddOp3(v, OP_Ge, regRowid2, lblDone, regRowid1);
-      sqlite3ReleaseTempReg(pParse, regRowid1);
-      sqlite3ReleaseTempReg(pParse, regRowid2);
-    }
-    regTmp = (nReg ? sqlite3GetTempRange(pParse, nReg) : 0);
+    int regTmp = (nReg ? sqlite3GetTempRange(pParse, nReg) : 0);
     windowReadPeerValues(p, csr, regTmp);
     windowIfNewPeer(pParse, pMWin->pOrderBy, regTmp, reg, addrContinue);
     sqlite3ReleaseTempRange(pParse, regTmp, nReg);
@@ -2611,20 +2580,20 @@ static int windowExprGtZero(Parse *pParse, Expr *pExpr){
 **         while( (csrEnd.key + regEnd) <= csrCurrent.key ){
 **           AGGSTEP
 **         }
-**         RETURN_ROW
 **         while( (csrStart.key + regStart) < csrCurrent.key ){
 **           AGGINVERSE
 **         }
+**         RETURN_ROW
 **       }
 **     }
 **     flush:
 **       while( (csrEnd.key + regEnd) <= csrCurrent.key ){
 **         AGGSTEP
 **       }
-**       RETURN_ROW
 **       while( (csrStart.key + regStart) < csrCurrent.key ){
 **         AGGINVERSE
 **       }
+**       RETURN_ROW
 **
 **   RANGE BETWEEN <expr1> FOLLOWING AND <expr2> FOLLOWING
 **
@@ -2689,6 +2658,8 @@ void sqlite3WindowCodeStep(
   int regFlushPart = 0;           /* Register for "Gosub flush_partition" */
   WindowCodeArg s;                /* Context object for sub-routines */
   int lblWhereEnd;                /* Label just before sqlite3WhereEnd() code */
+  int regStart = 0;               /* Value of <expr> PRECEDING */
+  int regEnd = 0;                 /* Value of <expr> FOLLOWING */
 
   assert( pMWin->eStart==TK_PRECEDING || pMWin->eStart==TK_CURRENT 
        || pMWin->eStart==TK_FOLLOWING || pMWin->eStart==TK_UNBOUNDED 
@@ -2759,10 +2730,10 @@ void sqlite3WindowCodeStep(
   ** clause, allocate registers to store the results of evaluating each
   ** <expr>.  */
   if( pMWin->eStart==TK_PRECEDING || pMWin->eStart==TK_FOLLOWING ){
-    s.regStart = ++pParse->nMem;
+    regStart = ++pParse->nMem;
   }
   if( pMWin->eEnd==TK_PRECEDING || pMWin->eEnd==TK_FOLLOWING ){
-    s.regEnd = ++pParse->nMem;
+    regEnd = ++pParse->nMem;
   }
 
   /* If this is not a "ROWS BETWEEN ..." frame, then allocate arrays of
@@ -2817,18 +2788,18 @@ void sqlite3WindowCodeStep(
   /* This block is run for the first row of each partition */
   s.regArg = windowInitAccum(pParse, pMWin);
 
-  if( s.regStart ){
-    sqlite3ExprCode(pParse, pMWin->pStart, s.regStart);
-    windowCheckValue(pParse, s.regStart, 0 + (pMWin->eFrmType==TK_RANGE?3:0));
+  if( regStart ){
+    sqlite3ExprCode(pParse, pMWin->pStart, regStart);
+    windowCheckValue(pParse, regStart, 0 + (pMWin->eFrmType==TK_RANGE?3:0));
   }
-  if( s.regEnd ){
-    sqlite3ExprCode(pParse, pMWin->pEnd, s.regEnd);
-    windowCheckValue(pParse, s.regEnd, 1 + (pMWin->eFrmType==TK_RANGE?3:0));
+  if( regEnd ){
+    sqlite3ExprCode(pParse, pMWin->pEnd, regEnd);
+    windowCheckValue(pParse, regEnd, 1 + (pMWin->eFrmType==TK_RANGE?3:0));
   }
 
-  if( pMWin->eFrmType!=TK_RANGE && pMWin->eStart==pMWin->eEnd && s.regStart ){
+  if( pMWin->eFrmType!=TK_RANGE && pMWin->eStart==pMWin->eEnd && regStart ){
     int op = ((pMWin->eStart==TK_FOLLOWING) ? OP_Ge : OP_Le);
-    int addrGe = sqlite3VdbeAddOp3(v, op, s.regStart, 0, s.regEnd);
+    int addrGe = sqlite3VdbeAddOp3(v, op, regStart, 0, regEnd);
     VdbeCoverageNeverNullIf(v, op==OP_Ge); /* NeverNull because bound <expr> */
     VdbeCoverageNeverNullIf(v, op==OP_Le); /*   values previously checked */
     windowAggFinal(&s, 0);
@@ -2839,9 +2810,9 @@ void sqlite3WindowCodeStep(
     sqlite3VdbeAddOp2(v, OP_Goto, 0, lblWhereEnd);
     sqlite3VdbeJumpHere(v, addrGe);
   }
-  if( pMWin->eStart==TK_FOLLOWING && pMWin->eFrmType!=TK_RANGE && s.regEnd ){
+  if( pMWin->eStart==TK_FOLLOWING && pMWin->eFrmType!=TK_RANGE && regEnd ){
     assert( pMWin->eEnd==TK_FOLLOWING );
-    sqlite3VdbeAddOp3(v, OP_Subtract, s.regStart, s.regEnd, s.regStart);
+    sqlite3VdbeAddOp3(v, OP_Subtract, regStart, regEnd, regStart);
   }
 
   if( pMWin->eStart!=TK_UNBOUNDED ){
@@ -2873,23 +2844,23 @@ void sqlite3WindowCodeStep(
       if( pMWin->eFrmType==TK_RANGE ){
         int lbl = sqlite3VdbeMakeLabel(pParse);
         int addrNext = sqlite3VdbeCurrentAddr(v);
-        windowCodeRangeTest(&s, OP_Ge, s.current.csr, s.regEnd, s.end.csr, lbl);
-        windowCodeOp(&s, WINDOW_AGGINVERSE, s.regStart, 0);
+        windowCodeRangeTest(&s, OP_Ge, s.current.csr, regEnd, s.end.csr, lbl);
+        windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0);
         windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 0);
         sqlite3VdbeAddOp2(v, OP_Goto, 0, addrNext);
         sqlite3VdbeResolveLabel(v, lbl);
       }else{
-        windowCodeOp(&s, WINDOW_RETURN_ROW, s.regEnd, 0);
-        windowCodeOp(&s, WINDOW_AGGINVERSE, s.regStart, 0);
+        windowCodeOp(&s, WINDOW_RETURN_ROW, regEnd, 0);
+        windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0);
       }
     }
   }else
   if( pMWin->eEnd==TK_PRECEDING ){
     int bRPS = (pMWin->eStart==TK_PRECEDING && pMWin->eFrmType==TK_RANGE);
-    windowCodeOp(&s, WINDOW_AGGSTEP, s.regEnd, 0);
-    if( bRPS ) windowCodeOp(&s, WINDOW_AGGINVERSE, s.regStart, 0);
+    windowCodeOp(&s, WINDOW_AGGSTEP, regEnd, 0);
+    if( bRPS ) windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0);
     windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 0);
-    if( !bRPS ) windowCodeOp(&s, WINDOW_AGGINVERSE, s.regStart, 0);
+    if( !bRPS ) windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0);
   }else{
     int addr = 0;
     windowCodeOp(&s, WINDOW_AGGSTEP, 0, 0);
@@ -2897,24 +2868,24 @@ void sqlite3WindowCodeStep(
       if( pMWin->eFrmType==TK_RANGE ){
         int lbl = 0;
         addr = sqlite3VdbeCurrentAddr(v);
-        if( s.regEnd ){
+        if( regEnd ){
           lbl = sqlite3VdbeMakeLabel(pParse);
-          windowCodeRangeTest(&s, OP_Ge, s.current.csr,s.regEnd,s.end.csr,lbl);
+          windowCodeRangeTest(&s, OP_Ge, s.current.csr, regEnd, s.end.csr, lbl);
         }
         windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 0);
-        windowCodeOp(&s, WINDOW_AGGINVERSE, s.regStart, 0);
-        if( s.regEnd ){
+        windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0);
+        if( regEnd ){
           sqlite3VdbeAddOp2(v, OP_Goto, 0, addr);
           sqlite3VdbeResolveLabel(v, lbl);
         }
       }else{
-        if( s.regEnd ){
-          addr = sqlite3VdbeAddOp3(v, OP_IfPos, s.regEnd, 0, 1);
+        if( regEnd ){
+          addr = sqlite3VdbeAddOp3(v, OP_IfPos, regEnd, 0, 1);
           VdbeCoverage(v);
         }
         windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 0);
-        windowCodeOp(&s, WINDOW_AGGINVERSE, s.regStart, 0);
-        if( s.regEnd ) sqlite3VdbeJumpHere(v, addr);
+        windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0);
+        if( regEnd ) sqlite3VdbeJumpHere(v, addr);
       }
     }
   }
@@ -2933,8 +2904,8 @@ void sqlite3WindowCodeStep(
   VdbeCoverage(v);
   if( pMWin->eEnd==TK_PRECEDING ){
     int bRPS = (pMWin->eStart==TK_PRECEDING && pMWin->eFrmType==TK_RANGE);
-    windowCodeOp(&s, WINDOW_AGGSTEP, s.regEnd, 0);
-    if( bRPS ) windowCodeOp(&s, WINDOW_AGGINVERSE, s.regStart, 0);
+    windowCodeOp(&s, WINDOW_AGGSTEP, regEnd, 0);
+    if( bRPS ) windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0);
     windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 0);
   }else if( pMWin->eStart==TK_FOLLOWING ){
     int addrStart;
@@ -2944,18 +2915,18 @@ void sqlite3WindowCodeStep(
     windowCodeOp(&s, WINDOW_AGGSTEP, 0, 0);
     if( pMWin->eFrmType==TK_RANGE ){
       addrStart = sqlite3VdbeCurrentAddr(v);
-      addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, s.regStart, 1);
+      addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 1);
       addrBreak1 = windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 1);
     }else
     if( pMWin->eEnd==TK_UNBOUNDED ){
       addrStart = sqlite3VdbeCurrentAddr(v);
-      addrBreak1 = windowCodeOp(&s, WINDOW_RETURN_ROW, s.regStart, 1);
+      addrBreak1 = windowCodeOp(&s, WINDOW_RETURN_ROW, regStart, 1);
       addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, 0, 1);
     }else{
       assert( pMWin->eEnd==TK_FOLLOWING );
       addrStart = sqlite3VdbeCurrentAddr(v);
-      addrBreak1 = windowCodeOp(&s, WINDOW_RETURN_ROW, s.regEnd, 1);
-      addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, s.regStart, 1);
+      addrBreak1 = windowCodeOp(&s, WINDOW_RETURN_ROW, regEnd, 1);
+      addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 1);
     }
     sqlite3VdbeAddOp2(v, OP_Goto, 0, addrStart);
     sqlite3VdbeJumpHere(v, addrBreak2);
@@ -2970,7 +2941,7 @@ void sqlite3WindowCodeStep(
     windowCodeOp(&s, WINDOW_AGGSTEP, 0, 0);
     addrStart = sqlite3VdbeCurrentAddr(v);
     addrBreak = windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 1);
-    windowCodeOp(&s, WINDOW_AGGINVERSE, s.regStart, 0);
+    windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0);
     sqlite3VdbeAddOp2(v, OP_Goto, 0, addrStart);
     sqlite3VdbeJumpHere(v, addrBreak);
   }
index 7390556779c82c2fef9c621d5b38b0675765cbc5..321c481406906a932d2b104975e9e531daa8c423 100644 (file)
@@ -297,4 +297,43 @@ do_execsql_test 6.2 {
   ) FROM t1;
 } {7 {} 8 {} abc 1001} 
 
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 7.0 {
+  CREATE TABLE t1(a, c);
+  CREATE INDEX i1 ON t1(a);
+
+  INSERT INTO t1 VALUES(NULL, 46);
+  INSERT INTO t1 VALUES(NULL, 45);
+  INSERT INTO t1 VALUES(7,  997);
+  INSERT INTO t1 VALUES(7,  1000);
+  INSERT INTO t1 VALUES(8,  997);
+  INSERT INTO t1 VALUES(8,  1000);
+  INSERT INTO t1 VALUES('abc', 1001);
+  INSERT INTO t1 VALUES('abc', 1004);
+  INSERT INTO t1 VALUES('xyz', 3333);
+}
+
+do_execsql_test 7.1 {
+  SELECT a, max(c) OVER (
+    ORDER BY a RANGE BETWEEN 2 FOLLOWING AND 0 FOLLOWING
+  ) FROM t1;
+} {{} 46 {} 46  7 {} 7 {} 8 {} 8 {}  abc 1004 abc 1004 xyz 3333}
+do_execsql_test 7.2 {
+  SELECT a, min(c) OVER (
+    ORDER BY a RANGE BETWEEN 2 FOLLOWING AND 0 FOLLOWING
+  ) FROM t1;
+} {{} 45 {} 45  7 {} 7 {} 8 {} 8 {}  abc 1001 abc 1001 xyz 3333}
+
+do_execsql_test 7.3 {
+  SELECT a, max(c) OVER (
+    ORDER BY a RANGE BETWEEN 0 PRECEDING AND 2 PRECEDING
+  ) FROM t1;
+} {{} 46 {} 46  7 {} 7 {} 8 {} 8 {}  abc 1004 abc 1004 xyz 3333}
+do_execsql_test 7.4 {
+  SELECT a, min(c) OVER (
+    ORDER BY a RANGE BETWEEN 0 PRECEDING AND 2 PRECEDING
+  ) FROM t1;
+} {{} 45 {} 45  7 {} 7 {} 8 {} 8 {}  abc 1001 abc 1001 xyz 3333}
+
 finish_test