|
|
|
@@ -914,7 +914,7 @@ Window *sqlite3WindowAlloc(
|
|
|
|
|
int bImplicitFrame = 0;
|
|
|
|
|
|
|
|
|
|
/* Parser assures the following: */
|
|
|
|
|
assert( eType==0 || eType==TK_RANGE || eType==TK_ROWS );
|
|
|
|
|
assert( eType==0 || eType==TK_RANGE || eType==TK_ROWS || eType==TK_GROUPS );
|
|
|
|
|
assert( eStart==TK_CURRENT || eStart==TK_PRECEDING
|
|
|
|
|
|| eStart==TK_UNBOUNDED || eStart==TK_FOLLOWING );
|
|
|
|
|
assert( eEnd==TK_CURRENT || eEnd==TK_FOLLOWING
|
|
|
|
@@ -1327,69 +1327,6 @@ static void windowAggFinal(Parse *pParse, Window *pMWin, int bFinal){
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
** This function generates VM code to invoke the sub-routine at address
|
|
|
|
|
** lblFlushPart once for each partition with the entire partition cached in
|
|
|
|
|
** the Window.iEphCsr temp table.
|
|
|
|
|
*/
|
|
|
|
|
static void windowPartitionCache(
|
|
|
|
|
Parse *pParse,
|
|
|
|
|
Select *p, /* The rewritten SELECT statement */
|
|
|
|
|
WhereInfo *pWInfo, /* WhereInfo to call WhereEnd() on */
|
|
|
|
|
int regFlushPart, /* Register to use with Gosub lblFlushPart */
|
|
|
|
|
int lblFlushPart, /* Subroutine to Gosub to */
|
|
|
|
|
int *pRegSize /* OUT: Register containing partition size */
|
|
|
|
|
){
|
|
|
|
|
Window *pMWin = p->pWin;
|
|
|
|
|
Vdbe *v = sqlite3GetVdbe(pParse);
|
|
|
|
|
int iSubCsr = p->pSrc->a[0].iCursor;
|
|
|
|
|
int nSub = p->pSrc->a[0].pTab->nCol;
|
|
|
|
|
int k;
|
|
|
|
|
|
|
|
|
|
int reg = pParse->nMem+1;
|
|
|
|
|
int regRecord = reg+nSub;
|
|
|
|
|
int regRowid = regRecord+1;
|
|
|
|
|
|
|
|
|
|
*pRegSize = regRowid;
|
|
|
|
|
pParse->nMem += nSub + 2;
|
|
|
|
|
|
|
|
|
|
/* Load the column values for the row returned by the sub-select
|
|
|
|
|
** into an array of registers starting at reg. */
|
|
|
|
|
for(k=0; k<nSub; k++){
|
|
|
|
|
sqlite3VdbeAddOp3(v, OP_Column, iSubCsr, k, reg+k);
|
|
|
|
|
}
|
|
|
|
|
sqlite3VdbeAddOp3(v, OP_MakeRecord, reg, nSub, regRecord);
|
|
|
|
|
|
|
|
|
|
/* Check if this is the start of a new partition. If so, call the
|
|
|
|
|
** flush_partition sub-routine. */
|
|
|
|
|
if( pMWin->pPartition ){
|
|
|
|
|
int addr;
|
|
|
|
|
ExprList *pPart = pMWin->pPartition;
|
|
|
|
|
int nPart = pPart->nExpr;
|
|
|
|
|
int regNewPart = reg + pMWin->nBufferCol;
|
|
|
|
|
KeyInfo *pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pPart, 0, 0);
|
|
|
|
|
|
|
|
|
|
addr = sqlite3VdbeAddOp3(v, OP_Compare, regNewPart, pMWin->regPart,nPart);
|
|
|
|
|
sqlite3VdbeAppendP4(v, (void*)pKeyInfo, P4_KEYINFO);
|
|
|
|
|
sqlite3VdbeAddOp3(v, OP_Jump, addr+2, addr+4, addr+2);
|
|
|
|
|
VdbeCoverageEqNe(v);
|
|
|
|
|
sqlite3VdbeAddOp3(v, OP_Copy, regNewPart, pMWin->regPart, nPart-1);
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Gosub, regFlushPart, lblFlushPart);
|
|
|
|
|
VdbeComment((v, "call flush_partition"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Buffer the current row in the ephemeral table. */
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_NewRowid, pMWin->iEphCsr, regRowid);
|
|
|
|
|
sqlite3VdbeAddOp3(v, OP_Insert, pMWin->iEphCsr, regRecord, regRowid);
|
|
|
|
|
|
|
|
|
|
/* End of the input loop */
|
|
|
|
|
sqlite3WhereEnd(pWInfo);
|
|
|
|
|
|
|
|
|
|
/* Invoke "flush_partition" to deal with the final (or only) partition */
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Gosub, regFlushPart, lblFlushPart);
|
|
|
|
|
VdbeComment((v, "call flush_partition"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
** Invoke the sub-routine at regGosub (generated by code in select.c) to
|
|
|
|
|
** return the current row of Window.iEphCsr. If all window functions are
|
|
|
|
@@ -1470,45 +1407,6 @@ static void windowReturnOneRow(
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Gosub, regGosub, addrGosub);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
** Invoke the code generated by windowReturnOneRow() and, optionally, the
|
|
|
|
|
** xInverse() function for each window function, for one or more rows
|
|
|
|
|
** from the Window.iEphCsr temp table. This routine generates VM code
|
|
|
|
|
** similar to:
|
|
|
|
|
**
|
|
|
|
|
** while( regCtr>0 ){
|
|
|
|
|
** regCtr--;
|
|
|
|
|
** windowReturnOneRow()
|
|
|
|
|
** if( bInverse ){
|
|
|
|
|
** AggInverse
|
|
|
|
|
** }
|
|
|
|
|
** Next (Window.iEphCsr)
|
|
|
|
|
** }
|
|
|
|
|
*/
|
|
|
|
|
static void windowReturnRows(
|
|
|
|
|
Parse *pParse,
|
|
|
|
|
Window *pMWin, /* List of window functions */
|
|
|
|
|
int regCtr, /* Register containing number of rows */
|
|
|
|
|
int regGosub, /* Register for Gosub addrGosub */
|
|
|
|
|
int addrGosub, /* Address of sub-routine for ReturnOneRow */
|
|
|
|
|
int regInvArg, /* Array of registers for xInverse args */
|
|
|
|
|
int regInvSize /* Register containing size of partition */
|
|
|
|
|
){
|
|
|
|
|
int addr;
|
|
|
|
|
Vdbe *v = sqlite3GetVdbe(pParse);
|
|
|
|
|
windowAggFinal(pParse, pMWin, 0);
|
|
|
|
|
addr = sqlite3VdbeAddOp3(v, OP_IfPos, regCtr, sqlite3VdbeCurrentAddr(v)+2 ,1);
|
|
|
|
|
VdbeCoverage(v);
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Goto, 0, 0);
|
|
|
|
|
windowReturnOneRow(pParse, pMWin, regGosub, addrGosub);
|
|
|
|
|
if( regInvArg ){
|
|
|
|
|
windowAggStep(pParse, pMWin, pMWin->iEphCsr, 1, regInvArg, regInvSize);
|
|
|
|
|
}
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Next, pMWin->iEphCsr, addr);
|
|
|
|
|
VdbeCoverage(v);
|
|
|
|
|
sqlite3VdbeJumpHere(v, addr+1); /* The OP_Goto */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
** Generate code to set the accumulator register for each window function
|
|
|
|
|
** in the linked list passed as the second argument to NULL. And perform
|
|
|
|
@@ -1562,7 +1460,46 @@ static int windowCachePartition(Window *pMWin){
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
** regOld and regNew are each the first register in an array of size
|
|
|
|
|
** pOrderBy->nExpr. This function generates code to compare the two
|
|
|
|
|
** arrays of registers using the collation sequences and other comparison
|
|
|
|
|
** parameters specified by pOrderBy.
|
|
|
|
|
**
|
|
|
|
|
** If the two arrays are not equal, the contents of regNew is copied to
|
|
|
|
|
** regOld and control falls through. Otherwise, if the contents of the arrays
|
|
|
|
|
** are equal, an OP_Goto is executed. The address of the OP_Goto is returned.
|
|
|
|
|
*/
|
|
|
|
|
static int windowIfNewPeer(
|
|
|
|
|
Parse *pParse,
|
|
|
|
|
ExprList *pOrderBy,
|
|
|
|
|
int regNew, /* First in array of new values */
|
|
|
|
|
int regOld /* First in array of old values */
|
|
|
|
|
){
|
|
|
|
|
Vdbe *v = sqlite3GetVdbe(pParse);
|
|
|
|
|
int addr;
|
|
|
|
|
if( pOrderBy ){
|
|
|
|
|
int nVal = pOrderBy->nExpr;
|
|
|
|
|
KeyInfo *pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pOrderBy, 0, 0);
|
|
|
|
|
sqlite3VdbeAddOp3(v, OP_Compare, regOld, regNew, nVal);
|
|
|
|
|
sqlite3VdbeAppendP4(v, (void*)pKeyInfo, P4_KEYINFO);
|
|
|
|
|
addr = sqlite3VdbeAddOp3(
|
|
|
|
|
v, OP_Jump, sqlite3VdbeCurrentAddr(v)+1, 0, sqlite3VdbeCurrentAddr(v)+1
|
|
|
|
|
);
|
|
|
|
|
VdbeCoverageEqNe(v);
|
|
|
|
|
sqlite3VdbeAddOp3(v, OP_Copy, regNew, regOld, nVal-1);
|
|
|
|
|
}else{
|
|
|
|
|
addr = sqlite3VdbeAddOp0(v, OP_Goto);
|
|
|
|
|
}
|
|
|
|
|
return addr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
typedef struct WindowCodeArg WindowCodeArg;
|
|
|
|
|
typedef struct WindowCsrAndReg WindowCsrAndReg;
|
|
|
|
|
struct WindowCsrAndReg {
|
|
|
|
|
int csr;
|
|
|
|
|
int reg;
|
|
|
|
|
};
|
|
|
|
|
struct WindowCodeArg {
|
|
|
|
|
Parse *pParse;
|
|
|
|
|
Window *pMWin;
|
|
|
|
@@ -1570,23 +1507,53 @@ struct WindowCodeArg {
|
|
|
|
|
int regGosub;
|
|
|
|
|
int addrGosub;
|
|
|
|
|
int regArg;
|
|
|
|
|
|
|
|
|
|
WindowCsrAndReg start;
|
|
|
|
|
WindowCsrAndReg current;
|
|
|
|
|
WindowCsrAndReg end;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define WINDOW_RETURN_ROW 1
|
|
|
|
|
#define WINDOW_AGGINVERSE 2
|
|
|
|
|
#define WINDOW_AGGSTEP 3
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
** Generate VM code to read the window frames peer values from cursor csr into
|
|
|
|
|
** an array of registers starting at reg.
|
|
|
|
|
*/
|
|
|
|
|
static void windowReadPeerValues(
|
|
|
|
|
WindowCodeArg *p,
|
|
|
|
|
int csr,
|
|
|
|
|
int reg
|
|
|
|
|
){
|
|
|
|
|
Window *pMWin = p->pMWin;
|
|
|
|
|
ExprList *pOrderBy = pMWin->pOrderBy;
|
|
|
|
|
if( pOrderBy ){
|
|
|
|
|
Vdbe *v = sqlite3GetVdbe(p->pParse);
|
|
|
|
|
ExprList *pPart = pMWin->pPartition;
|
|
|
|
|
int iColOff = pMWin->nBufferCol + (pPart ? pPart->nExpr : 0);
|
|
|
|
|
int i;
|
|
|
|
|
for(i=0; i<pOrderBy->nExpr; i++){
|
|
|
|
|
sqlite3VdbeAddOp3(v, OP_Column, csr, iColOff+i, reg+i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int windowCodeOp(
|
|
|
|
|
WindowCodeArg *p,
|
|
|
|
|
int op,
|
|
|
|
|
int csr,
|
|
|
|
|
int regCountdown,
|
|
|
|
|
int jumpOnEof
|
|
|
|
|
){
|
|
|
|
|
int csr, reg;
|
|
|
|
|
Parse *pParse = p->pParse;
|
|
|
|
|
Window *pMWin = p->pMWin;
|
|
|
|
|
int ret = 0;
|
|
|
|
|
Vdbe *v = p->pVdbe;
|
|
|
|
|
int addrIf = 0;
|
|
|
|
|
int addrContinue = 0;
|
|
|
|
|
int addrGoto = 0;
|
|
|
|
|
int bPeer = (pMWin->eType!=TK_ROWS);
|
|
|
|
|
|
|
|
|
|
/* Special case - WINDOW_AGGINVERSE is always a no-op if the frame
|
|
|
|
|
** starts with UNBOUNDED PRECEDING. */
|
|
|
|
@@ -1599,18 +1566,27 @@ static int windowCodeOp(
|
|
|
|
|
addrIf = sqlite3VdbeAddOp3(v, OP_IfPos, regCountdown, 0, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( op==WINDOW_RETURN_ROW ){
|
|
|
|
|
windowAggFinal(pParse, pMWin, 0);
|
|
|
|
|
}
|
|
|
|
|
addrContinue = sqlite3VdbeCurrentAddr(v);
|
|
|
|
|
switch( op ){
|
|
|
|
|
case WINDOW_RETURN_ROW:
|
|
|
|
|
windowAggFinal(p->pParse, pMWin, 0);
|
|
|
|
|
windowReturnOneRow(p->pParse, pMWin, p->regGosub, p->addrGosub);
|
|
|
|
|
csr = p->current.csr;
|
|
|
|
|
reg = p->current.reg;
|
|
|
|
|
windowReturnOneRow(pParse, pMWin, p->regGosub, p->addrGosub);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case WINDOW_AGGINVERSE:
|
|
|
|
|
windowAggStep(p->pParse, pMWin, csr, 1, p->regArg, pMWin->regSize);
|
|
|
|
|
csr = p->start.csr;
|
|
|
|
|
reg = p->start.reg;
|
|
|
|
|
windowAggStep(pParse, pMWin, csr, 1, p->regArg, pMWin->regSize);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case WINDOW_AGGSTEP:
|
|
|
|
|
windowAggStep(p->pParse, pMWin, csr, 0, p->regArg, pMWin->regSize);
|
|
|
|
|
csr = p->end.csr;
|
|
|
|
|
reg = p->end.reg;
|
|
|
|
|
windowAggStep(pParse, pMWin, csr, 0, p->regArg, pMWin->regSize);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1618,17 +1594,27 @@ static int windowCodeOp(
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Next, csr, sqlite3VdbeCurrentAddr(v)+2);
|
|
|
|
|
ret = sqlite3VdbeAddOp0(v, OP_Goto);
|
|
|
|
|
}else{
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Next, csr, sqlite3VdbeCurrentAddr(v)+1);
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Next, csr, sqlite3VdbeCurrentAddr(v)+1+bPeer);
|
|
|
|
|
if( bPeer ){
|
|
|
|
|
addrGoto = sqlite3VdbeAddOp0(v, OP_Goto);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( regCountdown>0 ){
|
|
|
|
|
sqlite3VdbeJumpHere(v, addrIf);
|
|
|
|
|
if( bPeer ){
|
|
|
|
|
int addr;
|
|
|
|
|
int nReg = (pMWin->pOrderBy ? pMWin->pOrderBy->nExpr : 0);
|
|
|
|
|
int regTmp = (nReg ? sqlite3GetTempRange(pParse, nReg) : 0);
|
|
|
|
|
windowReadPeerValues(p, csr, regTmp);
|
|
|
|
|
addr = windowIfNewPeer(pParse, pMWin->pOrderBy, regTmp, reg);
|
|
|
|
|
sqlite3VdbeChangeP2(v, addr, addrContinue);
|
|
|
|
|
sqlite3ReleaseTempRange(pParse, regTmp, nReg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( addrGoto ) sqlite3VdbeJumpHere(v, addrGoto);
|
|
|
|
|
if( addrIf ) sqlite3VdbeJumpHere(v, addrIf);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
** This function - windowCodeStep() - generates the VM code that reads data
|
|
|
|
|
** from the sub-select and returns rows to the consumer. For the simplest
|
|
|
|
@@ -1646,7 +1632,7 @@ static int windowCodeOp(
|
|
|
|
|
**
|
|
|
|
|
** if( first row of partition ){
|
|
|
|
|
** Rewind(csrEnd, skipNext=1)
|
|
|
|
|
** Rewind(csrStart, skipNext=1)
|
|
|
|
|
** Rewind(start.csr, skipNext=1)
|
|
|
|
|
** Rewind(csrCurrent, skipNext=1)
|
|
|
|
|
**
|
|
|
|
|
** regEnd = <expr2> // FOLLOWING expression
|
|
|
|
@@ -1656,8 +1642,8 @@ static int windowCodeOp(
|
|
|
|
|
** Next(csrCurrent)
|
|
|
|
|
** Return one row.
|
|
|
|
|
** if( (regStart--)<0 ){
|
|
|
|
|
** Next(csrStart)
|
|
|
|
|
** AggInverse(csrStart)
|
|
|
|
|
** Next(start.csr)
|
|
|
|
|
** AggInverse(start.csr)
|
|
|
|
|
** }
|
|
|
|
|
** }
|
|
|
|
|
** }
|
|
|
|
@@ -1671,8 +1657,8 @@ static int windowCodeOp(
|
|
|
|
|
** if( eof ) break
|
|
|
|
|
** Return one row.
|
|
|
|
|
** if( (regStart--)<0 ){
|
|
|
|
|
** Next(csrStart)
|
|
|
|
|
** AggInverse(csrStart)
|
|
|
|
|
** Next(start.csr)
|
|
|
|
|
** AggInverse(start.csr)
|
|
|
|
|
** }
|
|
|
|
|
** }
|
|
|
|
|
** Empty eph table.
|
|
|
|
@@ -1704,15 +1690,12 @@ static void windowCodeStep(
|
|
|
|
|
int addrGosub
|
|
|
|
|
){
|
|
|
|
|
Window *pMWin = p->pWin;
|
|
|
|
|
ExprList *pOrderBy = pMWin->pOrderBy;
|
|
|
|
|
Vdbe *v = sqlite3GetVdbe(pParse);
|
|
|
|
|
int regFlushPart; /* Register for "Gosub flush_partition" */
|
|
|
|
|
|
|
|
|
|
int regArg;
|
|
|
|
|
int csrCurrent = pMWin->iEphCsr;
|
|
|
|
|
int csrWrite = csrCurrent+1;
|
|
|
|
|
int csrStart = csrCurrent+2;
|
|
|
|
|
int csrEnd = csrCurrent+3;
|
|
|
|
|
|
|
|
|
|
int csrWrite = pMWin->iEphCsr+1;
|
|
|
|
|
|
|
|
|
|
int iSubCsr = p->pSrc->a[0].iCursor; /* Cursor of sub-select */
|
|
|
|
|
int nSub = p->pSrc->a[0].pTab->nCol; /* Number of cols returned by sub */
|
|
|
|
@@ -1727,6 +1710,7 @@ static void windowCodeStep(
|
|
|
|
|
|
|
|
|
|
int addrShortcut = 0;
|
|
|
|
|
int addrEmpty = 0;
|
|
|
|
|
int addrPeerJump = 0;
|
|
|
|
|
|
|
|
|
|
int bCache = windowCachePartition(pMWin);
|
|
|
|
|
|
|
|
|
@@ -1736,6 +1720,8 @@ static void windowCodeStep(
|
|
|
|
|
int reg = pParse->nMem+1;
|
|
|
|
|
int regRecord = reg+nSub;
|
|
|
|
|
int regRowid = regRecord+1;
|
|
|
|
|
int regPeer = 0;
|
|
|
|
|
int regNewPeer = 0;
|
|
|
|
|
WindowCodeArg s;
|
|
|
|
|
|
|
|
|
|
memset(&s, 0, sizeof(WindowCodeArg));
|
|
|
|
@@ -1744,6 +1730,9 @@ static void windowCodeStep(
|
|
|
|
|
s.pVdbe = v;
|
|
|
|
|
s.regGosub = regGosub;
|
|
|
|
|
s.addrGosub = addrGosub;
|
|
|
|
|
s.current.csr = pMWin->iEphCsr;
|
|
|
|
|
s.start.csr = s.current.csr+2;
|
|
|
|
|
s.end.csr = s.current.csr+3;
|
|
|
|
|
|
|
|
|
|
pParse->nMem += 1 + nSub + 1;
|
|
|
|
|
|
|
|
|
@@ -1756,6 +1745,19 @@ static void windowCodeStep(
|
|
|
|
|
regEnd = ++pParse->nMem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* If this is not a "ROWS BETWEEN ..." frame, then allocate registers to
|
|
|
|
|
** store a copy of the current ORDER BY expressions. */
|
|
|
|
|
if( pMWin->eType!=TK_ROWS ){
|
|
|
|
|
int nPeer = (pOrderBy ? pOrderBy->nExpr : 0);
|
|
|
|
|
regNewPeer = reg + pMWin->nBufferCol;
|
|
|
|
|
if( pMWin->pPartition ) regNewPeer += pMWin->pPartition->nExpr;
|
|
|
|
|
|
|
|
|
|
regPeer = pParse->nMem+1; pParse->nMem += nPeer;
|
|
|
|
|
s.start.reg = pParse->nMem+1; pParse->nMem += nPeer;
|
|
|
|
|
s.current.reg = pParse->nMem+1; pParse->nMem += nPeer;
|
|
|
|
|
s.end.reg = pParse->nMem+1; pParse->nMem += nPeer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert( pMWin->eStart==TK_PRECEDING
|
|
|
|
|
|| pMWin->eStart==TK_CURRENT
|
|
|
|
|
|| pMWin->eStart==TK_FOLLOWING
|
|
|
|
@@ -1835,13 +1837,13 @@ static void windowCodeStep(
|
|
|
|
|
windowAggFinal(pParse, pMWin, 0);
|
|
|
|
|
if( bCache ){
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Rowid, csrWrite, regRowid);
|
|
|
|
|
sqlite3VdbeAddOp3(v, OP_NotExists, csrCurrent, 0, regRowid);
|
|
|
|
|
sqlite3VdbeAddOp3(v, OP_NotExists, s.current.csr, 0, regRowid);
|
|
|
|
|
windowReturnOneRow(pParse, pMWin, regGosub, addrGosub);
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Next, csrWrite, addrCacheRewind+1);
|
|
|
|
|
}else{
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Rewind, csrCurrent, 1);
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Rewind, s.current.csr, 1);
|
|
|
|
|
windowReturnOneRow(pParse, pMWin, regGosub, addrGosub);
|
|
|
|
|
sqlite3VdbeAddOp1(v, OP_ResetSorter, csrCurrent);
|
|
|
|
|
sqlite3VdbeAddOp1(v, OP_ResetSorter, s.current.csr);
|
|
|
|
|
}
|
|
|
|
|
addrShortcut = sqlite3VdbeAddOp0(v, OP_Goto);
|
|
|
|
|
sqlite3VdbeJumpHere(v, addrGe);
|
|
|
|
@@ -1852,10 +1854,20 @@ static void windowCodeStep(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( pMWin->eStart!=TK_UNBOUNDED ){
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Rewind, csrStart, 1);
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Rewind, s.start.csr, 1);
|
|
|
|
|
}
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Rewind, s.current.csr, 1);
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Rewind, s.end.csr, 1);
|
|
|
|
|
if( regPeer && pOrderBy ){
|
|
|
|
|
if( bCache ){
|
|
|
|
|
windowReadPeerValues(&s, csrWrite, regPeer);
|
|
|
|
|
}else{
|
|
|
|
|
sqlite3VdbeAddOp3(v, OP_Copy, regNewPeer, regPeer, pOrderBy->nExpr-1);
|
|
|
|
|
}
|
|
|
|
|
sqlite3VdbeAddOp3(v, OP_Copy, regPeer, s.start.reg, pOrderBy->nExpr-1);
|
|
|
|
|
sqlite3VdbeAddOp3(v, OP_Copy, regPeer, s.current.reg, pOrderBy->nExpr-1);
|
|
|
|
|
sqlite3VdbeAddOp3(v, OP_Copy, regPeer, s.end.reg, pOrderBy->nExpr-1);
|
|
|
|
|
}
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Rewind, csrCurrent, 1);
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Rewind, csrEnd, 1);
|
|
|
|
|
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Integer, 0, pMWin->regFirst);
|
|
|
|
|
addrGoto = sqlite3VdbeAddOp0(v, OP_Goto);
|
|
|
|
@@ -1864,30 +1876,39 @@ static void windowCodeStep(
|
|
|
|
|
VdbeModuleComment((pParse->pVdbe, "Begin windowCodeStep.SECOND_ROW_CODE"));
|
|
|
|
|
if( bCache ){
|
|
|
|
|
addrCacheNext = sqlite3VdbeCurrentAddr(v);
|
|
|
|
|
if( pMWin->eType!=TK_ROWS ){
|
|
|
|
|
windowReadPeerValues(&s, csrWrite, regNewPeer);
|
|
|
|
|
}
|
|
|
|
|
}else{
|
|
|
|
|
sqlite3VdbeJumpHere(v, addrIf);
|
|
|
|
|
}
|
|
|
|
|
if( regPeer ){
|
|
|
|
|
addrPeerJump = windowIfNewPeer(pParse, pOrderBy, regNewPeer, regPeer);
|
|
|
|
|
}
|
|
|
|
|
if( pMWin->eStart==TK_FOLLOWING ){
|
|
|
|
|
windowCodeOp(&s, WINDOW_AGGSTEP, csrEnd, 0, 0);
|
|
|
|
|
windowCodeOp(&s, WINDOW_AGGSTEP, 0, 0);
|
|
|
|
|
if( pMWin->eEnd!=TK_UNBOUNDED ){
|
|
|
|
|
windowCodeOp(&s, WINDOW_RETURN_ROW, csrCurrent, regEnd, 0);
|
|
|
|
|
windowCodeOp(&s, WINDOW_AGGINVERSE, csrStart, regStart, 0);
|
|
|
|
|
windowCodeOp(&s, WINDOW_RETURN_ROW, regEnd, 0);
|
|
|
|
|
windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0);
|
|
|
|
|
}
|
|
|
|
|
}else
|
|
|
|
|
if( pMWin->eEnd==TK_PRECEDING ){
|
|
|
|
|
windowCodeOp(&s, WINDOW_AGGSTEP, csrEnd, regEnd, 0);
|
|
|
|
|
windowCodeOp(&s, WINDOW_RETURN_ROW, csrCurrent, 0, 0);
|
|
|
|
|
windowCodeOp(&s, WINDOW_AGGINVERSE, csrStart, regStart, 0);
|
|
|
|
|
windowCodeOp(&s, WINDOW_AGGSTEP, regEnd, 0);
|
|
|
|
|
windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 0);
|
|
|
|
|
windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0);
|
|
|
|
|
}else{
|
|
|
|
|
int addr;
|
|
|
|
|
windowCodeOp(&s, WINDOW_AGGSTEP, csrEnd, 0, 0);
|
|
|
|
|
windowCodeOp(&s, WINDOW_AGGSTEP, 0, 0);
|
|
|
|
|
if( pMWin->eEnd!=TK_UNBOUNDED ){
|
|
|
|
|
if( regEnd ) addr = sqlite3VdbeAddOp3(v, OP_IfPos, regEnd, 0, 1);
|
|
|
|
|
windowCodeOp(&s, WINDOW_RETURN_ROW, csrCurrent, 0, 0);
|
|
|
|
|
windowCodeOp(&s, WINDOW_AGGINVERSE, csrStart, regStart, 0);
|
|
|
|
|
windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 0);
|
|
|
|
|
windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0);
|
|
|
|
|
if( regEnd ) sqlite3VdbeJumpHere(v, addr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if( addrPeerJump ){
|
|
|
|
|
sqlite3VdbeJumpHere(v, addrPeerJump);
|
|
|
|
|
}
|
|
|
|
|
VdbeModuleComment((pParse->pVdbe, "End windowCodeStep.SECOND_ROW_CODE"));
|
|
|
|
|
|
|
|
|
|
/* End of the main input loop */
|
|
|
|
@@ -1909,38 +1930,38 @@ static void windowCodeStep(
|
|
|
|
|
VdbeModuleComment((pParse->pVdbe, "Begin windowCodeStep.FLUSH_CODE"));
|
|
|
|
|
addrEmpty = sqlite3VdbeAddOp1(v, OP_Rewind, csrWrite);
|
|
|
|
|
if( pMWin->eEnd==TK_PRECEDING ){
|
|
|
|
|
windowCodeOp(&s, WINDOW_AGGSTEP, csrEnd, regEnd, 0);
|
|
|
|
|
windowCodeOp(&s, WINDOW_RETURN_ROW, csrCurrent, 0, 0);
|
|
|
|
|
windowCodeOp(&s, WINDOW_AGGSTEP, regEnd, 0);
|
|
|
|
|
windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 0);
|
|
|
|
|
}else if( pMWin->eStart==TK_FOLLOWING ){
|
|
|
|
|
int addrStart;
|
|
|
|
|
int addrBreak1;
|
|
|
|
|
int addrBreak2;
|
|
|
|
|
int addrBreak3;
|
|
|
|
|
windowCodeOp(&s, WINDOW_AGGSTEP, csrEnd, 0, 0);
|
|
|
|
|
windowCodeOp(&s, WINDOW_AGGSTEP, 0, 0);
|
|
|
|
|
if( pMWin->eEnd==TK_UNBOUNDED ){
|
|
|
|
|
addrStart = sqlite3VdbeCurrentAddr(v);
|
|
|
|
|
addrBreak1 = windowCodeOp(&s, WINDOW_RETURN_ROW, csrCurrent, regStart, 1);
|
|
|
|
|
addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, csrStart, 0, 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, csrCurrent, regEnd, 1);
|
|
|
|
|
addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, csrStart, 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);
|
|
|
|
|
addrStart = sqlite3VdbeCurrentAddr(v);
|
|
|
|
|
addrBreak3 = windowCodeOp(&s, WINDOW_RETURN_ROW, csrCurrent, 0, 1);
|
|
|
|
|
addrBreak3 = windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 1);
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Goto, 0, addrStart);
|
|
|
|
|
sqlite3VdbeJumpHere(v, addrBreak1);
|
|
|
|
|
sqlite3VdbeJumpHere(v, addrBreak3);
|
|
|
|
|
}else{
|
|
|
|
|
int addrBreak;
|
|
|
|
|
int addrStart;
|
|
|
|
|
windowCodeOp(&s, WINDOW_AGGSTEP, csrEnd, 0, 0);
|
|
|
|
|
windowCodeOp(&s, WINDOW_AGGSTEP, 0, 0);
|
|
|
|
|
addrStart = sqlite3VdbeCurrentAddr(v);
|
|
|
|
|
addrBreak = windowCodeOp(&s, WINDOW_RETURN_ROW, csrCurrent, 0, 1);
|
|
|
|
|
windowCodeOp(&s, WINDOW_AGGINVERSE, csrStart, regStart, 0);
|
|
|
|
|
addrBreak = windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 1);
|
|
|
|
|
windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0);
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Goto, 0, addrStart);
|
|
|
|
|
sqlite3VdbeJumpHere(v, addrBreak);
|
|
|
|
|
}
|
|
|
|
@@ -1948,7 +1969,7 @@ static void windowCodeStep(
|
|
|
|
|
sqlite3VdbeJumpHere(v, addrEmpty);
|
|
|
|
|
|
|
|
|
|
if( bCache && addrShortcut>0 ) sqlite3VdbeJumpHere(v, addrShortcut);
|
|
|
|
|
sqlite3VdbeAddOp1(v, OP_ResetSorter, csrCurrent);
|
|
|
|
|
sqlite3VdbeAddOp1(v, OP_ResetSorter, s.current.csr);
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Integer, 0, pMWin->regSize);
|
|
|
|
|
if( bCache==0 ) sqlite3VdbeAddOp2(v, OP_Integer, 1, pMWin->regFirst);
|
|
|
|
|
VdbeModuleComment((pParse->pVdbe, "End windowCodeStep.FLUSH_CODE"));
|
|
|
|
@@ -1958,370 +1979,6 @@ static void windowCodeStep(
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
** This function does the work of sqlite3WindowCodeStep() for cases that
|
|
|
|
|
** would normally be handled by windowCodeDefaultStep() when there are
|
|
|
|
|
** one or more built-in window-functions that require the entire partition
|
|
|
|
|
** to be cached in a temp table before any rows can be returned. Additionally.
|
|
|
|
|
** "RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING" is always handled by
|
|
|
|
|
** this function.
|
|
|
|
|
**
|
|
|
|
|
** Pseudo-code corresponding to the VM code generated by this function
|
|
|
|
|
** for each type of window follows.
|
|
|
|
|
**
|
|
|
|
|
** RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
|
|
|
|
|
**
|
|
|
|
|
** flush_partition:
|
|
|
|
|
** Once {
|
|
|
|
|
** OpenDup (iEphCsr -> csrLead)
|
|
|
|
|
** }
|
|
|
|
|
** Integer ctr 0
|
|
|
|
|
** foreach row (csrLead){
|
|
|
|
|
** if( new peer ){
|
|
|
|
|
** AggFinal (xValue)
|
|
|
|
|
** for(i=0; i<ctr; i++){
|
|
|
|
|
** Gosub addrGosub
|
|
|
|
|
** Next iEphCsr
|
|
|
|
|
** }
|
|
|
|
|
** Integer ctr 0
|
|
|
|
|
** }
|
|
|
|
|
** AggStep (csrLead)
|
|
|
|
|
** Incr ctr
|
|
|
|
|
** }
|
|
|
|
|
**
|
|
|
|
|
** AggFinal (xFinalize)
|
|
|
|
|
** for(i=0; i<ctr; i++){
|
|
|
|
|
** Gosub addrGosub
|
|
|
|
|
** Next iEphCsr
|
|
|
|
|
** }
|
|
|
|
|
**
|
|
|
|
|
** ResetSorter (csr)
|
|
|
|
|
** Return
|
|
|
|
|
**
|
|
|
|
|
** ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
|
|
|
|
|
**
|
|
|
|
|
** As above, except that the "if( new peer )" branch is always taken.
|
|
|
|
|
**
|
|
|
|
|
** RANGE BETWEEN CURRENT ROW AND CURRENT ROW
|
|
|
|
|
**
|
|
|
|
|
** As above, except that each of the for() loops becomes:
|
|
|
|
|
**
|
|
|
|
|
** for(i=0; i<ctr; i++){
|
|
|
|
|
** Gosub addrGosub
|
|
|
|
|
** AggInverse (iEphCsr)
|
|
|
|
|
** Next iEphCsr
|
|
|
|
|
** }
|
|
|
|
|
**
|
|
|
|
|
** RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
|
|
|
|
|
**
|
|
|
|
|
** flush_partition:
|
|
|
|
|
** Once {
|
|
|
|
|
** OpenDup (iEphCsr -> csrLead)
|
|
|
|
|
** }
|
|
|
|
|
** foreach row (csrLead) {
|
|
|
|
|
** AggStep (csrLead)
|
|
|
|
|
** }
|
|
|
|
|
** foreach row (iEphCsr) {
|
|
|
|
|
** Gosub addrGosub
|
|
|
|
|
** }
|
|
|
|
|
**
|
|
|
|
|
** RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
|
|
|
|
|
**
|
|
|
|
|
** flush_partition:
|
|
|
|
|
** Once {
|
|
|
|
|
** OpenDup (iEphCsr -> csrLead)
|
|
|
|
|
** }
|
|
|
|
|
** foreach row (csrLead){
|
|
|
|
|
** AggStep (csrLead)
|
|
|
|
|
** }
|
|
|
|
|
** Rewind (csrLead)
|
|
|
|
|
** Integer ctr 0
|
|
|
|
|
** foreach row (csrLead){
|
|
|
|
|
** if( new peer ){
|
|
|
|
|
** AggFinal (xValue)
|
|
|
|
|
** for(i=0; i<ctr; i++){
|
|
|
|
|
** Gosub addrGosub
|
|
|
|
|
** AggInverse (iEphCsr)
|
|
|
|
|
** Next iEphCsr
|
|
|
|
|
** }
|
|
|
|
|
** Integer ctr 0
|
|
|
|
|
** }
|
|
|
|
|
** Incr ctr
|
|
|
|
|
** }
|
|
|
|
|
**
|
|
|
|
|
** AggFinal (xFinalize)
|
|
|
|
|
** for(i=0; i<ctr; i++){
|
|
|
|
|
** Gosub addrGosub
|
|
|
|
|
** Next iEphCsr
|
|
|
|
|
** }
|
|
|
|
|
**
|
|
|
|
|
** ResetSorter (csr)
|
|
|
|
|
** Return
|
|
|
|
|
*/
|
|
|
|
|
static void windowCodeCacheStep(
|
|
|
|
|
Parse *pParse,
|
|
|
|
|
Select *p,
|
|
|
|
|
WhereInfo *pWInfo,
|
|
|
|
|
int regGosub,
|
|
|
|
|
int addrGosub
|
|
|
|
|
){
|
|
|
|
|
Window *pMWin = p->pWin;
|
|
|
|
|
Vdbe *v = sqlite3GetVdbe(pParse);
|
|
|
|
|
int k;
|
|
|
|
|
int addr;
|
|
|
|
|
ExprList *pPart = pMWin->pPartition;
|
|
|
|
|
ExprList *pOrderBy = pMWin->pOrderBy;
|
|
|
|
|
int nPeer = pOrderBy ? pOrderBy->nExpr : 0;
|
|
|
|
|
int regNewPeer;
|
|
|
|
|
|
|
|
|
|
int addrGoto; /* Address of Goto used to jump flush_par.. */
|
|
|
|
|
int addrNext; /* Jump here for next iteration of loop */
|
|
|
|
|
int regFlushPart;
|
|
|
|
|
int lblFlushPart;
|
|
|
|
|
int csrLead;
|
|
|
|
|
int regCtr;
|
|
|
|
|
int regArg; /* Register array to martial function args */
|
|
|
|
|
int regSize;
|
|
|
|
|
int lblEmpty;
|
|
|
|
|
int bReverse = pMWin->pOrderBy && pMWin->eStart==TK_CURRENT
|
|
|
|
|
&& pMWin->eEnd==TK_UNBOUNDED;
|
|
|
|
|
|
|
|
|
|
assert( (pMWin->eStart==TK_UNBOUNDED && pMWin->eEnd==TK_CURRENT)
|
|
|
|
|
|| (pMWin->eStart==TK_UNBOUNDED && pMWin->eEnd==TK_UNBOUNDED)
|
|
|
|
|
|| (pMWin->eStart==TK_CURRENT && pMWin->eEnd==TK_CURRENT)
|
|
|
|
|
|| (pMWin->eStart==TK_CURRENT && pMWin->eEnd==TK_UNBOUNDED)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
lblEmpty = sqlite3VdbeMakeLabel(pParse);
|
|
|
|
|
regNewPeer = pParse->nMem+1;
|
|
|
|
|
pParse->nMem += nPeer;
|
|
|
|
|
|
|
|
|
|
/* Allocate register and label for the "flush_partition" sub-routine. */
|
|
|
|
|
regFlushPart = ++pParse->nMem;
|
|
|
|
|
lblFlushPart = sqlite3VdbeMakeLabel(pParse);
|
|
|
|
|
|
|
|
|
|
csrLead = pParse->nTab++;
|
|
|
|
|
regCtr = ++pParse->nMem;
|
|
|
|
|
|
|
|
|
|
windowPartitionCache(pParse, p, pWInfo, regFlushPart, lblFlushPart, ®Size);
|
|
|
|
|
addrGoto = sqlite3VdbeAddOp0(v, OP_Goto);
|
|
|
|
|
|
|
|
|
|
/* Start of "flush_partition" */
|
|
|
|
|
sqlite3VdbeResolveLabel(v, lblFlushPart);
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Once, 0, sqlite3VdbeCurrentAddr(v)+2);
|
|
|
|
|
VdbeCoverage(v);
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_OpenDup, csrLead, pMWin->iEphCsr);
|
|
|
|
|
|
|
|
|
|
/* Initialize the accumulator register for each window function to NULL */
|
|
|
|
|
regArg = windowInitAccum(pParse, pMWin);
|
|
|
|
|
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Integer, 0, regCtr);
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Rewind, csrLead, lblEmpty);
|
|
|
|
|
VdbeCoverage(v);
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Rewind, pMWin->iEphCsr, lblEmpty);
|
|
|
|
|
VdbeCoverageNeverTaken(v);
|
|
|
|
|
|
|
|
|
|
if( bReverse ){
|
|
|
|
|
int addr2 = sqlite3VdbeCurrentAddr(v);
|
|
|
|
|
windowAggStep(pParse, pMWin, csrLead, 0, regArg, regSize);
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Next, csrLead, addr2);
|
|
|
|
|
VdbeCoverage(v);
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Rewind, csrLead, lblEmpty);
|
|
|
|
|
VdbeCoverageNeverTaken(v);
|
|
|
|
|
}
|
|
|
|
|
addrNext = sqlite3VdbeCurrentAddr(v);
|
|
|
|
|
|
|
|
|
|
if( pOrderBy && (pMWin->eEnd==TK_CURRENT || pMWin->eStart==TK_CURRENT) ){
|
|
|
|
|
int bCurrent = (pMWin->eStart==TK_CURRENT);
|
|
|
|
|
int addrJump = 0; /* Address of OP_Jump below */
|
|
|
|
|
if( pMWin->eType==TK_RANGE ){
|
|
|
|
|
int iOff = pMWin->nBufferCol + (pPart ? pPart->nExpr : 0);
|
|
|
|
|
int regPeer = pMWin->regPart + (pPart ? pPart->nExpr : 0);
|
|
|
|
|
KeyInfo *pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pOrderBy, 0, 0);
|
|
|
|
|
for(k=0; k<nPeer; k++){
|
|
|
|
|
sqlite3VdbeAddOp3(v, OP_Column, csrLead, iOff+k, regNewPeer+k);
|
|
|
|
|
}
|
|
|
|
|
addr = sqlite3VdbeAddOp3(v, OP_Compare, regNewPeer, regPeer, nPeer);
|
|
|
|
|
sqlite3VdbeAppendP4(v, (void*)pKeyInfo, P4_KEYINFO);
|
|
|
|
|
addrJump = sqlite3VdbeAddOp3(v, OP_Jump, addr+2, 0, addr+2);
|
|
|
|
|
VdbeCoverage(v);
|
|
|
|
|
sqlite3VdbeAddOp3(v, OP_Copy, regNewPeer, regPeer, nPeer-1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
windowReturnRows(pParse, pMWin, regCtr, regGosub, addrGosub,
|
|
|
|
|
(bCurrent ? regArg : 0), (bCurrent ? regSize : 0)
|
|
|
|
|
);
|
|
|
|
|
if( addrJump ) sqlite3VdbeJumpHere(v, addrJump);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( bReverse==0 ){
|
|
|
|
|
windowAggStep(pParse, pMWin, csrLead, 0, regArg, regSize);
|
|
|
|
|
}
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_AddImm, regCtr, 1);
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Next, csrLead, addrNext);
|
|
|
|
|
VdbeCoverage(v);
|
|
|
|
|
|
|
|
|
|
windowReturnRows(pParse, pMWin, regCtr, regGosub, addrGosub, 0, 0);
|
|
|
|
|
|
|
|
|
|
sqlite3VdbeResolveLabel(v, lblEmpty);
|
|
|
|
|
sqlite3VdbeAddOp1(v, OP_ResetSorter, pMWin->iEphCsr);
|
|
|
|
|
sqlite3VdbeAddOp1(v, OP_Return, regFlushPart);
|
|
|
|
|
|
|
|
|
|
/* Jump to here to skip over flush_partition */
|
|
|
|
|
sqlite3VdbeJumpHere(v, addrGoto);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
** RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
|
|
|
|
|
**
|
|
|
|
|
** ...
|
|
|
|
|
** if( new partition ){
|
|
|
|
|
** AggFinal (xFinalize)
|
|
|
|
|
** Gosub addrGosub
|
|
|
|
|
** ResetSorter eph-table
|
|
|
|
|
** }
|
|
|
|
|
** else if( new peer ){
|
|
|
|
|
** AggFinal (xValue)
|
|
|
|
|
** Gosub addrGosub
|
|
|
|
|
** ResetSorter eph-table
|
|
|
|
|
** }
|
|
|
|
|
** AggStep
|
|
|
|
|
** Insert (record into eph-table)
|
|
|
|
|
** sqlite3WhereEnd()
|
|
|
|
|
** AggFinal (xFinalize)
|
|
|
|
|
** Gosub addrGosub
|
|
|
|
|
**
|
|
|
|
|
** RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
|
|
|
|
|
**
|
|
|
|
|
** As above, except take no action for a "new peer". Invoke
|
|
|
|
|
** the sub-routine once only for each partition.
|
|
|
|
|
**
|
|
|
|
|
** RANGE BETWEEN CURRENT ROW AND CURRENT ROW
|
|
|
|
|
**
|
|
|
|
|
** As above, except that the "new peer" condition is handled in the
|
|
|
|
|
** same way as "new partition" (so there is no "else if" block).
|
|
|
|
|
**
|
|
|
|
|
** ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
|
|
|
|
|
**
|
|
|
|
|
** As above, except assume every row is a "new peer".
|
|
|
|
|
*/
|
|
|
|
|
static void windowCodeDefaultStep(
|
|
|
|
|
Parse *pParse,
|
|
|
|
|
Select *p,
|
|
|
|
|
WhereInfo *pWInfo,
|
|
|
|
|
int regGosub,
|
|
|
|
|
int addrGosub
|
|
|
|
|
){
|
|
|
|
|
Window *pMWin = p->pWin;
|
|
|
|
|
Vdbe *v = sqlite3GetVdbe(pParse);
|
|
|
|
|
int k;
|
|
|
|
|
int iSubCsr = p->pSrc->a[0].iCursor;
|
|
|
|
|
int nSub = p->pSrc->a[0].pTab->nCol;
|
|
|
|
|
int reg = pParse->nMem+1;
|
|
|
|
|
int regRecord = reg+nSub;
|
|
|
|
|
int regRowid = regRecord+1;
|
|
|
|
|
int addr;
|
|
|
|
|
ExprList *pPart = pMWin->pPartition;
|
|
|
|
|
ExprList *pOrderBy = pMWin->pOrderBy;
|
|
|
|
|
|
|
|
|
|
assert( pMWin->eType==TK_RANGE
|
|
|
|
|
|| (pMWin->eStart==TK_UNBOUNDED && pMWin->eEnd==TK_CURRENT)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert( (pMWin->eStart==TK_UNBOUNDED && pMWin->eEnd==TK_CURRENT)
|
|
|
|
|
|| (pMWin->eStart==TK_UNBOUNDED && pMWin->eEnd==TK_UNBOUNDED)
|
|
|
|
|
|| (pMWin->eStart==TK_CURRENT && pMWin->eEnd==TK_CURRENT)
|
|
|
|
|
|| (pMWin->eStart==TK_CURRENT && pMWin->eEnd==TK_UNBOUNDED && !pOrderBy)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if( pMWin->eEnd==TK_UNBOUNDED ){
|
|
|
|
|
pOrderBy = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pParse->nMem += nSub + 2;
|
|
|
|
|
|
|
|
|
|
/* Load the individual column values of the row returned by
|
|
|
|
|
** the sub-select into an array of registers. */
|
|
|
|
|
for(k=0; k<nSub; k++){
|
|
|
|
|
sqlite3VdbeAddOp3(v, OP_Column, iSubCsr, k, reg+k);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Check if this is the start of a new partition or peer group. */
|
|
|
|
|
if( pPart || pOrderBy ){
|
|
|
|
|
int nPart = (pPart ? pPart->nExpr : 0);
|
|
|
|
|
int addrGoto = 0;
|
|
|
|
|
int addrJump = 0;
|
|
|
|
|
int nPeer = (pOrderBy ? pOrderBy->nExpr : 0);
|
|
|
|
|
|
|
|
|
|
if( pPart ){
|
|
|
|
|
int regNewPart = reg + pMWin->nBufferCol;
|
|
|
|
|
KeyInfo *pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pPart, 0, 0);
|
|
|
|
|
addr = sqlite3VdbeAddOp3(v, OP_Compare, regNewPart, pMWin->regPart,nPart);
|
|
|
|
|
sqlite3VdbeAppendP4(v, (void*)pKeyInfo, P4_KEYINFO);
|
|
|
|
|
addrJump = sqlite3VdbeAddOp3(v, OP_Jump, addr+2, 0, addr+2);
|
|
|
|
|
VdbeCoverageEqNe(v);
|
|
|
|
|
windowAggFinal(pParse, pMWin, 1);
|
|
|
|
|
if( pOrderBy ){
|
|
|
|
|
addrGoto = sqlite3VdbeAddOp0(v, OP_Goto);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( pOrderBy ){
|
|
|
|
|
int regNewPeer = reg + pMWin->nBufferCol + nPart;
|
|
|
|
|
int regPeer = pMWin->regPart + nPart;
|
|
|
|
|
|
|
|
|
|
if( addrJump ) sqlite3VdbeJumpHere(v, addrJump);
|
|
|
|
|
if( pMWin->eType==TK_RANGE ){
|
|
|
|
|
KeyInfo *pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pOrderBy, 0, 0);
|
|
|
|
|
addr = sqlite3VdbeAddOp3(v, OP_Compare, regNewPeer, regPeer, nPeer);
|
|
|
|
|
sqlite3VdbeAppendP4(v, (void*)pKeyInfo, P4_KEYINFO);
|
|
|
|
|
addrJump = sqlite3VdbeAddOp3(v, OP_Jump, addr+2, 0, addr+2);
|
|
|
|
|
VdbeCoverage(v);
|
|
|
|
|
}else{
|
|
|
|
|
addrJump = 0;
|
|
|
|
|
}
|
|
|
|
|
windowAggFinal(pParse, pMWin, pMWin->eStart==TK_CURRENT);
|
|
|
|
|
if( addrGoto ) sqlite3VdbeJumpHere(v, addrGoto);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Rewind, pMWin->iEphCsr,sqlite3VdbeCurrentAddr(v)+3);
|
|
|
|
|
VdbeCoverage(v);
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Gosub, regGosub, addrGosub);
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Next, pMWin->iEphCsr, sqlite3VdbeCurrentAddr(v)-1);
|
|
|
|
|
VdbeCoverage(v);
|
|
|
|
|
|
|
|
|
|
sqlite3VdbeAddOp1(v, OP_ResetSorter, pMWin->iEphCsr);
|
|
|
|
|
sqlite3VdbeAddOp3(
|
|
|
|
|
v, OP_Copy, reg+pMWin->nBufferCol, pMWin->regPart, nPart+nPeer-1
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if( addrJump ) sqlite3VdbeJumpHere(v, addrJump);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Invoke step function for window functions */
|
|
|
|
|
windowAggStep(pParse, pMWin, -1, 0, reg, 0);
|
|
|
|
|
|
|
|
|
|
/* Buffer the current row in the ephemeral table. */
|
|
|
|
|
if( pMWin->nBufferCol>0 ){
|
|
|
|
|
sqlite3VdbeAddOp3(v, OP_MakeRecord, reg, pMWin->nBufferCol, regRecord);
|
|
|
|
|
}else{
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Blob, 0, regRecord);
|
|
|
|
|
sqlite3VdbeAppendP4(v, (void*)"", 0);
|
|
|
|
|
}
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_NewRowid, pMWin->iEphCsr, regRowid);
|
|
|
|
|
sqlite3VdbeAddOp3(v, OP_Insert, pMWin->iEphCsr, regRecord, regRowid);
|
|
|
|
|
|
|
|
|
|
/* End the database scan loop. */
|
|
|
|
|
sqlite3WhereEnd(pWInfo);
|
|
|
|
|
|
|
|
|
|
windowAggFinal(pParse, pMWin, 1);
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Rewind, pMWin->iEphCsr,sqlite3VdbeCurrentAddr(v)+3);
|
|
|
|
|
VdbeCoverage(v);
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Gosub, regGosub, addrGosub);
|
|
|
|
|
sqlite3VdbeAddOp2(v, OP_Next, pMWin->iEphCsr, sqlite3VdbeCurrentAddr(v)-1);
|
|
|
|
|
VdbeCoverage(v);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
** Allocate and return a duplicate of the Window object indicated by the
|
|
|
|
@@ -2381,78 +2038,9 @@ void sqlite3WindowCodeStep(
|
|
|
|
|
int regGosub, /* Register for OP_Gosub */
|
|
|
|
|
int addrGosub /* OP_Gosub here to return each row */
|
|
|
|
|
){
|
|
|
|
|
Window *pMWin = p->pWin;
|
|
|
|
|
|
|
|
|
|
/* There are three different functions that may be used to do the work
|
|
|
|
|
** of this one, depending on the window frame and the specific built-in
|
|
|
|
|
** window functions used (if any).
|
|
|
|
|
**
|
|
|
|
|
** windowCodeRowExprStep() handles all "ROWS" window frames, except for:
|
|
|
|
|
**
|
|
|
|
|
** ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
|
|
|
|
|
**
|
|
|
|
|
** The exception is because windowCodeRowExprStep() implements all window
|
|
|
|
|
** frame types by caching the entire partition in a temp table, and
|
|
|
|
|
** "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW" is easy enough to
|
|
|
|
|
** implement without such a cache.
|
|
|
|
|
**
|
|
|
|
|
** windowCodeCacheStep() is used for:
|
|
|
|
|
**
|
|
|
|
|
** RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
|
|
|
|
|
**
|
|
|
|
|
** It is also used for anything not handled by windowCodeRowExprStep()
|
|
|
|
|
** that invokes a built-in window function that requires the entire
|
|
|
|
|
** partition to be cached in a temp table before any rows are returned
|
|
|
|
|
** (e.g. nth_value() or percent_rank()).
|
|
|
|
|
**
|
|
|
|
|
** Finally, assuming there is no built-in window function that requires
|
|
|
|
|
** the partition to be cached, windowCodeDefaultStep() is used for:
|
|
|
|
|
**
|
|
|
|
|
** RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
|
|
|
|
|
** RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
|
|
|
|
|
** RANGE BETWEEN CURRENT ROW AND CURRENT ROW
|
|
|
|
|
** ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
|
|
|
|
|
**
|
|
|
|
|
** windowCodeDefaultStep() is the only one of the three functions that
|
|
|
|
|
** does not cache each partition in a temp table before beginning to
|
|
|
|
|
** return rows.
|
|
|
|
|
*/
|
|
|
|
|
if( pMWin->eType==TK_ROWS ){
|
|
|
|
|
VdbeModuleComment((pParse->pVdbe, "Begin windowCodeStep()"));
|
|
|
|
|
windowCodeStep(pParse, p, pWInfo, regGosub, addrGosub);
|
|
|
|
|
VdbeModuleComment((pParse->pVdbe, "End windowCodeStep()"));
|
|
|
|
|
}else{
|
|
|
|
|
Window *pWin;
|
|
|
|
|
int bCache = 0; /* True to use CacheStep() */
|
|
|
|
|
|
|
|
|
|
if( pMWin->eStart==TK_CURRENT && pMWin->eEnd==TK_UNBOUNDED ){
|
|
|
|
|
bCache = 1;
|
|
|
|
|
}else{
|
|
|
|
|
for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
|
|
|
|
|
FuncDef *pFunc = pWin->pFunc;
|
|
|
|
|
if( (pFunc->funcFlags & SQLITE_FUNC_WINDOW_SIZE)
|
|
|
|
|
|| (pFunc->zName==nth_valueName)
|
|
|
|
|
|| (pFunc->zName==first_valueName)
|
|
|
|
|
|| (pFunc->zName==leadName)
|
|
|
|
|
|| (pFunc->zName==lagName)
|
|
|
|
|
){
|
|
|
|
|
bCache = 1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Otherwise, call windowCodeDefaultStep(). */
|
|
|
|
|
if( bCache ){
|
|
|
|
|
VdbeModuleComment((pParse->pVdbe, "Begin CacheStep()"));
|
|
|
|
|
windowCodeCacheStep(pParse, p, pWInfo, regGosub, addrGosub);
|
|
|
|
|
VdbeModuleComment((pParse->pVdbe, "End CacheStep()"));
|
|
|
|
|
}else{
|
|
|
|
|
VdbeModuleComment((pParse->pVdbe, "Begin DefaultStep()"));
|
|
|
|
|
windowCodeDefaultStep(pParse, p, pWInfo, regGosub, addrGosub);
|
|
|
|
|
VdbeModuleComment((pParse->pVdbe, "End DefaultStep()"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
VdbeModuleComment((pParse->pVdbe, "Begin windowCodeStep()"));
|
|
|
|
|
windowCodeStep(pParse, p, pWInfo, regGosub, addrGosub);
|
|
|
|
|
VdbeModuleComment((pParse->pVdbe, "End windowCodeStep()"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif /* SQLITE_OMIT_WINDOWFUNC */
|
|
|
|
|