diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/parse.y | 32 | ||||
-rw-r--r-- | src/sqliteInt.h | 7 | ||||
-rw-r--r-- | src/window.c | 1332 |
3 files changed, 589 insertions, 782 deletions
diff --git a/src/parse.y b/src/parse.y index 1b89e69fd..f8701534e 100644 --- a/src/parse.y +++ b/src/parse.y @@ -1633,13 +1633,14 @@ wqlist(A) ::= wqlist(A) COMMA nm(X) eidlist_opt(Y) AS LP select(Z) RP. { windowdefn_list(A) ::= windowdefn(Z). { A = Z; } windowdefn_list(A) ::= windowdefn_list(Y) COMMA windowdefn(Z). { assert( Z!=0 ); + sqlite3WindowChain(pParse, Z, Y); Z->pNextWin = Y; A = Z; } %type windowdefn {Window*} %destructor windowdefn {sqlite3WindowDelete(pParse->db, $$);} -windowdefn(A) ::= nm(X) AS window(Y). { +windowdefn(A) ::= nm(X) AS LP window(Y) RP. { if( ALWAYS(Y) ){ Y->zName = sqlite3DbStrNDup(pParse->db, X.z, X.n); } @@ -1667,19 +1668,27 @@ windowdefn(A) ::= nm(X) AS window(Y). { %type frame_bound_e {struct FrameBound} %destructor frame_bound_e {sqlite3ExprDelete(pParse->db, $$.pExpr);} -window(A) ::= LP part_opt(X) orderby_opt(Y) frame_opt(Z) RP. { +window(A) ::= PARTITION BY nexprlist(X) orderby_opt(Y) frame_opt(Z). { + A = sqlite3WindowAssemble(pParse, Z, X, Y, 0); +} +window(A) ::= nm(W) PARTITION BY nexprlist(X) orderby_opt(Y) frame_opt(Z). { + A = sqlite3WindowAssemble(pParse, Z, X, Y, &W); +} +window(A) ::= ORDER BY sortlist(Y) frame_opt(Z). { + A = sqlite3WindowAssemble(pParse, Z, 0, Y, 0); +} +window(A) ::= nm(W) ORDER BY sortlist(Y) frame_opt(Z). { + A = sqlite3WindowAssemble(pParse, Z, 0, Y, &W); +} +window(A) ::= frame_opt(Z). { A = Z; - if( ALWAYS(A) ){ - A->pPartition = X; - A->pOrderBy = Y; - } } - -part_opt(A) ::= PARTITION BY nexprlist(X). { A = X; } -part_opt(A) ::= . { A = 0; } +window(A) ::= nm(W) frame_opt(Z). { + A = sqlite3WindowAssemble(pParse, Z, 0, 0, &W); +} frame_opt(A) ::= . { - A = sqlite3WindowAlloc(pParse, TK_RANGE, TK_UNBOUNDED, 0, TK_CURRENT, 0); + A = sqlite3WindowAlloc(pParse, 0, TK_UNBOUNDED, 0, TK_CURRENT, 0); } frame_opt(A) ::= range_or_rows(X) frame_bound_s(Y). { A = sqlite3WindowAlloc(pParse, X, Y.eType, Y.pExpr, TK_CURRENT, 0); @@ -1690,6 +1699,7 @@ frame_opt(A) ::= range_or_rows(X) BETWEEN frame_bound_s(Y) AND frame_bound_e(Z). range_or_rows(A) ::= RANGE. { A = TK_RANGE; } range_or_rows(A) ::= ROWS. { A = TK_ROWS; } +range_or_rows(A) ::= GROUPS. { A = TK_GROUPS;} frame_bound_s(A) ::= frame_bound(X). { A = X; } @@ -1707,7 +1717,7 @@ window_clause(A) ::= WINDOW windowdefn_list(B). { A = B; } %type over_clause {Window*} %destructor over_clause {sqlite3WindowDelete(pParse->db, $$);} -over_clause(A) ::= filter_opt(W) OVER window(Z). { +over_clause(A) ::= filter_opt(W) OVER LP window(Z) RP. { A = Z; assert( A!=0 ); A->pFilter = W; diff --git a/src/sqliteInt.h b/src/sqliteInt.h index f12e2b622..5c7cac4f2 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -3555,11 +3555,13 @@ struct TreeView { */ struct Window { char *zName; /* Name of window (may be NULL) */ + char *zBase; /* Name of base window for chaining (may be NULL) */ ExprList *pPartition; /* PARTITION BY clause */ ExprList *pOrderBy; /* ORDER BY clause */ u8 eType; /* TK_RANGE or TK_ROWS */ u8 eStart; /* UNBOUNDED, CURRENT, PRECEDING or FOLLOWING */ u8 eEnd; /* UNBOUNDED, CURRENT, PRECEDING or FOLLOWING */ + u8 bImplicitFrame; /* True if frame was implicitly specified */ Expr *pStart; /* Expression for "<expr> PRECEDING" */ Expr *pEnd; /* Expression for "<expr> FOLLOWING" */ Window *pNextWin; /* Next window function belonging to this SELECT */ @@ -3575,6 +3577,9 @@ struct Window { Expr *pOwner; /* Expression object this window is attached to */ int nBufferCol; /* Number of columns in buffer table */ int iArgCol; /* Offset of first argument for this function */ + + int regFirst; + int regSize; }; #ifndef SQLITE_OMIT_WINDOWFUNC @@ -3591,6 +3596,8 @@ void sqlite3WindowUpdate(Parse*, Window*, Window*, FuncDef*); Window *sqlite3WindowDup(sqlite3 *db, Expr *pOwner, Window *p); Window *sqlite3WindowListDup(sqlite3 *db, Window *p); void sqlite3WindowFunctions(void); +void sqlite3WindowChain(Parse*, Window*, Window*); +Window *sqlite3WindowAssemble(Parse*, Window*, ExprList*, ExprList*, Token*); #else # define sqlite3WindowDelete(a,b) # define sqlite3WindowFunctions() diff --git a/src/window.c b/src/window.c index b0d28c493..b1beda5ce 100644 --- a/src/window.c +++ b/src/window.c @@ -512,6 +512,17 @@ void sqlite3WindowFunctions(void){ sqlite3InsertBuiltinFuncs(aWindowFuncs, ArraySize(aWindowFuncs)); } +static Window *windowFind(Parse *pParse, Window *pList, const char *zName){ + Window *p; + for(p=pList; p; p=p->pNextWin){ + if( sqlite3StrICmp(p->zName, zName)==0 ) break; + } + if( p==0 ){ + sqlite3ErrorMsg(pParse, "no such window: %s", zName); + } + return p; +} + /* ** This function is called immediately after resolving the function name ** for a window function within a SELECT statement. Argument pList is a @@ -536,14 +547,8 @@ void sqlite3WindowUpdate( FuncDef *pFunc /* Window function definition */ ){ if( pWin->zName && pWin->eType==0 ){ - Window *p; - for(p=pList; p; p=p->pNextWin){ - if( sqlite3StrICmp(p->zName, pWin->zName)==0 ) break; - } - if( p==0 ){ - sqlite3ErrorMsg(pParse, "no such window: %s", pWin->zName); - return; - } + Window *p = windowFind(pParse, pList, pWin->zName); + if( p==0 ) return; pWin->pPartition = sqlite3ExprListDup(pParse->db, p->pPartition, 0); pWin->pOrderBy = sqlite3ExprListDup(pParse->db, p->pOrderBy, 0); pWin->pStart = sqlite3ExprDup(pParse->db, p->pStart, 0); @@ -551,6 +556,8 @@ void sqlite3WindowUpdate( pWin->eStart = p->eStart; pWin->eEnd = p->eEnd; pWin->eType = p->eType; + }else{ + sqlite3WindowChain(pParse, pWin, pList); } if( pFunc->funcFlags & SQLITE_FUNC_WINDOW ){ sqlite3 *db = pParse->db; @@ -781,6 +788,7 @@ int sqlite3WindowRewrite(Parse *pParse, Select *p){ ** The OpenEphemeral instruction is coded later, after it is known how ** many columns the table will have. */ pMWin->iEphCsr = pParse->nTab++; + pParse->nTab += 3; selectWindowRewriteEList(pParse, pMWin, pSrc, p->pEList, &pSublist); selectWindowRewriteEList(pParse, pMWin, pSrc, p->pOrderBy, &pSublist); @@ -836,6 +844,9 @@ int sqlite3WindowRewrite(Parse *pParse, Select *p){ } sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pMWin->iEphCsr, pSublist->nExpr); + sqlite3VdbeAddOp2(v, OP_OpenDup, pMWin->iEphCsr+1, pMWin->iEphCsr); + sqlite3VdbeAddOp2(v, OP_OpenDup, pMWin->iEphCsr+2, pMWin->iEphCsr); + sqlite3VdbeAddOp2(v, OP_OpenDup, pMWin->iEphCsr+3, pMWin->iEphCsr); }else{ sqlite3SelectDelete(db, pSub); } @@ -856,6 +867,7 @@ void sqlite3WindowDelete(sqlite3 *db, Window *p){ sqlite3ExprDelete(db, p->pEnd); sqlite3ExprDelete(db, p->pStart); sqlite3DbFree(db, p->zName); + sqlite3DbFree(db, p->zBase); sqlite3DbFree(db, p); } } @@ -899,9 +911,10 @@ Window *sqlite3WindowAlloc( Expr *pEnd /* End window size if TK_FOLLOWING or PRECEDING */ ){ Window *pWin = 0; + int bImplicitFrame = 0; /* Parser assures the following: */ - assert( 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 @@ -909,6 +922,10 @@ Window *sqlite3WindowAlloc( assert( (eStart==TK_PRECEDING || eStart==TK_FOLLOWING)==(pStart!=0) ); assert( (eEnd==TK_FOLLOWING || eEnd==TK_PRECEDING)==(pEnd!=0) ); + if( eType==0 ){ + bImplicitFrame = 1; + eType = TK_RANGE; + } /* If a frame is declared "RANGE" (not "ROWS"), then it may not use ** either "<expr> PRECEDING" or "<expr> FOLLOWING". @@ -944,6 +961,7 @@ Window *sqlite3WindowAlloc( pWin->eType = eType; pWin->eStart = eStart; pWin->eEnd = eEnd; + pWin->bImplicitFrame = bImplicitFrame; pWin->pEnd = sqlite3WindowOffsetExpr(pParse, pEnd); pWin->pStart = sqlite3WindowOffsetExpr(pParse, pStart); return pWin; @@ -955,6 +973,69 @@ windowAllocErr: } /* +** Attach PARTITION and ORDER BY clauses pPartition and pOrderBy to window +** pWin. Also, if parameter pBase is not NULL, set pWin->zBase to the +** equivalent nul-terminated string. +*/ +Window *sqlite3WindowAssemble( + Parse *pParse, + Window *pWin, + ExprList *pPartition, + ExprList *pOrderBy, + Token *pBase +){ + if( pWin ){ + pWin->pPartition = pPartition; + pWin->pOrderBy = pOrderBy; + if( pBase ){ + pWin->zBase = sqlite3DbStrNDup(pParse->db, pBase->z, pBase->n); + } + }else{ + sqlite3ExprListDelete(pParse->db, pPartition); + sqlite3ExprListDelete(pParse->db, pOrderBy); + } + return pWin; +} + +/* +** Window *pWin has just been created from a WINDOW clause. Tokne pBase +** is the base window. Earlier windows from the same WINDOW clause are +** stored in the linked list starting at pWin->pNextWin. This function +** either updates *pWin according to the base specification, or else +** leaves an error in pParse. +*/ +void sqlite3WindowChain(Parse *pParse, Window *pWin, Window *pList){ + if( pWin->zBase ){ + sqlite3 *db = pParse->db; + Window *pExist = windowFind(pParse, pList, pWin->zBase); + if( pExist ){ + const char *zErr = 0; + /* Check for errors */ + if( pWin->pPartition ){ + zErr = "PARTITION clause"; + }else if( pExist->pOrderBy && pWin->pOrderBy ){ + zErr = "ORDER BY clause"; + }else if( pExist->bImplicitFrame==0 ){ + zErr = "frame specification"; + } + if( zErr ){ + sqlite3ErrorMsg(pParse, + "cannot override %s of window: %s", zErr, pWin->zBase + ); + }else{ + pWin->pPartition = sqlite3ExprListDup(db, pExist->pPartition, 0); + if( pExist->pOrderBy ){ + assert( pWin->pOrderBy==0 ); + pWin->pOrderBy = sqlite3ExprListDup(db, pExist->pOrderBy, 0); + } + sqlite3DbFree(db, pWin->zBase); + pWin->zBase = 0; + } + } + } +} + +/* ** Attach window object pWin to expression p. */ void sqlite3WindowAttach(Parse *pParse, Expr *p, Window *pWin){ @@ -1009,6 +1090,11 @@ void sqlite3WindowCodeInit(Parse *pParse, Window *pMWin){ sqlite3VdbeAddOp3(v, OP_Null, 0, pMWin->regPart, pMWin->regPart+nPart-1); } + pMWin->regFirst = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Integer, 1, pMWin->regFirst); + pMWin->regSize = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Integer, 0, pMWin->regSize); + for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ FuncDef *p = pWin->pFunc; if( (p->funcFlags & SQLITE_FUNC_MINMAX) && pWin->eStart!=TK_UNBOUNDED ){ @@ -1242,69 +1328,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 ** aggregate window functions that use the standard API, a single @@ -1352,10 +1375,10 @@ static void windowReturnOneRow( } else if( pFunc->zName==leadName || pFunc->zName==lagName ){ int nArg = pWin->pOwner->x.pList->nExpr; - int iEph = pMWin->iEphCsr; int csr = pWin->csrApp; int lbl = sqlite3VdbeMakeLabel(pParse); int tmpReg = sqlite3GetTempReg(pParse); + int iEph = pMWin->iEphCsr; if( nArg<3 ){ sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regResult); @@ -1385,45 +1408,6 @@ static void windowReturnOneRow( } /* -** 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 ** any equivalent initialization required by any built-in window functions @@ -1456,408 +1440,249 @@ static int windowInitAccum(Parse *pParse, Window *pMWin){ return regArg; } +/* +** Return true if the entire partition should be cached in the ephemeral +** table before processing any rows. +*/ +static int windowCachePartition(Window *pMWin){ + Window *pWin; + 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) + ){ + return 1; + } + } + return 0; +} /* -** This function does the work of sqlite3WindowCodeStep() for all "ROWS" -** window frame types except for "BETWEEN UNBOUNDED PRECEDING AND CURRENT -** ROW". Pseudo-code for each follows. -** -** ROWS BETWEEN <expr1> PRECEDING AND <expr2> FOLLOWING -** -** ... -** if( new partition ){ -** Gosub flush_partition -** } -** Insert (record in eph-table) -** sqlite3WhereEnd() -** Gosub flush_partition -** -** flush_partition: -** Once { -** OpenDup (iEphCsr -> csrStart) -** OpenDup (iEphCsr -> csrEnd) -** } -** 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 ){ -** AggInverse (csrStart) -** Next(csrStart) -** } -** } -** flush_partition_done: -** ResetSorter (csr) -** Return -** -** 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 -** if( (regStart--)<=0 ){ -** AggInverse (csrStart) -** Next(csrStart) -** } -** } -** -** For the "CURRENT ROW AND UNBOUNDED FOLLOWING" case, the final if() -** condition is always true (as if regStart were initialized to 0). -** -** 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 ){ -** AggInverse (csrStart) -** Next(csrStart) -** } -** } -** -** ROWS BETWEEN <expr> FOLLOWING AND <expr> FOLLOWING -** -** regEnd = regEnd - regStart -** Rewind (csr,csrStart,csrEnd) // if EOF goto flush_partition_done -** Aggstep (csrEnd) -** Next(csrEnd) // if EOF fall-through -** if( (regEnd--)<=0 ){ -** if( (regStart--)<=0 ){ -** AggFinal (xValue) -** Gosub addrGosub -** Next(csr) // if EOF goto flush_partition_done -** } -** AggInverse (csrStart) -** Next (csrStart) -** } -** -** ROWS BETWEEN <expr> PRECEDING AND <expr> PRECEDING -** -** Replace the bit after "Rewind" in the above with: -** -** if( (regEnd--)<=0 ){ -** AggStep (csrEnd) -** Next (csrEnd) -** } -** AggFinal (xValue) -** Gosub addrGosub -** Next(csr) // if EOF goto flush_partition_done -** if( (regStart--)<=0 ){ -** AggInverse (csr2) -** Next (csr2) -** } -** +** 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 void windowCodeRowExprStep( - Parse *pParse, - Select *p, - WhereInfo *pWInfo, - int regGosub, - int addrGosub +static int windowIfNewPeer( + Parse *pParse, + ExprList *pOrderBy, + int regNew, /* First in array of new values */ + int regOld /* First in array of old values */ ){ - Window *pMWin = p->pWin; Vdbe *v = sqlite3GetVdbe(pParse); - int regFlushPart; /* Register for "Gosub flush_partition" */ - int lblFlushPart; /* Label for "Gosub flush_partition" */ - int lblFlushDone; /* Label for "Gosub flush_partition_done" */ - - int regArg; int addr; - int csrStart = pParse->nTab++; - int csrEnd = pParse->nTab++; - int regStart; /* Value of <expr> PRECEDING */ - int regEnd; /* Value of <expr> FOLLOWING */ - int addrGoto; - int addrTop; - int addrIfPos1 = 0; - int addrIfPos2 = 0; - int regSize = 0; - - assert( pMWin->eStart==TK_PRECEDING - || pMWin->eStart==TK_CURRENT - || pMWin->eStart==TK_FOLLOWING - || pMWin->eStart==TK_UNBOUNDED - ); - assert( pMWin->eEnd==TK_FOLLOWING - || pMWin->eEnd==TK_CURRENT - || pMWin->eEnd==TK_UNBOUNDED - || pMWin->eEnd==TK_PRECEDING - ); + 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; +} - /* Allocate register and label for the "flush_partition" sub-routine. */ - regFlushPart = ++pParse->nMem; - lblFlushPart = sqlite3VdbeMakeLabel(pParse); - lblFlushDone = sqlite3VdbeMakeLabel(pParse); +typedef struct WindowCodeArg WindowCodeArg; +typedef struct WindowCsrAndReg WindowCsrAndReg; +struct WindowCsrAndReg { + int csr; + int reg; +}; +struct WindowCodeArg { + Parse *pParse; + Window *pMWin; + Vdbe *pVdbe; + int regGosub; + int addrGosub; + int regArg; - regStart = ++pParse->nMem; - regEnd = ++pParse->nMem; + WindowCsrAndReg start; + WindowCsrAndReg current; + WindowCsrAndReg end; +}; - windowPartitionCache(pParse, p, pWInfo, regFlushPart, lblFlushPart, ®Size); +#define WINDOW_RETURN_ROW 1 +#define WINDOW_AGGINVERSE 2 +#define WINDOW_AGGSTEP 3 - addrGoto = sqlite3VdbeAddOp0(v, OP_Goto); - - /* Start of "flush_partition" */ - sqlite3VdbeResolveLabel(v, lblFlushPart); - sqlite3VdbeAddOp2(v, OP_Once, 0, sqlite3VdbeCurrentAddr(v)+3); - VdbeCoverage(v); - VdbeComment((v, "Flush_partition subroutine")); - sqlite3VdbeAddOp2(v, OP_OpenDup, csrStart, pMWin->iEphCsr); - sqlite3VdbeAddOp2(v, OP_OpenDup, csrEnd, pMWin->iEphCsr); - - /* If either regStart or regEnd are not non-negative integers, throw - ** an exception. */ - if( pMWin->pStart ){ - sqlite3ExprCode(pParse, pMWin->pStart, regStart); - windowCheckIntValue(pParse, regStart, 0); +/* +** 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); + } } - if( pMWin->pEnd ){ - sqlite3ExprCode(pParse, pMWin->pEnd, regEnd); - windowCheckIntValue(pParse, regEnd, 1); +} + +static int windowCodeOp( + WindowCodeArg *p, + int op, + 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. */ + if( op==WINDOW_AGGINVERSE && pMWin->eStart==TK_UNBOUNDED ){ + assert( regCountdown==0 && jumpOnEof==0 ); + return 0; } - /* If this is "ROWS <expr1> FOLLOWING AND ROWS <expr2> FOLLOWING", do: - ** - ** if( regEnd<regStart ){ - ** // The frame always consists of 0 rows - ** regStart = regSize; - ** } - ** regEnd = regEnd - regStart; - */ - if( pMWin->pEnd && pMWin->eStart==TK_FOLLOWING ){ - assert( pMWin->pStart!=0 ); - assert( pMWin->eEnd==TK_FOLLOWING ); - sqlite3VdbeAddOp3(v, OP_Ge, regStart, sqlite3VdbeCurrentAddr(v)+2, regEnd); - VdbeCoverageNeverNull(v); - sqlite3VdbeAddOp2(v, OP_Copy, regSize, regStart); - sqlite3VdbeAddOp3(v, OP_Subtract, regStart, regEnd, regEnd); - } - - if( pMWin->pStart && pMWin->eEnd==TK_PRECEDING ){ - assert( pMWin->pEnd!=0 ); - assert( pMWin->eStart==TK_PRECEDING ); - sqlite3VdbeAddOp3(v, OP_Le, regStart, sqlite3VdbeCurrentAddr(v)+3, regEnd); - VdbeCoverageNeverNull(v); - sqlite3VdbeAddOp2(v, OP_Copy, regSize, regStart); - sqlite3VdbeAddOp2(v, OP_Copy, regSize, regEnd); - } - - /* Initialize the accumulator register for each window function to NULL */ - regArg = windowInitAccum(pParse, pMWin); - - sqlite3VdbeAddOp2(v, OP_Rewind, pMWin->iEphCsr, lblFlushDone); - VdbeCoverage(v); - sqlite3VdbeAddOp2(v, OP_Rewind, csrStart, lblFlushDone); - VdbeCoverageNeverTaken(v); - sqlite3VdbeChangeP5(v, 1); - sqlite3VdbeAddOp2(v, OP_Rewind, csrEnd, lblFlushDone); - VdbeCoverageNeverTaken(v); - sqlite3VdbeChangeP5(v, 1); - - /* Invoke AggStep function for each window function using the row that - ** csrEnd currently points to. Or, if csrEnd is already at EOF, - ** do nothing. */ - addrTop = sqlite3VdbeCurrentAddr(v); - if( pMWin->eEnd==TK_PRECEDING ){ - addrIfPos1 = sqlite3VdbeAddOp3(v, OP_IfPos, regEnd, 0 , 1); - VdbeCoverage(v); - } - sqlite3VdbeAddOp2(v, OP_Next, csrEnd, sqlite3VdbeCurrentAddr(v)+2); - VdbeCoverage(v); - addr = sqlite3VdbeAddOp0(v, OP_Goto); - windowAggStep(pParse, pMWin, csrEnd, 0, regArg, regSize); - if( pMWin->eEnd==TK_UNBOUNDED ){ - sqlite3VdbeAddOp2(v, OP_Goto, 0, addrTop); - sqlite3VdbeJumpHere(v, addr); - addrTop = sqlite3VdbeCurrentAddr(v); - }else{ - sqlite3VdbeJumpHere(v, addr); - if( pMWin->eEnd==TK_PRECEDING ){ - sqlite3VdbeJumpHere(v, addrIfPos1); - } + if( regCountdown>0 ){ + addrIf = sqlite3VdbeAddOp3(v, OP_IfPos, regCountdown, 0, 1); } - if( pMWin->eEnd==TK_FOLLOWING ){ - addrIfPos1 = sqlite3VdbeAddOp3(v, OP_IfPos, regEnd, 0 , 1); - VdbeCoverage(v); + if( op==WINDOW_RETURN_ROW ){ + windowAggFinal(pParse, pMWin, 0); } - if( pMWin->eStart==TK_FOLLOWING ){ - addrIfPos2 = sqlite3VdbeAddOp3(v, OP_IfPos, regStart, 0 , 1); - VdbeCoverage(v); - } - windowAggFinal(pParse, pMWin, 0); - windowReturnOneRow(pParse, pMWin, regGosub, addrGosub); - sqlite3VdbeAddOp2(v, OP_Next, pMWin->iEphCsr, sqlite3VdbeCurrentAddr(v)+2); - VdbeCoverage(v); - sqlite3VdbeAddOp2(v, OP_Goto, 0, lblFlushDone); - if( pMWin->eStart==TK_FOLLOWING ){ - sqlite3VdbeJumpHere(v, addrIfPos2); + addrContinue = sqlite3VdbeCurrentAddr(v); + switch( op ){ + case WINDOW_RETURN_ROW: + csr = p->current.csr; + reg = p->current.reg; + windowReturnOneRow(pParse, pMWin, p->regGosub, p->addrGosub); + break; + + case WINDOW_AGGINVERSE: + csr = p->start.csr; + reg = p->start.reg; + windowAggStep(pParse, pMWin, csr, 1, p->regArg, pMWin->regSize); + break; + + case WINDOW_AGGSTEP: + csr = p->end.csr; + reg = p->end.reg; + windowAggStep(pParse, pMWin, csr, 0, p->regArg, pMWin->regSize); + break; } - if( pMWin->eStart==TK_CURRENT - || pMWin->eStart==TK_PRECEDING - || pMWin->eStart==TK_FOLLOWING - ){ - int lblSkipInverse = sqlite3VdbeMakeLabel(pParse);; - if( pMWin->eStart==TK_PRECEDING ){ - sqlite3VdbeAddOp3(v, OP_IfPos, regStart, lblSkipInverse, 1); - VdbeCoverage(v); - } - if( pMWin->eStart==TK_FOLLOWING ){ - sqlite3VdbeAddOp2(v, OP_Next, csrStart, sqlite3VdbeCurrentAddr(v)+2); - VdbeCoverage(v); - sqlite3VdbeAddOp2(v, OP_Goto, 0, lblSkipInverse); - }else{ - sqlite3VdbeAddOp2(v, OP_Next, csrStart, sqlite3VdbeCurrentAddr(v)+1); - VdbeCoverageAlwaysTaken(v); + if( jumpOnEof ){ + sqlite3VdbeAddOp2(v, OP_Next, csr, sqlite3VdbeCurrentAddr(v)+2); + ret = sqlite3VdbeAddOp0(v, OP_Goto); + }else{ + sqlite3VdbeAddOp2(v, OP_Next, csr, sqlite3VdbeCurrentAddr(v)+1+bPeer); + if( bPeer ){ + addrGoto = sqlite3VdbeAddOp0(v, OP_Goto); } - windowAggStep(pParse, pMWin, csrStart, 1, regArg, regSize); - sqlite3VdbeResolveLabel(v, lblSkipInverse); - } - if( pMWin->eEnd==TK_FOLLOWING ){ - sqlite3VdbeJumpHere(v, addrIfPos1); } - sqlite3VdbeAddOp2(v, OP_Goto, 0, addrTop); - /* flush_partition_done: */ - sqlite3VdbeResolveLabel(v, lblFlushDone); - sqlite3VdbeAddOp1(v, OP_ResetSorter, pMWin->iEphCsr); - sqlite3VdbeAddOp1(v, OP_Return, regFlushPart); - VdbeComment((v, "end flush_partition subroutine")); + 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); + } - /* Jump to here to skip over flush_partition */ - sqlite3VdbeJumpHere(v, addrGoto); + if( addrGoto ) sqlite3VdbeJumpHere(v, addrGoto); + if( addrIf ) sqlite3VdbeJumpHere(v, addrIf); + return ret; } /* -** 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. +** This function - windowCodeStep() - generates the VM code that reads data +** from the sub-select and returns rows to the consumer. For the simplest +** case: ** -** Pseudo-code corresponding to the VM code generated by this function -** for each type of window follows. +** ROWS BETWEEN <expr1> PRECEDING AND <expr2> FOLLOWING ** -** RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW +** The VM code generated is equivalent in spirit to the following: ** -** 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 +** while( !eof ){ +** if( new partition ){ +** Gosub flush +** } +** Insert new row into eph table. +** +** if( first row of partition ){ +** Rewind(csrEnd, skipNext=1) +** Rewind(start.csr, skipNext=1) +** Rewind(csrCurrent, skipNext=1) +** +** regEnd = <expr2> // FOLLOWING expression +** regStart = <expr1> // PRECEDING expression +** }else{ +** if( (regEnd--)<=0 ){ +** Next(csrCurrent) +** Return one row. +** if( (regStart--)<0 ){ +** Next(start.csr) +** AggInverse(start.csr) +** } ** } -** 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 +** } +** +** Next(csrEnd) +** AggStep(csrEnd) +** } +** flush: +** while( 1 ){ +** Next(csrCurrent) +** if( eof ) break +** Return one row. +** if( (regStart--)<0 ){ +** Next(start.csr) +** AggInverse(start.csr) ** } +** } +** Empty eph table. ** -** RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING +** More generally, the pattern used for all window types is: ** -** 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 -** } +** while( !eof ){ +** if( new partition ){ +** Gosub flush +** } +** Insert new row into eph table. +** if( first row of partition ){ +** FIRST_ROW_CODE +** }else{ +** SECOND_ROW_CODE +** } +** ALL_ROW_CODE +** } +** flush: +** FLUSH_CODE +** Empty eph table. ** -** ResetSorter (csr) -** Return */ -static void windowCodeCacheStep( +static void windowCodeStep( Parse *pParse, Select *p, WhereInfo *pWInfo, @@ -1865,263 +1690,296 @@ static void windowCodeCacheStep( 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) - ); + Vdbe *v = sqlite3GetVdbe(pParse); + int regFlushPart; /* Register for "Gosub flush_partition" */ - lblEmpty = sqlite3VdbeMakeLabel(pParse); - regNewPeer = pParse->nMem+1; - pParse->nMem += nPeer; + int regArg; + int csrWrite = pMWin->iEphCsr+1; - /* Allocate register and label for the "flush_partition" sub-routine. */ - regFlushPart = ++pParse->nMem; - lblFlushPart = sqlite3VdbeMakeLabel(pParse); + 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 */ + int iCol; /* To iterate through sub cols */ - csrLead = pParse->nTab++; - regCtr = ++pParse->nMem; + int addrGoto; + int addrIf; + int addrGosubFlush; + int addrInteger; + int addrCacheRewind; + int addrCacheNext; - windowPartitionCache(pParse, p, pWInfo, regFlushPart, lblFlushPart, ®Size); - addrGoto = sqlite3VdbeAddOp0(v, OP_Goto); + int addrShortcut = 0; + int addrEmpty = 0; + int addrPeerJump = 0; - /* 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); - } + int bCache = windowCachePartition(pMWin); - windowReturnRows(pParse, pMWin, regCtr, regGosub, addrGosub, - (bCurrent ? regArg : 0), (bCurrent ? regSize : 0) - ); - if( addrJump ) sqlite3VdbeJumpHere(v, addrJump); - } + int regStart = 0; /* Value of <expr> PRECEDING */ + int regEnd = 0; /* Value of <expr> FOLLOWING */ - 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); + 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)); + s.pParse = pParse; + s.pMWin = pMWin; + 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; - sqlite3VdbeResolveLabel(v, lblEmpty); - sqlite3VdbeAddOp1(v, OP_ResetSorter, pMWin->iEphCsr); - sqlite3VdbeAddOp1(v, OP_Return, regFlushPart); + regFlushPart = ++pParse->nMem; - /* Jump to here to skip over flush_partition */ - sqlite3VdbeJumpHere(v, addrGoto); -} + if( pMWin->eStart==TK_PRECEDING || pMWin->eStart==TK_FOLLOWING ){ + regStart = ++pParse->nMem; + } + if( pMWin->eEnd==TK_PRECEDING || pMWin->eEnd==TK_FOLLOWING ){ + 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; -/* -** 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; + 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->eType==TK_RANGE - || (pMWin->eStart==TK_UNBOUNDED && pMWin->eEnd==TK_CURRENT) + assert( pMWin->eStart==TK_PRECEDING + || pMWin->eStart==TK_CURRENT + || pMWin->eStart==TK_FOLLOWING + || pMWin->eStart==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 && !pOrderBy) + assert( pMWin->eEnd==TK_FOLLOWING + || pMWin->eEnd==TK_CURRENT + || pMWin->eEnd==TK_UNBOUNDED + || pMWin->eEnd==TK_PRECEDING ); - if( pMWin->eEnd==TK_UNBOUNDED ){ - pOrderBy = 0; + + /* Load the column values for the row returned by the sub-select + ** into an array of registers starting at reg. Assemble them into + ** a record in register regRecord. TODO: An optimization here? */ + for(iCol=0; iCol<nSub; iCol++){ + sqlite3VdbeAddOp3(v, OP_Column, iSubCsr, iCol, reg+iCol); } + sqlite3VdbeAddOp3(v, OP_MakeRecord, reg, nSub, regRecord); - pParse->nMem += nSub + 2; + /* An input row has just been read into an array of registers starting + ** at reg. If the window has a PARTITION clause, this block generates + ** VM code to check if the input row is the start of a new partition. + ** If so, it does an OP_Gosub to an address to be filled in later. The + ** address of the OP_Gosub is stored in local variable addrGosubFlush. + */ + 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); - /* 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); + addrIf = sqlite3VdbeAddOp1(v, OP_If, pMWin->regFirst); + 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); + addrGosubFlush = sqlite3VdbeAddOp1(v, OP_Gosub, regFlushPart); + VdbeComment((v, "call flush_partition")); + sqlite3VdbeJumpHere(v, addrIf); + sqlite3VdbeAddOp3(v, OP_Copy, regNewPart, pMWin->regPart, nPart-1); } - /* 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); - } + /* Insert the new row into the ephemeral table */ + sqlite3VdbeAddOp2(v, OP_NewRowid, csrWrite, regRowid); + sqlite3VdbeAddOp3(v, OP_Insert, csrWrite, regRecord, regRowid); + sqlite3VdbeAddOp2(v, OP_AddImm, pMWin->regSize, 1); + + if( bCache ){ + sqlite3VdbeAddOp2(v, OP_Integer, 0, pMWin->regFirst); + sqlite3WhereEnd(pWInfo); + addrInteger = sqlite3VdbeAddOp2(v, OP_Integer, 0, regFlushPart); + if( pMWin->pPartition ){ + sqlite3VdbeJumpHere(v, addrGosubFlush); } + addrCacheRewind = sqlite3VdbeAddOp1(v, OP_Rewind, csrWrite); + }else{ + addrIf = sqlite3VdbeAddOp1(v, OP_IfNot, pMWin->regFirst); + } - if( pOrderBy ){ - int regNewPeer = reg + pMWin->nBufferCol + nPart; - int regPeer = pMWin->regPart + nPart; + /* This block is run for the first row of each partition */ + s.regArg = regArg = windowInitAccum(pParse, pMWin); - 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); + if( regStart ){ + sqlite3ExprCode(pParse, pMWin->pStart, regStart); + windowCheckIntValue(pParse, regStart, 0); + } + if( regEnd ){ + sqlite3ExprCode(pParse, pMWin->pEnd, regEnd); + windowCheckIntValue(pParse, regEnd, 1); + } + + if( pMWin->eStart==pMWin->eEnd && regStart && regEnd ){ + int op = ((pMWin->eStart==TK_FOLLOWING) ? OP_Ge : OP_Le); + int addrGe = sqlite3VdbeAddOp3(v, op, regStart, 0, regEnd); + windowAggFinal(pParse, pMWin, 0); + if( bCache ){ + sqlite3VdbeAddOp2(v, OP_Rowid, csrWrite, 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, s.current.csr, 1); + windowReturnOneRow(pParse, pMWin, regGosub, addrGosub); + sqlite3VdbeAddOp1(v, OP_ResetSorter, s.current.csr); + } + addrShortcut = sqlite3VdbeAddOp0(v, OP_Goto); + sqlite3VdbeJumpHere(v, addrGe); + } + if( pMWin->eStart==TK_FOLLOWING && regEnd ){ + assert( pMWin->eEnd==TK_FOLLOWING ); + sqlite3VdbeAddOp3(v, OP_Subtract, regStart, regEnd, regStart); + } + + if( pMWin->eStart!=TK_UNBOUNDED ){ + 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, pMWin->iEphCsr,sqlite3VdbeCurrentAddr(v)+3); - VdbeCoverage(v); - sqlite3VdbeAddOp2(v, OP_Gosub, regGosub, addrGosub); - sqlite3VdbeAddOp2(v, OP_Next, pMWin->iEphCsr, sqlite3VdbeCurrentAddr(v)-1); - VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_Integer, 0, pMWin->regFirst); + addrGoto = sqlite3VdbeAddOp0(v, OP_Goto); - sqlite3VdbeAddOp1(v, OP_ResetSorter, pMWin->iEphCsr); - sqlite3VdbeAddOp3( - v, OP_Copy, reg+pMWin->nBufferCol, pMWin->regPart, nPart+nPeer-1 - ); + /* Begin generating SECOND_ROW_CODE */ + 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, 0, 0); + if( pMWin->eEnd!=TK_UNBOUNDED ){ + windowCodeOp(&s, WINDOW_RETURN_ROW, regEnd, 0); + windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0); + } + }else + if( pMWin->eEnd==TK_PRECEDING ){ + 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, 0, 0); + if( pMWin->eEnd!=TK_UNBOUNDED ){ + if( regEnd ) addr = sqlite3VdbeAddOp3(v, OP_IfPos, regEnd, 0, 1); + 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")); - if( addrJump ) sqlite3VdbeJumpHere(v, addrJump); + /* End of the main input loop */ + sqlite3VdbeJumpHere(v, addrGoto); + if( bCache ){ + sqlite3VdbeAddOp2(v, OP_Next, csrWrite, addrCacheNext); + sqlite3VdbeJumpHere(v, addrCacheRewind); + }else{ + if( addrShortcut>0 ) sqlite3VdbeJumpHere(v, addrShortcut); + sqlite3WhereEnd(pWInfo); } - /* Invoke step function for window functions */ - windowAggStep(pParse, pMWin, -1, 0, reg, 0); + /* Fall through */ + if( pMWin->pPartition && bCache==0 ){ + addrInteger = sqlite3VdbeAddOp2(v, OP_Integer, 0, regFlushPart); + sqlite3VdbeJumpHere(v, addrGosubFlush); + } - /* Buffer the current row in the ephemeral table. */ - if( pMWin->nBufferCol>0 ){ - sqlite3VdbeAddOp3(v, OP_MakeRecord, reg, pMWin->nBufferCol, regRecord); + VdbeModuleComment((pParse->pVdbe, "Begin windowCodeStep.FLUSH_CODE")); + addrEmpty = sqlite3VdbeAddOp1(v, OP_Rewind, csrWrite); + if( pMWin->eEnd==TK_PRECEDING ){ + 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, 0, 0); + if( pMWin->eEnd==TK_UNBOUNDED ){ + addrStart = sqlite3VdbeCurrentAddr(v); + 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, 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, 0, 1); + sqlite3VdbeAddOp2(v, OP_Goto, 0, addrStart); + sqlite3VdbeJumpHere(v, addrBreak1); + sqlite3VdbeJumpHere(v, addrBreak3); }else{ - sqlite3VdbeAddOp2(v, OP_Blob, 0, regRecord); - sqlite3VdbeAppendP4(v, (void*)"", 0); + int addrBreak; + int addrStart; + windowCodeOp(&s, WINDOW_AGGSTEP, 0, 0); + addrStart = sqlite3VdbeCurrentAddr(v); + addrBreak = windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 1); + windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0); + sqlite3VdbeAddOp2(v, OP_Goto, 0, addrStart); + sqlite3VdbeJumpHere(v, addrBreak); } - sqlite3VdbeAddOp2(v, OP_NewRowid, pMWin->iEphCsr, regRowid); - sqlite3VdbeAddOp3(v, OP_Insert, pMWin->iEphCsr, regRecord, regRowid); - /* End the database scan loop. */ - sqlite3WhereEnd(pWInfo); + sqlite3VdbeJumpHere(v, addrEmpty); - 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); + if( bCache && addrShortcut>0 ) sqlite3VdbeJumpHere(v, addrShortcut); + 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")); + if( pMWin->pPartition ){ + sqlite3VdbeChangeP1(v, addrInteger, sqlite3VdbeCurrentAddr(v)); + sqlite3VdbeAddOp1(v, OP_Return, regFlushPart); + } } + /* ** Allocate and return a duplicate of the Window object indicated by the ** third argument. Set the Window.pOwner field of the new object to @@ -2180,77 +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 - && (pMWin->eStart!=TK_UNBOUNDED||pMWin->eEnd!=TK_CURRENT||!pMWin->pOrderBy) - ){ - VdbeModuleComment((pParse->pVdbe, "Begin RowExprStep()")); - windowCodeRowExprStep(pParse, p, pWInfo, regGosub, addrGosub); - }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); - }else{ - VdbeModuleComment((pParse->pVdbe, "Begin DefaultStep()")); - windowCodeDefaultStep(pParse, p, pWInfo, regGosub, addrGosub); - } - } + VdbeModuleComment((pParse->pVdbe, "Begin windowCodeStep()")); + windowCodeStep(pParse, p, pWInfo, regGosub, addrGosub); + VdbeModuleComment((pParse->pVdbe, "End windowCodeStep()")); } #endif /* SQLITE_OMIT_WINDOWFUNC */ |