1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-08-05 15:55:57 +03:00

Support some "ROWS BETWEEN N PRECEDING AND M FOLLOWING" window functions without caching entire partitions.

FossilOrigin-Name: e7a91f12282afb5d5d7d78397a11d18e0268ee0c931d85e21fce00d13929494e
This commit is contained in:
dan
2019-03-04 21:07:11 +00:00
parent e7c9ca41b2
commit 680f6e8e2e
9 changed files with 508 additions and 15 deletions

View File

@@ -788,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);
@@ -843,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);
}
@@ -1086,6 +1090,9 @@ 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);
for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
FuncDef *p = pWin->pFunc;
if( (p->funcFlags & SQLITE_FUNC_MINMAX) && pWin->eStart!=TK_UNBOUNDED ){
@@ -1429,10 +1436,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);
@@ -1834,6 +1841,154 @@ static void windowCodeRowExprStep(
sqlite3VdbeJumpHere(v, addrGoto);
}
static void windowCodeStep(
Parse *pParse,
Select *p,
WhereInfo *pWInfo,
int regGosub,
int addrGosub
){
Window *pMWin = p->pWin;
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 regStart; /* Value of <expr> PRECEDING */
int regEnd; /* Value of <expr> FOLLOWING */
int iSubCsr = p->pSrc->a[0].iCursor;
int nSub = p->pSrc->a[0].pTab->nCol;
int k;
int addrGoto;
int addrIf;
int addrIfEnd;
int addrIfStart;
int addrGosubFlush;
int addrInteger;
int addrGoto2;
int reg = pParse->nMem+1;
int regRecord = reg+nSub;
int regRowid = regRecord+1;
pParse->nMem += 1 + nSub + 1;
regFlushPart = ++pParse->nMem;
regStart = ++pParse->nMem;
regEnd = ++pParse->nMem;
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
);
/* 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(k=0; k<nSub; k++){
sqlite3VdbeAddOp3(v, OP_Column, iSubCsr, k, reg+k);
}
sqlite3VdbeAddOp3(v, OP_MakeRecord, reg, nSub, regRecord);
/* Check if the current iteration is the first row of a new partition */
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);
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+3, addr+2);
VdbeCoverageEqNe(v);
addrGosubFlush = sqlite3VdbeAddOp1(v, OP_Gosub, regFlushPart);
VdbeComment((v, "call flush_partition"));
sqlite3VdbeJumpHere(v, addrIf);
}
/* Insert the new row into the ephemeral table */
sqlite3VdbeAddOp2(v, OP_NewRowid, csrWrite, regRowid);
sqlite3VdbeAddOp3(v, OP_Insert, csrWrite, regRecord, regRowid);
/* This block is run for the first row of each partition */
addrIf = sqlite3VdbeAddOp1(v, OP_IfNot, pMWin->regFirst);
if( pMWin->pPartition ){
sqlite3VdbeAddOp3(v, OP_Copy,
reg+pMWin->nBufferCol, pMWin->regPart, pMWin->pPartition->nExpr-1
);
}
sqlite3VdbeAddOp2(v, OP_Rewind, csrStart, 1); sqlite3VdbeChangeP5(v, 1);
sqlite3VdbeAddOp2(v, OP_Rewind, csrCurrent, 1); sqlite3VdbeChangeP5(v, 1);
sqlite3VdbeAddOp2(v, OP_Rewind, csrEnd, 1);
regArg = windowInitAccum(pParse, pMWin);
sqlite3VdbeAddOp2(v, OP_Integer, 0, pMWin->regFirst);
sqlite3ExprCode(pParse, pMWin->pStart, regStart);
windowCheckIntValue(pParse, regStart, 0);
sqlite3ExprCode(pParse, pMWin->pEnd, regEnd);
windowCheckIntValue(pParse, regEnd, 1);
addrGoto = sqlite3VdbeAddOp0(v, OP_Goto);
/* This block is run for the second and subsequent rows of each partition */
sqlite3VdbeJumpHere(v, addrIf);
sqlite3VdbeAddOp2(v, OP_Next, csrEnd, sqlite3VdbeCurrentAddr(v)+1);
addrIfEnd = sqlite3VdbeAddOp3(v, OP_IfPos, regEnd, 0, 1);
windowAggFinal(pParse, pMWin, 0);
sqlite3VdbeAddOp2(v, OP_Next, csrCurrent, sqlite3VdbeCurrentAddr(v)+1);
windowReturnOneRow(pParse, pMWin, regGosub, addrGosub);
addrIfStart = sqlite3VdbeAddOp3(v, OP_IfPos, regStart, 0, 1);
sqlite3VdbeAddOp2(v, OP_Next, csrStart, sqlite3VdbeCurrentAddr(v)+1);
windowAggStep(pParse, pMWin, csrStart, 1, regArg, 0);
sqlite3VdbeJumpHere(v, addrIfStart);
sqlite3VdbeJumpHere(v, addrIfEnd);
sqlite3VdbeJumpHere(v, addrGoto);
windowAggStep(pParse, pMWin, csrEnd, 0, regArg, 0);
/* End of the main input loop */
sqlite3WhereEnd(pWInfo);
/* Fall through */
if( pMWin->pPartition ){
addrInteger = sqlite3VdbeAddOp2(v, OP_Integer, 0, regFlushPart);
sqlite3VdbeJumpHere(v, addrGosubFlush);
}
sqlite3VdbeAddOp2(v, OP_Next, csrCurrent, sqlite3VdbeCurrentAddr(v)+2);
addrGoto = sqlite3VdbeAddOp0(v, OP_Goto);
windowAggFinal(pParse, pMWin, 0);
windowReturnOneRow(pParse, pMWin, regGosub, addrGosub);
addrIfStart = sqlite3VdbeAddOp3(v, OP_IfPos, regStart, 0, 1);
sqlite3VdbeAddOp2(v, OP_Next, csrStart, sqlite3VdbeCurrentAddr(v)+2);
addrGoto2 = sqlite3VdbeAddOp0(v, OP_Goto);
windowAggStep(pParse, pMWin, csrStart, 1, regArg, 0);
sqlite3VdbeJumpHere(v, addrIfStart);
sqlite3VdbeAddOp2(v, OP_Goto, 0, addrGoto-1);
sqlite3VdbeJumpHere(v, addrGoto);
sqlite3VdbeJumpHere(v, addrGoto2);
sqlite3VdbeAddOp1(v, OP_ResetSorter, csrCurrent);
sqlite3VdbeAddOp2(v, OP_Integer, 1, pMWin->regFirst);
if( pMWin->pPartition ){
sqlite3VdbeChangeP1(v, addrInteger, sqlite3VdbeCurrentAddr(v));
sqlite3VdbeAddOp1(v, OP_Return, regFlushPart);
}
}
/*
** This function does the work of sqlite3WindowCodeStep() for cases that
** would normally be handled by windowCodeDefaultStep() when there are
@@ -2296,8 +2451,29 @@ void sqlite3WindowCodeStep(
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);
Window *pWin;
int bCache = 0; /* True to use CacheStep() */
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;
}
}
if( bCache || pMWin->eStart!=TK_PRECEDING || pMWin->eEnd!=TK_FOLLOWING ){
VdbeModuleComment((pParse->pVdbe, "Begin RowExprStep()"));
windowCodeRowExprStep(pParse, p, pWInfo, regGosub, addrGosub);
VdbeModuleComment((pParse->pVdbe, "End RowExprStep()"));
}else{
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() */
@@ -2323,9 +2499,11 @@ void sqlite3WindowCodeStep(
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()"));
}
}
}