aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordan <dan@noemail.net>2018-05-25 20:30:17 +0000
committerdan <dan@noemail.net>2018-05-25 20:30:17 +0000
commit09590aaa1bc5c31ecba63e40a640ee58c98bfc10 (patch)
tree762425cbe5a02ab4a96cff7dd251274ba518f765 /src
parent83023a335754151aaa32e4b3b4803dde1cc0cdc1 (diff)
downloadsqlite-09590aaa1bc5c31ecba63e40a640ee58c98bfc10.tar.gz
sqlite-09590aaa1bc5c31ecba63e40a640ee58c98bfc10.zip
Fix "RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING" window frame
processing. FossilOrigin-Name: b4e9c686697a5211a3bfa47e63f0684e3d4241d8c292cffe1a967bc39ad7cd8f
Diffstat (limited to 'src')
-rw-r--r--src/window.c204
1 files changed, 124 insertions, 80 deletions
diff --git a/src/window.c b/src/window.c
index 1c8b759d4..ecbbb9c35 100644
--- a/src/window.c
+++ b/src/window.c
@@ -114,47 +114,95 @@ static void windowAggStep(
}
/*
-** ROWS BETWEEN <expr> PRECEDING AND <expr> FOLLOWING
+** ROWS BETWEEN <expr1> PRECEDING AND <expr2> FOLLOWING
+** ----------------------------------------------------
**
-** ...
-** if( new partition ){
-** Gosub flush_partition
+** Pseudo-code for the implementation of this window frame type is as
+** follows. sqlite3WhereBegin() has already been called to generate the
+** top of the main loop when this function is called.
+**
+** Each time the sub-routine at addrGosub is invoked, a single output
+** row is generated based on the current row indicated by Window.iEphCsr.
+**
+** ...
+** if( new partition ){
+** Gosub flush_partition
+** }
+** Insert (record in eph-table)
+** sqlite3WhereEnd()
+** Gosub flush_partition
+**
+** flush_partition:
+** Once {
+** OpenDup (iEphCsr -> csrStart)
+** OpenDup (iEphCsr -> csrEnd)
** }
-** Insert (record in eph-table)
-** sqlite3WhereEnd()
-** Gosub flush_partition
+** regStart = <expr1> // PRECEDING expression
+** regEnd = <expr2> // FOLLOWING expression
+** if( regStart<0 || regEnd<0 ){ error! }
+** Rewind (csr,csrStart,csrEnd) // if EOF goto flush_partition_done
+** Next(csrEnd) // if EOF skip Aggstep
+** Aggstep (csrEnd)
+** if( (regEnd--)<=0 ){
+** AggFinal (xValue)
+** Gosub addrGosub
+** Next(csr) // if EOF goto flush_partition_done
+** if( (regStart--)<=0 ){
+** AggStep (csrStart, xInverse)
+** Next(csrStart)
+** }
+** }
+** flush_partition_done:
+** ResetSorter (csr)
+** Return
**
-** flush_partition:
-** Once {
-** OpenDup (iEphCsr -> csrStart)
-** OpenDup (iEphCsr -> csrEnd)
-** }
-** regStart = <expr1> // PRECEDING expression
-** regEnd = <expr2> // FOLLOWING expression
-** if( regStart<0 || regEnd<0 ) throw exception!
-** Rewind (csr,csrStart,csrEnd) // if EOF goto flush_partition_done
-** Aggstep (csrEnd)
-** Next(csrEnd) // if EOF fall-through
-** if( (regEnd--)<=0 ){
+** ROWS BETWEEN <expr> PRECEDING AND CURRENT ROW
+** ROWS BETWEEN CURRENT ROW AND <expr> FOLLOWING
+** ROWS BETWEEN UNBOUNDED PRECEDING AND <expr> FOLLOWING
+**
+** These are similar to the above. For "CURRENT ROW", intialize the
+** register to 0. For "UNBOUNDED PRECEDING" to infinity.
+**
+** ROWS BETWEEN <expr> PRECEDING AND UNBOUNDED FOLLOWING
+** ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+**
+** Rewind (csr,csrStart,csrEnd) // if EOF goto flush_partition_done
+** while( 1 ){
+** Next(csrEnd) // Exit while(1) at EOF
+** Aggstep (csrEnd)
+** }
+** while( 1 ){
** AggFinal (xValue)
** Gosub addrGosub
-** Next(csr) // if EOF goto flush_partition_done
+** Next(csr) // if EOF goto flush_partition_done
** if( (regStart--)<=0 ){
** AggStep (csrStart, xInverse)
** Next(csrStart)
** }
** }
-** flush_partition_done:
-** ResetSorter (csr)
-** Return
**
-** ROWS BETWEEN <expr> PRECEDING AND CURRENT ROW
-** ROWS BETWEEN CURRENT ROW AND <expr> FOLLOWING
-** ROWS BETWEEN <expr> PRECEDING AND UNBOUNDED FOLLOWING
-** ROWS BETWEEN UNBOUNDED PRECEDING AND <expr> FOLLOWING
+** For the "CURRENT ROW AND UNBOUNDED FOLLOWING" case, the final if()
+** condition is always true (as if regStart were initialized to 0).
**
-** These are similar to the above. For "CURRENT ROW", intialize the
-** register to 0. For "UNBOUNDED ..." to infinity.
+** RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+**
+** This is the only RANGE case handled by this routine. It modifies the
+** second while( 1 ) loop in "ROWS BETWEEN CURRENT ... UNBOUNDED..." to
+** be:
+**
+** while( 1 ){
+** AggFinal (xValue)
+** while( 1 ){
+** regPeer++
+** Gosub addrGosub
+** Next(csr) // if EOF goto flush_partition_done
+** if( new peer ) break;
+** }
+** while( (regPeer--)>0 ){
+** AggStep (csrStart, xInverse)
+** Next(csrStart)
+** }
+** }
**
** ROWS BETWEEN <expr> FOLLOWING AND <expr> FOLLOWING
**
@@ -220,6 +268,11 @@ static void windowCodeRowExprStep(
int addrIfPos1;
int addrIfPos2;
+ int regPeer = 0; /* Number of peers in current group */
+ int regPeerVal = 0; /* Array of values identifying peer group */
+ int iPeer = 0; /* Column offset in eph-table of peer vals */
+ int nPeerVal; /* Number of peer values */
+
assert( pMWin->eStart==TK_PRECEDING
|| pMWin->eStart==TK_CURRENT
|| pMWin->eStart==TK_FOLLOWING
@@ -334,6 +387,18 @@ static void windowCodeRowExprStep(
if( pMWin->eStart==TK_FOLLOWING ){
addrIfPos2 = sqlite3VdbeAddOp3(v, OP_IfPos, regStart, 0 , 1);
}
+ if( pMWin->eType==TK_RANGE ){
+ assert( pMWin->eStart==TK_CURRENT && pMWin->pOrderBy );
+ regPeer = ++pParse->nMem;
+ regPeerVal = pParse->nMem+1;
+ iPeer = pMWin->nBufferCol + (pMWin->pPartition?pMWin->pPartition->nExpr:0);
+ nPeerVal = pMWin->pOrderBy->nExpr;
+ pParse->nMem += (2 * nPeerVal);
+ for(k=0; k<nPeerVal; k++){
+ sqlite3VdbeAddOp3(v, OP_Column, pMWin->iEphCsr, iPeer+k, regPeerVal+k);
+ }
+ sqlite3VdbeAddOp2(v, OP_Integer, 0, regPeer);
+ }
for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regResult);
sqlite3VdbeAddOp3(v,
@@ -341,9 +406,24 @@ static void windowCodeRowExprStep(
);
sqlite3VdbeAppendP4(v, pWin->pFunc, P4_FUNCDEF);
}
+ if( pMWin->eType==TK_RANGE ){
+ sqlite3VdbeAddOp2(v, OP_AddImm, regPeer, 1);
+ }
sqlite3VdbeAddOp2(v, OP_Gosub, regGosub, addrGosub);
sqlite3VdbeAddOp2(v, OP_Next, pMWin->iEphCsr, sqlite3VdbeCurrentAddr(v)+2);
sqlite3VdbeAddOp2(v, OP_Goto, 0, lblFlushDone);
+ if( pMWin->eType==TK_RANGE ){
+ KeyInfo *pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pMWin->pOrderBy,0,0);
+ int addrJump = sqlite3VdbeCurrentAddr(v)-4;
+ for(k=0; k<nPeerVal; k++){
+ int iOut = regPeerVal + nPeerVal + k;
+ sqlite3VdbeAddOp3(v, OP_Column, pMWin->iEphCsr, iPeer+k, iOut);
+ }
+ sqlite3VdbeAddOp3(v, OP_Compare, regPeerVal, regPeerVal+nPeerVal, nPeerVal);
+ sqlite3VdbeAppendP4(v, (void*)pKeyInfo, P4_KEYINFO);
+ addr = sqlite3VdbeCurrentAddr(v)+1;
+ sqlite3VdbeAddOp3(v, OP_Jump, addr, addrJump, addr);
+ }
if( pMWin->eStart==TK_FOLLOWING ){
sqlite3VdbeJumpHere(v, addrIfPos2);
}
@@ -352,13 +432,21 @@ static void windowCodeRowExprStep(
|| pMWin->eStart==TK_PRECEDING
|| pMWin->eStart==TK_FOLLOWING
){
+ int addrJumpHere = 0;
if( pMWin->eStart==TK_PRECEDING ){
- addrIfPos2 = sqlite3VdbeAddOp3(v, OP_IfPos, regStart, 0 , 1);
+ addrJumpHere = sqlite3VdbeAddOp3(v, OP_IfPos, regStart, 0 , 1);
+ }
+ if( pMWin->eType==TK_RANGE ){
+ sqlite3VdbeAddOp3(v, OP_IfPos, regPeer, sqlite3VdbeCurrentAddr(v)+2, 1);
+ addrJumpHere = sqlite3VdbeAddOp0(v, OP_Goto);
}
sqlite3VdbeAddOp2(v, OP_Next, csrStart, sqlite3VdbeCurrentAddr(v)+1);
windowAggStep(pParse, pMWin, csrStart, 1, reg);
- if( pMWin->eStart==TK_PRECEDING ){
- sqlite3VdbeJumpHere(v, addrIfPos2);
+ if( pMWin->eType==TK_RANGE ){
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, addrJumpHere-1);
+ }
+ if( addrJumpHere ){
+ sqlite3VdbeJumpHere(v, addrJumpHere);
}
}
if( pMWin->eEnd==TK_FOLLOWING ){
@@ -554,52 +642,6 @@ static void windowCodeDefaultStep(
** Gosub addrGosub
** sqlite3WhereEnd()
**
-** ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
-** ROWS BETWEEN CURRENT ROW AND CURRENT ROW
-** ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
-**
-**========================================================================
-**
-** ROWS BETWEEN <expr> PRECEDING AND <expr> PRECEDING
-**
-** Replace the bit after "Rewind" in the above with:
-**
-** if( (regEnd--)<=0 ){
-** AggStep (csr3)
-** Next (csr3)
-** }
-** AggFinal (xValue)
-** Gosub addrGosub
-** Next(csr) // if EOF goto flush_partition_done
-** if( (regStart--)<=0 ){
-** AggStep (csr2, xInverse)
-** Next (csr2)
-** }
-**
-** ROWS BETWEEN <expr> FOLLOWING AND <expr> FOLLOWING
-**
-** regEnd = regEnd - regStart
-** Rewind (csr,csr2,csr3) // if EOF goto flush_partition_done
-** Aggstep (csr3)
-** Next(csr3) // if EOF fall-through
-** if( (regEnd--)<=0 ){
-** AggStep (csr2, xInverse)
-** Next (csr2)
-** if( (regStart--)<=0 ){
-** AggFinal (xValue)
-** Gosub addrGosub
-** Next(csr) // if EOF goto flush_partition_done
-** }
-** }
-**
-** ROWS BETWEEN UNBOUNDED PRECEDING AND <expr> PRECEDING
-** ROWS BETWEEN <expr> FOLLOWING AND UNBOUNDED FOLLOWING
-**
-** Similar to the above, except with regStart or regEnd set to infinity,
-** as appropriate.
-**
-**
-**
*/
void sqlite3WindowCodeStep(
Parse *pParse,
@@ -611,8 +653,10 @@ void sqlite3WindowCodeStep(
){
Window *pMWin = p->pWin;
- if( pMWin->pStart || pMWin->pEnd ){
- assert( pMWin->eType==TK_ROWS );
+ if( (pMWin->eType==TK_ROWS
+ && (pMWin->eStart!=TK_UNBOUNDED || pMWin->eEnd!=TK_CURRENT))
+ || (pMWin->eStart==TK_CURRENT && pMWin->eEnd==TK_UNBOUNDED)
+ ){
*pbLoop = 0;
windowCodeRowExprStep(pParse, p, pWInfo, regGosub, addrGosub);
return;