Index: src/btree.c ================================================================== --- src/btree.c +++ src/btree.c @@ -5188,16 +5188,18 @@ /* ** This function is a no-op if cursor pCur does not point to a valid row. ** Otherwise, if pCur is valid, configure it so that the next call to ** sqlite3BtreeNext() is a no-op. */ +#ifndef SQLITE_OMIT_WINDOWFUNC void sqlite3BtreeSkipNext(BtCursor *pCur){ if( pCur->eState==CURSOR_VALID ){ pCur->eState = CURSOR_SKIPNEXT; pCur->skipNext = 1; } } +#endif /* SQLITE_OMIT_WINDOWFUNC */ /* Move the cursor to the last entry in the table. Return SQLITE_OK ** on success. Set *pRes to 0 if the cursor actually points to something ** or set *pRes to 1 if the table is empty. */ Index: src/btree.h ================================================================== --- src/btree.h +++ src/btree.h @@ -299,11 +299,13 @@ }; int sqlite3BtreeInsert(BtCursor*, const BtreePayload *pPayload, int flags, int seekResult); int sqlite3BtreeFirst(BtCursor*, int *pRes); +#ifndef SQLITE_OMIT_WINDOWFUNC void sqlite3BtreeSkipNext(BtCursor*); +#endif int sqlite3BtreeLast(BtCursor*, int *pRes); int sqlite3BtreeNext(BtCursor*, int flags); int sqlite3BtreeEof(BtCursor*); int sqlite3BtreePrevious(BtCursor*, int flags); i64 sqlite3BtreeIntegerKey(BtCursor*); Index: src/expr.c ================================================================== --- src/expr.c +++ src/expr.c @@ -770,11 +770,10 @@ pNew = sqlite3DbMallocRawNN(db, sizeof(Expr)+nExtra); if( pNew ){ memset(pNew, 0, sizeof(Expr)); pNew->op = (u8)op; pNew->iAgg = -1; - pNew->pWin = 0; if( pToken ){ if( nExtra==0 ){ pNew->flags |= EP_IntValue|EP_Leaf; pNew->u.iValue = iValue; }else{ @@ -860,11 +859,10 @@ p = sqlite3DbMallocRawNN(pParse->db, sizeof(Expr)); if( p ){ memset(p, 0, sizeof(Expr)); p->op = op & TKFLG_MASK; p->iAgg = -1; - p->pWin = 0; } sqlite3ExprAttachSubtrees(pParse->db, p, pLeft, pRight); } if( p ) { sqlite3ExprCheckHeight(pParse, p->nHeight); @@ -1126,11 +1124,15 @@ static int dupedExprStructSize(Expr *p, int flags){ int nSize; assert( flags==EXPRDUP_REDUCE || flags==0 ); /* Only one flag value allowed */ assert( EXPR_FULLSIZE<=0xfff ); assert( (0xfff & (EP_Reduced|EP_TokenOnly))==0 ); - if( 0==flags || p->op==TK_SELECT_COLUMN || p->pWin ){ + if( 0==flags || p->op==TK_SELECT_COLUMN +#ifndef SQLITE_OMIT_WINDOWFUNC + || p->pWin +#endif + ){ nSize = EXPR_FULLSIZE; }else{ assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) ); assert( !ExprHasProperty(p, EP_FromJoin) ); assert( !ExprHasProperty(p, EP_MemToken) ); @@ -1266,15 +1268,17 @@ } if( pzBuffer ){ *pzBuffer = zAlloc; } }else{ +#ifndef SQLITE_OMIT_WINDOWFUNC if( ExprHasProperty(p, EP_Reduced|EP_TokenOnly) ){ pNew->pWin = 0; }else{ pNew->pWin = sqlite3WindowDup(db, pNew, p->pWin); } +#endif /* SQLITE_OMIT_WINDOWFUNC */ if( !ExprHasProperty(p, EP_TokenOnly|EP_Leaf) ){ if( pNew->op==TK_SELECT_COLUMN ){ pNew->pLeft = p->pLeft; assert( p->iColumn==0 || p->pRight==0 ); assert( p->pRight==0 || p->pRight==p->pLeft ); @@ -1477,12 +1481,14 @@ pNew->selFlags = p->selFlags & ~SF_UsesEphemeral; pNew->addrOpenEphm[0] = -1; pNew->addrOpenEphm[1] = -1; pNew->nSelectRow = p->nSelectRow; pNew->pWith = withDup(db, p->pWith); +#ifndef SQLITE_OMIT_WINDOWFUNC pNew->pWin = 0; pNew->pWinDefn = sqlite3WindowListDup(db, p->pWinDefn); +#endif sqlite3SelectSetName(pNew, p->zSelName); *pp = pNew; pp = &pNew->pPrior; pNext = pNew; } @@ -3798,13 +3804,15 @@ int i; /* Loop counter */ sqlite3 *db = pParse->db; /* The database connection */ u8 enc = ENC(db); /* The text encoding used by this database */ CollSeq *pColl = 0; /* A collating sequence */ +#ifndef SQLITE_OMIT_WINDOWFUNC if( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) && pExpr->pWin ){ return pExpr->pWin->regResult; } +#endif if( ConstFactorOk(pParse) && sqlite3ExprIsConstantNotJoin(pExpr) ){ /* SQL functions can be expensive. So try to move constant functions ** out of the inner loop, even if that means an extra OP_Copy. */ return sqlite3ExprCodeAtInit(pParse, pExpr, -1); Index: src/func.c ================================================================== --- src/func.c +++ src/func.c @@ -1511,10 +1511,11 @@ p->rSum += sqlite3_value_double(argv[0]); p->approx = 1; } } } +#ifndef SQLITE_OMIT_WINDOWFUNC static void sumInverse(sqlite3_context *context, int argc, sqlite3_value**argv){ SumCtx *p; int type; assert( argc==1 ); UNUSED_PARAMETER(argc); @@ -1532,10 +1533,13 @@ p->rSum += sqlite3_value_double(argv[0]); p->approx = 1; } } } +#else +# define sumInverse 0 +#endif /* SQLITE_OMIT_WINDOWFUNC */ static void sumFinalize(sqlite3_context *context){ SumCtx *p; p = sqlite3_aggregate_context(context, 0); if( p && p->cnt>0 ){ if( p->overflow ){ @@ -1644,13 +1648,17 @@ sqlite3_result_value(context, pRes); } if( bValue==0 ) sqlite3VdbeMemRelease(pRes); } } +#ifndef SQLITE_OMIT_WINDOWFUNC static void minMaxValue(sqlite3_context *context){ return minMaxValueFinalize(context, 1); } +#else +# define minMaxValue 0 +#endif /* SQLITE_OMIT_WINDOWFUNC */ static void minMaxFinalize(sqlite3_context *context){ return minMaxValueFinalize(context, 0); } /* @@ -1686,10 +1694,11 @@ zVal = (char*)sqlite3_value_text(argv[0]); nVal = sqlite3_value_bytes(argv[0]); if( zVal ) sqlite3_str_append(pAccum, zVal, nVal); } } +#ifndef SQLITE_OMIT_WINDOWFUNC static void groupConcatInverse( sqlite3_context *context, int argc, sqlite3_value **argv ){ @@ -1710,10 +1719,13 @@ memmove(pAccum->zText, &pAccum->zText[n], pAccum->nChar); } if( pAccum->nChar==0 ) pAccum->mxAlloc = 0; } } +#else +# define groupConcatInverse 0 +#endif /* SQLITE_OMIT_WINDOWFUNC */ static void groupConcatFinalize(sqlite3_context *context){ StrAccum *pAccum; pAccum = sqlite3_aggregate_context(context, 0); if( pAccum ){ if( pAccum->accError==SQLITE_TOOBIG ){ @@ -1724,10 +1736,11 @@ sqlite3_result_text(context, sqlite3StrAccumFinish(pAccum), -1, sqlite3_free); } } } +#ifndef SQLITE_OMIT_WINDOWFUNC static void groupConcatValue(sqlite3_context *context){ sqlite3_str *pAccum; pAccum = (sqlite3_str*)sqlite3_aggregate_context(context, 0); if( pAccum ){ if( pAccum->accError==SQLITE_TOOBIG ){ @@ -1738,10 +1751,13 @@ const char *zText = sqlite3_str_value(pAccum); sqlite3_result_text(context, zText, -1, SQLITE_TRANSIENT); } } } +#else +# define groupConcatValue 0 +#endif /* SQLITE_OMIT_WINDOWFUNC */ /* ** This routine does per-connection function registration. Most ** of the built-in functions above are part of the global function set. ** This routine only deals with those that are not global. Index: src/parse.y ================================================================== --- src/parse.y +++ src/parse.y @@ -527,21 +527,26 @@ multiselect_op(A) ::= UNION(OP). {A = @OP; /*A-overwrites-OP*/} multiselect_op(A) ::= UNION ALL. {A = TK_ALL;} multiselect_op(A) ::= EXCEPT|INTERSECT(OP). {A = @OP; /*A-overwrites-OP*/} %endif SQLITE_OMIT_COMPOUND_SELECT oneselect(A) ::= SELECT(S) distinct(D) selcollist(W) from(X) where_opt(Y) - groupby_opt(P) having_opt(Q) windowdefn_opt(R) + groupby_opt(P) having_opt(Q) +%ifndef SQLITE_OMIT_WINDOWFUNC + windowdefn_opt(R) +%endif orderby_opt(Z) limit_opt(L). { #if SELECTTRACE_ENABLED Token s = S; /*A-overwrites-S*/ #endif A = sqlite3SelectNew(pParse,W,X,Y,P,Q,Z,D,L); +#ifndef SQLITE_OMIT_WINDOWFUNC if( A ){ A->pWinDefn = R; }else{ sqlite3WindowListDelete(pParse->db, R); } +#endif // SQLITE_OMIT_WINDOWFUNC #if SELECTTRACE_ENABLED /* Populate the Select.zSelName[] string that is used to help with ** query planner debugging, to differentiate between multiple Select ** objects in a complex query. ** @@ -1009,27 +1014,37 @@ expr(A) ::= CAST LP expr(E) AS typetoken(T) RP. { A = sqlite3ExprAlloc(pParse->db, TK_CAST, &T, 1); sqlite3ExprAttachSubtrees(pParse->db, A, E, 0); } %endif SQLITE_OMIT_CAST -expr(A) ::= id(X) LP distinct(D) exprlist(Y) RP over_opt(Z). { +expr(A) ::= id(X) LP distinct(D) exprlist(Y) RP +%ifndef SQLITE_OMIT_WINDOWFUNC + over_opt(Z) +%endif +. { if( Y && Y->nExpr>pParse->db->aLimit[SQLITE_LIMIT_FUNCTION_ARG] ){ sqlite3ErrorMsg(pParse, "too many arguments on function %T", &X); } A = sqlite3ExprFunction(pParse, Y, &X); sqlite3WindowAttach(pParse, A, Z); if( D==SF_Distinct && A ){ A->flags |= EP_Distinct; } } -expr(A) ::= id(X) LP STAR RP over_opt(Z). { +expr(A) ::= id(X) LP STAR RP +%ifndef SQLITE_OMIT_WINDOWFUNC + over_opt(Z) +%endif +. { A = sqlite3ExprFunction(pParse, 0, &X); sqlite3WindowAttach(pParse, A, Z); } term(A) ::= CTIME_KW(OP). { A = sqlite3ExprFunction(pParse, 0, &OP); } + +%ifndef SQLITE_OMIT_WINDOWFUNC %type windowdefn_opt {Window*} %destructor windowdefn_opt {sqlite3WindowDelete(pParse->db, $$);} windowdefn_opt(A) ::= . { A = 0; } windowdefn_opt(A) ::= WINDOW windowdefn_list(B). { A = B; } @@ -1119,10 +1134,11 @@ frame_bound(A) ::= expr(X) PRECEDING. { A.eType = TK_PRECEDING; A.pExpr = X; } frame_bound(A) ::= CURRENT ROW. { A.eType = TK_CURRENT ; A.pExpr = 0; } frame_bound(A) ::= expr(X) FOLLOWING. { A.eType = TK_FOLLOWING; A.pExpr = X; } frame_bound(A) ::= UNBOUNDED FOLLOWING. { A.eType = TK_UNBOUNDED; A.pExpr = 0; } +%endif // SQLITE_OMIT_WINDOWFUNC expr(A) ::= LP nexprlist(X) COMMA expr(Y) RP. { ExprList *pList = sqlite3ExprListAppend(pParse, X, Y); A = sqlite3PExpr(pParse, TK_VECTOR, 0, 0); if( A ){ Index: src/resolve.c ================================================================== --- src/resolve.c +++ src/resolve.c @@ -755,10 +755,11 @@ notValid(pParse, pNC, "non-deterministic functions", NC_IdxExpr|NC_PartIdx); } } +#ifndef SQLITE_OMIT_WINDOWFUNC if( is_agg==0 && pExpr->pWin ){ sqlite3ErrorMsg(pParse, "%.*s() may not be used as a window function", nId, zId ); pNC->nErr++; @@ -771,10 +772,14 @@ if( (pDef->funcFlags & SQLITE_FUNC_WINDOW) || pExpr->pWin ){ zType = "window"; }else{ zType = "aggregate"; } +#else + if( (is_agg && (pNC->ncFlags & NC_AllowAgg)==0) ){ + const char *zType = "aggregate"; +#endif sqlite3ErrorMsg(pParse, "misuse of %s function %.*s()", zType, nId,zId); pNC->nErr++; is_agg = 0; }else if( no_such_func && pParse->db->init.busy==0 #ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION @@ -789,20 +794,23 @@ pNC->nErr++; } if( is_agg ) pNC->ncFlags &= ~NC_AllowAgg; sqlite3WalkExprList(pWalker, pList); if( is_agg ){ +#ifndef SQLITE_OMIT_WINDOWFUNC if( pExpr->pWin ){ Select *pSel = pNC->pWinSelect; sqlite3WindowUpdate(pParse, pSel->pWinDefn, pExpr->pWin, pDef); if( 0==pSel->pWin || 0==sqlite3WindowCompare(pParse, pSel->pWin, pExpr->pWin) ){ pExpr->pWin->pNextWin = pSel->pWin; pSel->pWin = pExpr->pWin; } - }else{ + }else +#endif /* SQLITE_OMIT_WINDOWFUNC */ + { NameContext *pNC2 = pNC; pExpr->op = TK_AGG_FUNCTION; pExpr->op2 = 0; while( pNC2 && !sqlite3FunctionUsesThisSrc(pExpr, pNC2->pSrcList) ){ pExpr->op2++; @@ -1262,11 +1270,10 @@ isCompound = p->pPrior!=0; nCompound = 0; pLeftmost = p; while( p ){ - assert( p->pWin==0 ); assert( (p->selFlags & SF_Expanded)!=0 ); assert( (p->selFlags & SF_Resolved)==0 ); p->selFlags |= SF_Resolved; /* Resolve the expressions in the LIMIT and OFFSET clauses. These Index: src/select.c ================================================================== --- src/select.c +++ src/select.c @@ -94,13 +94,15 @@ sqlite3ExprDelete(db, p->pWhere); sqlite3ExprListDelete(db, p->pGroupBy); sqlite3ExprDelete(db, p->pHaving); sqlite3ExprListDelete(db, p->pOrderBy); sqlite3ExprDelete(db, p->pLimit); +#ifndef SQLITE_OMIT_WINDOWFUNC if( OK_IF_ALWAYS_TRUE(p->pWinDefn) ){ sqlite3WindowListDelete(db, p->pWinDefn); } +#endif if( OK_IF_ALWAYS_TRUE(p->pWith) ) sqlite3WithDelete(db, p->pWith); if( bFree ) sqlite3DbFreeNN(db, p); p = pPrior; bFree = 1; } @@ -163,12 +165,14 @@ pNew->pOrderBy = pOrderBy; pNew->pPrior = 0; pNew->pNext = 0; pNew->pLimit = pLimit; pNew->pWith = 0; +#ifndef SQLITE_OMIT_WINDOWFUNC pNew->pWin = 0; pNew->pWinDefn = 0; +#endif if( pParse->db->mallocFailed ) { clearSelect(pParse->db, pNew, pNew!=&standin); pNew = 0; }else{ assert( pNew->pSrc!=0 || pParse->nErr>0 ); @@ -3718,11 +3722,13 @@ pSubitem = &pSrc->a[iFrom]; iParent = pSubitem->iCursor; pSub = pSubitem->pSelect; assert( pSub!=0 ); +#ifndef SQLITE_OMIT_WINDOWFUNC if( p->pWin || pSub->pWin ) return 0; /* Restriction (25) */ +#endif pSubSrc = pSub->pSrc; assert( pSubSrc ); /* Prior to version 3.1.2, when LIMIT and OFFSET had to be simple constants, ** not arbitrary expressions, we allowed some combining of LIMIT and OFFSET @@ -5485,19 +5491,21 @@ if( pDest->eDest==SRT_Output ){ generateColumnNames(pParse, p); } - if( (rc = sqlite3WindowRewrite(pParse, p)) ){ +#ifndef SQLITE_OMIT_WINDOWFUNC + if( sqlite3WindowRewrite(pParse, p) ){ goto select_end; } #if SELECTTRACE_ENABLED if( sqlite3SelectTrace & 0x108 ){ SELECTTRACE(0x104,pParse,p, ("after window rewrite:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif +#endif /* SQLITE_OMIT_WINDOWFUNC */ pTabList = p->pSrc; isAgg = (p->selFlags & SF_Aggregate)!=0; /* Try to various optimizations (flattening subqueries, and strength ** reduction of join operators) in the FROM clause up into the main query @@ -5871,20 +5879,21 @@ }else{ sDistinct.eTnctType = WHERE_DISTINCT_NOOP; } if( !isAgg && pGroupBy==0 ){ - Window *pWin = p->pWin; /* Master window object (or NULL) */ - /* No aggregate functions and no GROUP BY clause */ - u16 wctrlFlags = (sDistinct.isTnct ? WHERE_WANT_DISTINCT : 0); - assert( WHERE_USE_LIMIT==SF_FixedLimit ); - wctrlFlags |= p->selFlags & SF_FixedLimit; - + u16 wctrlFlags = (sDistinct.isTnct ? WHERE_WANT_DISTINCT : 0) + | (p->selFlags & SF_FixedLimit); +#ifndef SQLITE_OMIT_WINDOWFUNC + Window *pWin = p->pWin; /* Master window object (or NULL) */ if( pWin ){ sqlite3WindowCodeInit(pParse, pWin); } +#endif + assert( WHERE_USE_LIMIT==SF_FixedLimit ); + /* Begin the database scan. */ SELECTTRACE(1,pParse,p,("WhereBegin\n")); pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, sSort.pOrderBy, p->pEList, wctrlFlags, p->nSelectRow); @@ -5910,10 +5919,11 @@ if( sSort.addrSortIndex>=0 && sSort.pOrderBy==0 ){ sqlite3VdbeChangeToNoop(v, sSort.addrSortIndex); } assert( p->pEList==pEList ); +#ifndef SQLITE_OMIT_WINDOWFUNC if( pWin ){ int addrGosub = sqlite3VdbeMakeLabel(v); int iCont = sqlite3VdbeMakeLabel(v); int regGosub = ++pParse->nMem; int addr = 0; @@ -5925,11 +5935,13 @@ selectInnerLoop(pParse, p, -1, &sSort, &sDistinct, pDest, iCont, 0); sqlite3VdbeResolveLabel(v, iCont); sqlite3VdbeAddOp1(v, OP_Return, regGosub); sqlite3VdbeJumpHere(v, addr); - }else{ + }else +#endif /* SQLITE_OMIT_WINDOWFUNC */ + { /* Use the standard inner loop. */ selectInnerLoop(pParse, p, -1, &sSort, &sDistinct, pDest, sqlite3WhereContinueLabel(pWInfo), sqlite3WhereBreakLabel(pWInfo)); Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -2464,11 +2464,13 @@ ** TK_COLUMN: the value of p5 for OP_Column ** TK_AGG_FUNCTION: nesting depth */ AggInfo *pAggInfo; /* Used by TK_AGG_COLUMN and TK_AGG_FUNCTION */ Table *pTab; /* Table for TK_COLUMN expressions. Can be NULL ** for a column of an index on an expression */ +#ifndef SQLITE_OMIT_WINDOWFUNC Window *pWin; /* Window definition for window functions */ +#endif }; /* ** The following are the meanings of bits in the Expr.flags field. */ @@ -2817,12 +2819,14 @@ ExprList *pOrderBy; /* The ORDER BY clause */ Select *pPrior; /* Prior select in a compound select statement */ Select *pNext; /* Next select to the left in a compound */ Expr *pLimit; /* LIMIT expression. NULL means not used. */ With *pWith; /* WITH clause attached to this select. Or NULL. */ +#ifndef SQLITE_OMIT_WINDOWFUNC Window *pWin; /* List of window functions */ Window *pWinDefn; /* List of named window definitions */ +#endif }; /* ** Allowed values for Select.selFlags. The "SF" prefix stands for ** "Select Flag". @@ -3515,10 +3519,11 @@ 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 */ }; +#ifndef SQLITE_OMIT_WINDOWFUNC void sqlite3WindowDelete(sqlite3*, Window*); void sqlite3WindowListDelete(sqlite3 *db, Window *p); Window *sqlite3WindowAlloc(Parse*, int, int, Expr*, int , Expr*); void sqlite3WindowAttach(Parse*, Expr*, Window*); int sqlite3WindowCompare(Parse*, Window*, Window*); @@ -3528,10 +3533,15 @@ int sqlite3ExpandSubquery(Parse*, struct SrcList_item*); void sqlite3WindowUpdate(Parse*, Window*, Window*, FuncDef*); Window *sqlite3WindowDup(sqlite3 *db, Expr *pOwner, Window *p); Window *sqlite3WindowListDup(sqlite3 *db, Window *p); void sqlite3WindowFunctions(void); +#else +# define sqlite3WindowDelete(a,b) +# define sqlite3WindowFunctions() +# define sqlite3WindowAttach(a,b,c) +#endif /* ** Assuming zIn points to the first byte of a UTF-8 character, ** advance zIn to point to the first byte of the next UTF-8 character. */ Index: src/test_config.c ================================================================== --- src/test_config.c +++ src/test_config.c @@ -759,10 +759,16 @@ #ifdef SQLITE_ENABLE_URI_00_ERROR Tcl_SetVar2(interp, "sqlite_options", "uri_00_error", "1", TCL_GLOBAL_ONLY); #else Tcl_SetVar2(interp, "sqlite_options", "uri_00_error", "0", TCL_GLOBAL_ONLY); #endif + +#ifdef SQLITE_OMIT_WINDOWFUNC + Tcl_SetVar2(interp, "sqlite_options", "windowfunc", "0", TCL_GLOBAL_ONLY); +#else + Tcl_SetVar2(interp, "sqlite_options", "windowfunc", "1", TCL_GLOBAL_ONLY); +#endif #define LINKVAR(x) { \ static const int cv_ ## x = SQLITE_ ## x; \ Tcl_LinkVar(interp, "SQLITE_" #x, (char *)&(cv_ ## x), \ TCL_LINK_INT | TCL_LINK_READ_ONLY); } Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -5116,11 +5116,13 @@ }else{ assert( pC->eCurType==CURTYPE_BTREE ); pCrsr = pC->uc.pCursor; assert( pCrsr ); rc = sqlite3BtreeFirst(pCrsr, &res); +#ifndef SQLITE_OMIT_WINDOWFUNC if( pOp->p5 ) sqlite3BtreeSkipNext(pCrsr); +#endif pC->deferredMoveto = 0; pC->cacheStatus = CACHE_STALE; } if( rc ) goto abort_due_to_error; pC->nullRow = (u8)res; @@ -6367,12 +6369,17 @@ pMem->n++; assert( pCtx->pOut->flags==MEM_Null ); assert( pCtx->isError==0 ); assert( pCtx->skipFlag==0 ); - (pOp->p1 ? (pCtx->pFunc->xInverse) : (pCtx->pFunc->xSFunc)) - (pCtx,pCtx->argc,pCtx->argv); /* IMP: R-24505-23230 */ +#ifndef SQLITE_OMIT_WINDOWFUNC + if( pOp->p1 ){ + (pCtx->pFunc->xInverse)(pCtx,pCtx->argc,pCtx->argv); + }else +#endif + (pCtx->pFunc->xSFunc)(pCtx,pCtx->argc,pCtx->argv); /* IMP: R-24505-23230 */ + if( pCtx->isError ){ if( pCtx->isError>0 ){ sqlite3VdbeError(p, "%s", sqlite3_value_text(pCtx->pOut)); rc = pCtx->isError; } @@ -6410,16 +6417,18 @@ case OP_AggFinal: { Mem *pMem; assert( pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor) ); pMem = &aMem[pOp->p1]; assert( (pMem->flags & ~(MEM_Null|MEM_Agg))==0 ); +#ifndef SQLITE_OMIT_WINDOWFUNC if( pOp->p3 ){ rc = sqlite3VdbeMemAggValue(pMem, &aMem[pOp->p3], pOp->p4.pFunc); pMem = &aMem[pOp->p3]; - }else{ - rc = sqlite3VdbeMemFinalize(pMem, pOp->p4.pFunc); - } + }else +#endif + rc = sqlite3VdbeMemFinalize(pMem, pOp->p4.pFunc); + if( rc ){ sqlite3VdbeError(p, "%s", sqlite3_value_text(pMem)); goto abort_due_to_error; } sqlite3VdbeChangeEncoding(pMem, encoding); Index: src/vdbeInt.h ================================================================== --- src/vdbeInt.h +++ src/vdbeInt.h @@ -495,11 +495,13 @@ int sqlite3VdbeMemNumerify(Mem*); void sqlite3VdbeMemCast(Mem*,u8,u8); int sqlite3VdbeMemFromBtree(BtCursor*,u32,u32,Mem*); void sqlite3VdbeMemRelease(Mem *p); int sqlite3VdbeMemFinalize(Mem*, FuncDef*); +#ifndef SQLITE_OMIT_WINDOWFUNC int sqlite3VdbeMemAggValue(Mem*, Mem*, FuncDef*); +#endif const char *sqlite3OpcodeName(int); int sqlite3VdbeMemGrow(Mem *pMem, int n, int preserve); int sqlite3VdbeMemClearAndResize(Mem *pMem, int n); int sqlite3VdbeCloseStatement(Vdbe *, int); void sqlite3VdbeFrameDelete(VdbeFrame*); Index: src/vdbemem.c ================================================================== --- src/vdbemem.c +++ src/vdbemem.c @@ -421,10 +421,11 @@ ** the results in memory cell pMem. ** ** SQLITE_ERROR is returned if xValue() reports an error. SQLITE_OK ** otherwise. */ +#ifndef SQLITE_OMIT_WINDOWFUNC int sqlite3VdbeMemAggValue(Mem *pAccum, Mem *pOut, FuncDef *pFunc){ sqlite3_context ctx; Mem t; assert( pFunc!=0 ); assert( pFunc->xValue!=0 ); @@ -438,10 +439,11 @@ ctx.pMem = pAccum; ctx.pFunc = pFunc; pFunc->xValue(&ctx); return ctx.isError; } +#endif /* SQLITE_OMIT_WINDOWFUNC */ /* ** If the memory cell contains a value that must be freed by ** invoking the external callback in Mem.xDel, then this routine ** will free that value. It also sets Mem.flags to MEM_Null. Index: src/window.c ================================================================== --- src/window.c +++ src/window.c @@ -10,10 +10,12 @@ ** ************************************************************************* */ #include "sqliteInt.h" +#ifndef SQLITE_OMIT_WINDOWFUNC + /* ** SELECT REWRITING ** ** Any SELECT statement that contains one or more window functions in ** either the select list or ORDER BY clause (the only two places window @@ -2064,5 +2066,6 @@ windowCodeDefaultStep(pParse, p, pWInfo, regGosub, addrGosub); } } } +#endif /* SQLITE_OMIT_WINDOWFUNC */ Index: test/pg_common.tcl ================================================================== --- test/pg_common.tcl +++ test/pg_common.tcl @@ -128,6 +128,10 @@ proc finish_test {} { puts $::fd finish_test close $::fd } + +proc ifcapable {arg} { + puts $::fd "ifcapable $arg { finish_test ; return }" +} Index: test/window1.test ================================================================== --- test/window1.test +++ test/window1.test @@ -13,10 +13,14 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix window1 +ifcapable !windowfunc { + finish_test + return +} do_execsql_test 1.0 { CREATE TABLE t1(a, b, c, d); INSERT INTO t1 VALUES(1, 2, 3, 4); INSERT INTO t1 VALUES(5, 6, 7, 8); Index: test/window2.tcl ================================================================== --- test/window2.tcl +++ test/window2.tcl @@ -14,10 +14,12 @@ #========================================================================= start_test window2 "2018 May 19" + +ifcapable !windowfunc execsql_test 1.0 { DROP TABLE IF EXISTS t1; CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT, c TEXT, d INTEGER); INSERT INTO t1 VALUES(1, 'odd', 'one', 1); Index: test/window2.test ================================================================== --- test/window2.test +++ test/window2.test @@ -17,10 +17,11 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix window2 +ifcapable !windowfunc { finish_test ; return } do_execsql_test 1.0 { DROP TABLE IF EXISTS t1; CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT, c TEXT, d INTEGER); INSERT INTO t1 VALUES(1, 'odd', 'one', 1); INSERT INTO t1 VALUES(2, 'even', 'two', 2); Index: test/window3.tcl ================================================================== --- test/window3.tcl +++ test/window3.tcl @@ -13,10 +13,11 @@ source [file join [file dirname $argv0] pg_common.tcl] #========================================================================= start_test window3 "2018 May 31" +ifcapable !windowfunc execsql_test 1.0 { DROP TABLE IF EXISTS t2; CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER); INSERT INTO t2(a, b) VALUES Index: test/window3.test ================================================================== --- test/window3.test +++ test/window3.test cannot compute difference between binary files Index: test/window4.tcl ================================================================== --- test/window4.tcl +++ test/window4.tcl @@ -13,10 +13,11 @@ source [file join [file dirname $argv0] pg_common.tcl] #========================================================================= start_test window4 "2018 June 04" +ifcapable !windowfunc execsql_test 1.0 { DROP TABLE IF EXISTS t3; CREATE TABLE t3(a TEXT PRIMARY KEY); INSERT INTO t3 VALUES('a'), ('b'), ('c'), ('d'), ('e'); Index: test/window4.test ================================================================== --- test/window4.test +++ test/window4.test @@ -17,10 +17,11 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix window4 +ifcapable !windowfunc { finish_test ; return } do_execsql_test 1.0 { DROP TABLE IF EXISTS t3; CREATE TABLE t3(a TEXT PRIMARY KEY); INSERT INTO t3 VALUES('a'), ('b'), ('c'), ('d'), ('e'); INSERT INTO t3 VALUES('f'), ('g'), ('h'), ('i'), ('j'); Index: test/window5.test ================================================================== --- test/window5.test +++ test/window5.test @@ -12,11 +12,16 @@ # it tests the sqlite3_create_window_function() API. # set testdir [file dirname $argv0] source $testdir/tester.tcl -set testprefix window1 +set testprefix window5 + +ifcapable !windowfunc { + finish_test + return +} proc m_step {ctx val} { lappend ctx $val return $ctx } Index: test/windowfault.test ================================================================== --- test/windowfault.test +++ test/windowfault.test @@ -13,10 +13,14 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix windowfault +ifcapable !windowfunc { + finish_test + return +} do_execsql_test 1.0 { CREATE TABLE t1(a, b, c, d); INSERT INTO t1 VALUES(1, 2, 3, 4); INSERT INTO t1 VALUES(5, 6, 7, 8); Index: tool/mkkeywordhash.c ================================================================== --- tool/mkkeywordhash.c +++ tool/mkkeywordhash.c @@ -146,10 +146,15 @@ #ifdef SQLITE_OMIT_UPSERT # define UPSERT 0 #else # define UPSERT 0x00080000 #endif +#ifdef SQLITE_OMIT_WINDOWFUNC +# define WINDOWFUNC 0 +#else +# define WINDOWFUNC 0x00100000 +#endif /* ** These are the keywords */ static Keyword aKeywordTable[] = { @@ -178,11 +183,11 @@ { "COMMIT", "TK_COMMIT", ALWAYS }, { "CONFLICT", "TK_CONFLICT", CONFLICT }, { "CONSTRAINT", "TK_CONSTRAINT", ALWAYS }, { "CREATE", "TK_CREATE", ALWAYS }, { "CROSS", "TK_JOIN_KW", ALWAYS }, - { "CURRENT", "TK_CURRENT", ALWAYS }, + { "CURRENT", "TK_CURRENT", WINDOWFUNC }, { "CURRENT_DATE", "TK_CTIME_KW", ALWAYS }, { "CURRENT_TIME", "TK_CTIME_KW", ALWAYS }, { "CURRENT_TIMESTAMP","TK_CTIME_KW", ALWAYS }, { "DATABASE", "TK_DATABASE", ATTACH }, { "DEFAULT", "TK_DEFAULT", ALWAYS }, @@ -201,12 +206,12 @@ { "EXCEPT", "TK_EXCEPT", COMPOUND }, { "EXCLUSIVE", "TK_EXCLUSIVE", ALWAYS }, { "EXISTS", "TK_EXISTS", ALWAYS }, { "EXPLAIN", "TK_EXPLAIN", EXPLAIN }, { "FAIL", "TK_FAIL", CONFLICT|TRIGGER }, - { "FILTER", "TK_FILTER", ALWAYS }, - { "FOLLOWING", "TK_FOLLOWING", ALWAYS }, + { "FILTER", "TK_FILTER", WINDOWFUNC }, + { "FOLLOWING", "TK_FOLLOWING", WINDOWFUNC }, { "FOR", "TK_FOR", TRIGGER }, { "FOREIGN", "TK_FOREIGN", FKEY }, { "FROM", "TK_FROM", ALWAYS }, { "FULL", "TK_JOIN_KW", ALWAYS }, { "GLOB", "TK_LIKE_KW", ALWAYS }, @@ -242,19 +247,19 @@ { "OFFSET", "TK_OFFSET", ALWAYS }, { "ON", "TK_ON", ALWAYS }, { "OR", "TK_OR", ALWAYS }, { "ORDER", "TK_ORDER", ALWAYS }, { "OUTER", "TK_JOIN_KW", ALWAYS }, - { "OVER", "TK_OVER", ALWAYS }, - { "PARTITION", "TK_PARTITION", ALWAYS }, + { "OVER", "TK_OVER", WINDOWFUNC }, + { "PARTITION", "TK_PARTITION", WINDOWFUNC }, { "PLAN", "TK_PLAN", EXPLAIN }, { "PRAGMA", "TK_PRAGMA", PRAGMA }, - { "PRECEDING", "TK_PRECEDING", ALWAYS }, + { "PRECEDING", "TK_PRECEDING", WINDOWFUNC }, { "PRIMARY", "TK_PRIMARY", ALWAYS }, { "QUERY", "TK_QUERY", EXPLAIN }, { "RAISE", "TK_RAISE", TRIGGER }, - { "RANGE", "TK_RANGE", ALWAYS }, + { "RANGE", "TK_RANGE", WINDOWFUNC }, { "RECURSIVE", "TK_RECURSIVE", CTE }, { "REFERENCES", "TK_REFERENCES", FKEY }, { "REGEXP", "TK_LIKE_KW", ALWAYS }, { "REINDEX", "TK_REINDEX", REINDEX }, { "RELEASE", "TK_RELEASE", ALWAYS }, @@ -273,20 +278,20 @@ { "TEMPORARY", "TK_TEMP", ALWAYS }, { "THEN", "TK_THEN", ALWAYS }, { "TO", "TK_TO", ALWAYS }, { "TRANSACTION", "TK_TRANSACTION", ALWAYS }, { "TRIGGER", "TK_TRIGGER", TRIGGER }, - { "UNBOUNDED", "TK_UNBOUNDED", ALWAYS }, + { "UNBOUNDED", "TK_UNBOUNDED", WINDOWFUNC }, { "UNION", "TK_UNION", COMPOUND }, { "UNIQUE", "TK_UNIQUE", ALWAYS }, { "UPDATE", "TK_UPDATE", ALWAYS }, { "USING", "TK_USING", ALWAYS }, { "VACUUM", "TK_VACUUM", VACUUM }, { "VALUES", "TK_VALUES", ALWAYS }, { "VIEW", "TK_VIEW", VIEW }, { "VIRTUAL", "TK_VIRTUAL", VTAB }, - { "WINDOW", "TK_WINDOW", ALWAYS }, + { "WINDOW", "TK_WINDOW", WINDOWFUNC }, { "WITH", "TK_WITH", CTE }, { "WITHOUT", "TK_WITHOUT", ALWAYS }, { "WHEN", "TK_WHEN", ALWAYS }, { "WHERE", "TK_WHERE", ALWAYS }, };