diff --git a/manifest b/manifest index 5d19b5ec92..f47dcae0fb 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C added\sIN\sand\sBETWEEN\soperators\s(CVS\s57) -D 2000-06-06T13:54:15 +C GROUP\sBY\sand\sHAVING\sinstalled\s(CVS\s58) +D 2000-06-06T17:27:05 F COPYRIGHT 74a8a6531a42e124df07ab5599aad63870fa0bd4 F Makefile.in 17ba1ccf8d2d40c627796bba8f72952365d6d644 F README 51f6a4e7408b34afa5bc1c0485f61b6a4efb6958 @@ -10,19 +10,19 @@ F src/build.c 6c9454b2e2b866979527fb41b19ad8bc49c27a20 F src/dbbe.c ae8b5d2cdb4fa7dd11313059984be9457fa77f63 F src/dbbe.h a8a46f71238e0f09f3ec08fd9d1c8c7f4cdc49bf F src/delete.c 8c733bb82a1b84126116d03dcdccf433c0856f5d -F src/expr.c 1bedf5f426ee1e1609ef1758985b7ce0581987b8 +F src/expr.c d350fe393e1753aaa733a5d21f0830a23e547400 F src/insert.c 5e69dd70c3f91cf5ec5090f39fd6cd8e135af9bf F src/main.c 93a7ad14bb5a82ad13ad59da23ef674a94b0c3d6 -F src/parse.y 51ef63a49e73ced4ef3e81d7b1f9fd825d776837 -F src/select.c a1891cfce003a98a57374c01fa5875366c8d9be2 +F src/parse.y 210c888c052f3fde3def6ff1f402e4b4e8ba270d +F src/select.c 77906ffaae962e49acc9a0c0c39cb710f06e56d8 F src/shell.c 5fa24c0bb678782ffe9070128e3e160674f297eb F src/sqlite.h 58da0a8590133777b741f9836beaef3d58f40268 -F src/sqliteInt.h 821b435a18e1c9d0fddcd7bfeefcf5f3fe925c6e +F src/sqliteInt.h 267f66d4a851e19adb0f9a3050a3acb62fc70cae F src/tclsqlite.c 9f358618ae803bedf4fb96da5154fd45023bc1f7 -F src/tokenize.c f190e16ebb82dd60497796022f1e56e2e0527975 +F src/tokenize.c 32f0579d4a7276b8e3bfd353cac034f67976aa82 F src/update.c 18746f920f989b3d19d96c08263c92584823cd35 F src/util.c 33f9baa01e45394ef0cf85361a0e872987884315 -F src/vdbe.c 1ab61ada503b99d6b3224c9d40ed9bac855fe317 +F src/vdbe.c 7a4909b44a58a81e03dfc9376b926d5e8f773fcc F src/vdbe.h 49e0f9c6742860e224cd81d1278059f5d029dfb6 F src/where.c c9b90e7672f4662a83ef9a27a193020d69fe034c F test/all.test 0950c135cab7e60c07bd745ccfad1476211e5bd7 @@ -32,7 +32,7 @@ F test/expr.test 52be5592143a88479e0006dfd7e2023e43294636 F test/in.test 17cd46a9ca0e5d4a804483e6fb496458494858e6 F test/index.test 9f99dca2d904b8de330863a978587f136e2df65a F test/insert.test b4c186ffa4b97a231643726f3bcee29815b24eaf -F test/select1.test a0b00df77e85adff75c338e487718c5d31f69e3a +F test/select1.test 2311bddd40bca257c27a7d141ed2a359bbdbc906 F test/select2.test 3cd3c0f9d67e98b1b54af5853679b4a111224410 F test/subselect.test bf8b251a92fb091973c1c469ce499dc9648a41d5 F test/table.test 85d6f410d127ec508c6640f02d7c40d218414e81 @@ -49,7 +49,7 @@ F www/c_interface.tcl 8867d76ddd416d2fbd41e4cb3de8efa9cef105a5 F www/changes.tcl 567cc6066d87460bdedff8e5bbc20f41ddaadf77 F www/index.tcl f8189a7898f6d06307c34047b9d7e00860026e44 F www/sqlite.tcl 2f933ce18cffd34a0a020a82435ab937137970fd -P b52dd82fe32c38c999aef4f07d046d0428336965 -R d1b48bd641877701893f6074a4e397f8 +P 54d198189b58366e4e40139102bc6de94ac55e18 +R dcd329d5daa579a072c3c6f09022ae9f U drh -Z 3b39b6691fb1f454562ec994b26db409 +Z 6732a1b7035c88b38d7888f45d2d7142 diff --git a/manifest.uuid b/manifest.uuid index 247d360575..586e4fee4e 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -54d198189b58366e4e40139102bc6de94ac55e18 \ No newline at end of file +db88a0c2d4b5c5cd05e0172f061fc33763fe3829 \ No newline at end of file diff --git a/src/expr.c b/src/expr.c index ab07a54f63..2870a484d9 100644 --- a/src/expr.c +++ b/src/expr.c @@ -23,7 +23,7 @@ ************************************************************************* ** This file contains C code routines used for processing expressions ** -** $Id: expr.c,v 1.9 2000/06/06 13:54:15 drh Exp $ +** $Id: expr.c,v 1.10 2000/06/06 17:27:05 drh Exp $ */ #include "sqliteInt.h" @@ -326,6 +326,7 @@ int sqliteFuncId(Token *pToken){ { "min", 3, FN_Min }, { "max", 3, FN_Max }, { "sum", 3, FN_Sum }, + { "avg", 3, FN_Avg }, }; int i; for(i=0; iop ){ case TK_FUNCTION: { int id = sqliteFuncId(&pExpr->token); @@ -359,6 +359,7 @@ int sqliteExprCheck(Parse *pParse, Expr *pExpr, int allowAgg, int *pIsAgg){ int too_few_args = 0; int is_agg = 0; int i; + pExpr->iField = id; switch( id ){ case FN_Unknown: { no_such_func = 1; @@ -376,6 +377,7 @@ int sqliteExprCheck(Parse *pParse, Expr *pExpr, int allowAgg, int *pIsAgg){ is_agg = n==1; break; } + case FN_Avg: case FN_Sum: { no_such_func = !allowAgg; too_many_args = n>1; @@ -401,6 +403,7 @@ int sqliteExprCheck(Parse *pParse, Expr *pExpr, int allowAgg, int *pIsAgg){ pParse->nErr++; nErr++; } + if( is_agg ) pExpr->op = TK_AGG_FUNCTION; if( is_agg && pIsAgg ) *pIsAgg = 1; for(i=0; nErr==0 && ipList->a[i].pExpr, 0, 0); @@ -408,16 +411,17 @@ int sqliteExprCheck(Parse *pParse, Expr *pExpr, int allowAgg, int *pIsAgg){ } default: { if( pExpr->pLeft ){ - nErr = sqliteExprCheck(pParse, pExpr->pLeft, 0, 0); + nErr = sqliteExprCheck(pParse, pExpr->pLeft, allowAgg, pIsAgg); } if( nErr==0 && pExpr->pRight ){ - nErr = sqliteExprCheck(pParse, pExpr->pRight, 0, 0); + nErr = sqliteExprCheck(pParse, pExpr->pRight, allowAgg, pIsAgg); } if( nErr==0 && pExpr->pList ){ int n = pExpr->pList->nExpr; int i; for(i=0; nErr==0 && ipList->a[i].pExpr, 0, 0); + Expr *pE2 = pExpr->pList->a[i].pExpr; + nErr = sqliteExprCheck(pParse, pE2, allowAgg, pIsAgg); } } break; @@ -456,7 +460,11 @@ void sqliteExprCode(Parse *pParse, Expr *pExpr){ } switch( pExpr->op ){ case TK_FIELD: { - sqliteVdbeAddOp(v, OP_Field, pExpr->iTable, pExpr->iField, 0, 0); + if( pParse->useAgg ){ + sqliteVdbeAddOp(v, OP_AggGet, 0, pExpr->iAgg, 0, 0); + }else{ + sqliteVdbeAddOp(v, OP_Field, pExpr->iTable, pExpr->iField, 0, 0); + } break; } case TK_INTEGER: { @@ -523,8 +531,17 @@ void sqliteExprCode(Parse *pParse, Expr *pExpr){ sqliteVdbeAddOp(v, OP_AddImm, -1, 0, 0, 0); break; } + case TK_AGG_FUNCTION: { + sqliteVdbeAddOp(v, OP_AggGet, 0, pExpr->iAgg, 0, 0); + if( pExpr->iField==FN_Avg ){ + assert( pParse->iAggCount>=0 && pParse->iAggCountnAgg ); + sqliteVdbeAddOp(v, OP_AggGet, 0, pParse->iAggCount, 0, 0); + sqliteVdbeAddOp(v, OP_Divide, 0, 0, 0, 0); + } + break; + } case TK_FUNCTION: { - int id = sqliteFuncId(&pExpr->token); + int id = pExpr->iField; int op; int i; ExprList *pList = pExpr->pList; @@ -744,3 +761,142 @@ void sqliteExprIfFalse(Parse *pParse, Expr *pExpr, int dest){ } } } + +/* +** Do a deep comparison of two expression trees. Return TRUE (non-zero) +** if they are identical and return FALSE if they differ in any way. +*/ +static int exprDeepCompare(Expr *pA, Expr *pB){ + int i; + if( pA==0 ){ + return pB==0; + }else if( pB==0 ){ + return 0; + } + if( pA->op!=pB->op ) return 0; + if( !exprDeepCompare(pA->pLeft, pB->pLeft) ) return 0; + if( !exprDeepCompare(pA->pRight, pB->pRight) ) return 0; + if( pA->pList ){ + if( pB->pList==0 ) return 0; + if( pA->pList->nExpr!=pB->pList->nExpr ) return 0; + for(i=0; ipList->nExpr; i++){ + if( !exprDeepCompare(pA->pList->a[i].pExpr, pB->pList->a[i].pExpr) ){ + return 0; + } + } + }else if( pB->pList ){ + return 0; + } + if( pA->pSelect || pB->pSelect ) return 0; + if( pA->token.z ){ + if( pB->token.z==0 ) return 0; + if( pB->token.n!=pA->token.n ) return 0; + if( sqliteStrNICmp(pA->token.z, pB->token.z, pA->token.n)!=0 ) return 0; + } + return 1; +} + +/* +** Add a new element to the pParse->aAgg[] array and return its index. +*/ +static int appendAggInfo(Parse *pParse){ + if( (pParse->nAgg & 0x7)==0 ){ + int amt = pParse->nAgg + 8; + pParse->aAgg = sqliteRealloc(pParse->aAgg, amt*sizeof(pParse->aAgg[0])); + if( pParse->aAgg==0 ){ + sqliteSetString(&pParse->zErrMsg, "out of memory", 0); + pParse->nErr++; + return -1; + } + } + memset(&pParse->aAgg[pParse->nAgg], 0, sizeof(pParse->aAgg[0])); + return pParse->nAgg++; +} + +/* +** Analyze the given expression looking for aggregate functions and +** for variables that need to be added to the pParse->aAgg[] array. +** Make additional entries to the pParse->aAgg[] array as necessary. +** +** This routine should only be called after the expression has been +** analyzed by sqliteExprResolveIds() and sqliteExprCheck(). +** +** If errors are seen, leave an error message in zErrMsg and return +** the number of errors. +*/ +int sqliteExprAnalyzeAggregates(Parse *pParse, Expr *pExpr){ + int i; + AggExpr *aAgg; + int nErr = 0; + + if( pExpr==0 ) return 0; + switch( pExpr->op ){ + case TK_FIELD: { + aAgg = pParse->aAgg; + for(i=0; inAgg; i++){ + if( aAgg[i].isAgg ) continue; + if( aAgg[i].pExpr->iTable==pExpr->iTable + && aAgg[i].pExpr->iField==pExpr->iField ){ + pExpr->iAgg = i; + break; + } + } + if( i>=pParse->nAgg ){ + i = appendAggInfo(pParse); + if( i<0 ) return 1; + pParse->aAgg[i].isAgg = 0; + pParse->aAgg[i].pExpr = pExpr; + } + break; + } + case TK_AGG_FUNCTION: { + if( pExpr->iField==FN_Count || pExpr->iField==FN_Avg ){ + if( pParse->iAggCount>=0 ){ + i = pParse->iAggCount; + }else{ + i = appendAggInfo(pParse); + if( i<0 ) return 1; + pParse->aAgg[i].isAgg = 1; + pParse->aAgg[i].pExpr = 0; + pParse->iAggCount = i; + } + if( pExpr->iField==FN_Count ){ + pExpr->iAgg = i; + break; + } + } + aAgg = pParse->aAgg; + for(i=0; inAgg; i++){ + if( !aAgg[i].isAgg ) continue; + if( exprDeepCompare(aAgg[i].pExpr, pExpr) ){ + break; + } + } + if( i>=pParse->nAgg ){ + i = appendAggInfo(pParse); + if( i<0 ) return 1; + pParse->aAgg[i].isAgg = 1; + pParse->aAgg[i].pExpr = pExpr; + } + pExpr->iAgg = i; + break; + } + default: { + if( pExpr->pLeft ){ + nErr = sqliteExprAnalyzeAggregates(pParse, pExpr->pLeft); + } + if( nErr==0 && pExpr->pRight ){ + nErr = sqliteExprAnalyzeAggregates(pParse, pExpr->pRight); + } + if( nErr==0 && pExpr->pList ){ + int n = pExpr->pList->nExpr; + int i; + for(i=0; nErr==0 && ipList->a[i].pExpr); + } + } + break; + } + } + return nErr; +} diff --git a/src/parse.y b/src/parse.y index ab16c8b4cf..082ac270ca 100644 --- a/src/parse.y +++ b/src/parse.y @@ -26,7 +26,7 @@ ** the parser. Lemon will also generate a header file containing ** numeric codes for all of the tokens. ** -** @(#) $Id: parse.y,v 1.12 2000/06/06 13:54:15 drh Exp $ +** @(#) $Id: parse.y,v 1.13 2000/06/06 17:27:05 drh Exp $ */ %token_prefix TK_ %token_type {Token} @@ -51,7 +51,7 @@ input ::= cmdlist. // add them to the sqliteTokens.h output file. // input ::= END_OF_FILE ILLEGAL SPACE UNCLOSED_STRING COMMENT FUNCTION - UMINUS FIELD. + UMINUS FIELD AGG_FUNCTION. // A list of commands is zero or more commands // @@ -141,8 +141,8 @@ cmd ::= select(X). { %destructor select {sqliteSelectDelete($$);} select(A) ::= SELECT distinct(D) selcollist(W) from(X) where_opt(Y) - orderby_opt(Z). { - A = sqliteSelectNew(W,X,Y,0,0,Z,D); + groupby_opt(P) having_opt(Q) orderby_opt(Z). { + A = sqliteSelectNew(W,X,Y,P,Q,Z,D); } // The "distinct" nonterminal is true (1) if the DISTINCT keyword is @@ -212,6 +212,16 @@ sortorder(A) ::= ASC. {A = 0;} sortorder(A) ::= DESC. {A = 1;} sortorder(A) ::= . {A = 0;} +%type groupby_opt {ExprList*} +%destructor groupby_opt {sqliteExprListDelete($$);} +groupby_opt(A) ::= . {A = 0;} +groupby_opt(A) ::= GROUP BY exprlist(X). {A = X;} + +%type having_opt {Expr*} +%destructor having_opt {sqliteExprDelete($$);} +having_opt(A) ::= . {A = 0;} +having_opt(A) ::= HAVING expr(X). {A = X;} + cmd ::= DELETE FROM ID(X) where_opt(Y). {sqliteDeleteFrom(pParse, &X, Y);} diff --git a/src/select.c b/src/select.c index cf2587e9e4..d3690ac9ef 100644 --- a/src/select.c +++ b/src/select.c @@ -24,11 +24,10 @@ ** This file contains C code routines that are called by the parser ** to handle SELECT statements. ** -** $Id: select.c,v 1.10 2000/06/06 13:54:15 drh Exp $ +** $Id: select.c,v 1.11 2000/06/06 17:27:05 drh Exp $ */ #include "sqliteInt.h" - /* ** Allocate a new Select structure and return a pointer to that ** structure. @@ -68,6 +67,105 @@ void sqliteSelectDelete(Select *p){ sqliteFree(p); } +/* +** Delete the aggregate information from the parse structure. +*/ +void sqliteParseInfoReset(Parse *pParse){ + sqliteFree(pParse->aAgg); + pParse->aAgg = 0; + pParse->nAgg = 0; + pParse->iAggCount = -1; + pParse->useAgg = 0; +} + +/* +** This routine generates the code for the inside of the inner loop +** of a SELECT. +*/ +static int selectInnerLoop( + Parse *pParse, /* The parser context */ + ExprList *pEList, /* List of values being extracted */ + ExprList *pOrderBy, /* If not NULL, sort results using this key */ + int distinct, /* If >=0, make sure results are distinct */ + int eDest, /* How to dispose of the results */ + int iParm, /* An argument to the disposal method */ + int iContinue, /* Jump here to continue with next row */ + int iBreak /* Jump here to break out of the inner loop */ +){ + Vdbe *v = pParse->pVdbe; + int i; + + /* Pull the requested fields. + */ + for(i=0; inExpr; i++){ + sqliteExprCode(pParse, pEList->a[i].pExpr); + } + + /* If the current result is not distinct, skip the rest + ** of the processing for the current row. + */ + if( distinct>=0 ){ + int lbl = sqliteVdbeMakeLabel(v); + sqliteVdbeAddOp(v, OP_MakeKey, pEList->nExpr, 1, 0, 0); + sqliteVdbeAddOp(v, OP_Distinct, distinct, lbl, 0, 0); + sqliteVdbeAddOp(v, OP_Pop, pEList->nExpr+1, 0, 0, 0); + sqliteVdbeAddOp(v, OP_Goto, 0, iContinue, 0, 0); + sqliteVdbeAddOp(v, OP_String, 0, 0, "", lbl); + sqliteVdbeAddOp(v, OP_Put, distinct, 0, 0, 0); + } + /* If there is an ORDER BY clause, then store the results + ** in a sorter. + */ + if( pOrderBy ){ + char *zSortOrder; + sqliteVdbeAddOp(v, OP_SortMakeRec, pEList->nExpr, 0, 0, 0); + zSortOrder = sqliteMalloc( pOrderBy->nExpr + 1 ); + if( zSortOrder==0 ) return 1; + for(i=0; inExpr; i++){ + zSortOrder[i] = pOrderBy->a[i].idx ? '-' : '+'; + sqliteExprCode(pParse, pOrderBy->a[i].pExpr); + } + zSortOrder[pOrderBy->nExpr] = 0; + sqliteVdbeAddOp(v, OP_SortMakeKey, pOrderBy->nExpr, 0, zSortOrder, 0); + sqliteVdbeAddOp(v, OP_SortPut, 0, 0, 0, 0); + }else + + /* If we are writing to a table, then write the results to the table. + */ + if( eDest==SRT_Table ){ + sqliteVdbeAddOp(v, OP_MakeRecord, pEList->nExpr, 0, 0, 0); + sqliteVdbeAddOp(v, OP_New, iParm, 0, 0, 0); + sqliteVdbeAddOp(v, OP_Pull, 1, 0, 0, 0); + sqliteVdbeAddOp(v, OP_Put, iParm, 0, 0, 0); + }else + + /* If we are creating a set for an "expr IN (SELECT ...)" construct, + ** then there should be a single item on the stack. Write this + ** item into the set table with bogus data. + */ + if( eDest==SRT_Set ){ + assert( pEList->nExpr==1 ); + sqliteVdbeAddOp(v, OP_String, 0, 0, "", 0); + sqliteVdbeAddOp(v, OP_Put, iParm, 0, 0, 0); + }else + + /* If this is a scalar select that is part of an expression, then + ** store the results in the appropriate memory cell and break out + ** of the scan loop. + */ + if( eDest==SRT_Mem ){ + sqliteVdbeAddOp(v, OP_MemStore, iParm, 0, 0, 0); + sqliteVdbeAddOp(v, OP_Goto, 0, iBreak, 0, 0); + }else + + /* If none of the above, send the data to the callback function. + */ + { + sqliteVdbeAddOp(v, OP_Callback, pEList->nExpr, 0, 0, 0); + } + return 0; +} + /* ** Generate code for the given SELECT statement. ** @@ -105,6 +203,8 @@ int sqliteSelect( IdList *pTabList; /* List of tables to select from */ Expr *pWhere; /* The WHERE clause. May be NULL */ ExprList *pOrderBy; /* The ORDER BY clause. May be NULL */ + ExprList *pGroupBy; /* The GROUP BY clause. May be NULL */ + Expr *pHaving; /* The HAVING clause. May be NULL */ int isDistinct; /* True if the DISTINCT keyword is present */ int distinct; /* Table to use for the distinct set */ @@ -112,6 +212,8 @@ int sqliteSelect( pTabList = p->pSrc; pWhere = p->pWhere; pOrderBy = p->pOrderBy; + pGroupBy = p->pGroupBy; + pHaving = p->pHaving; isDistinct = p->isDistinct; /* @@ -119,6 +221,7 @@ int sqliteSelect( ** errors before this routine starts. */ if( pParse->nErr>0 ) return 0; + sqliteParseInfoReset(pParse); /* Look up every table in the table list. */ @@ -133,10 +236,13 @@ int sqliteSelect( } /* Allocate a temporary table to use for the DISTINCT set, if - ** necessary. + ** necessary. This must be done early to allocate the cursor before + ** any calls to sqliteExprResolveIds(). */ if( isDistinct ){ distinct = pParse->nTab++; + }else{ + distinct = -1; } /* If the list of fields to retrieve is "*" then replace it with @@ -154,7 +260,8 @@ int sqliteSelect( } } - /* If writing to memory, only a single column may be output. + /* If writing to memory or generating a set + ** only a single column may be output. */ if( (eDest==SRT_Mem || eDest==SRT_Set) && pEList->nExpr>1 ){ sqliteSetString(&pParse->zErrMsg, "only a single result allowed for " @@ -163,7 +270,13 @@ int sqliteSelect( return 1; } - /* Resolve the field names and do a semantics check on all the expressions. + /* ORDER BY is ignored if we are not sending the result to a callback. + */ + if( eDest!=SRT_Callback ){ + pOrderBy = 0; + } + + /* Allocate cursors for "expr IN (SELECT ...)" constructs. */ for(i=0; inExpr; i++){ sqliteExprResolveInSelect(pParse, pEList->a[i].pExpr); @@ -174,25 +287,23 @@ int sqliteSelect( sqliteExprResolveInSelect(pParse, pOrderBy->a[i].pExpr); } } + if( pGroupBy ){ + for(i=0; inExpr; i++){ + sqliteExprResolveInSelect(pParse, pGroupBy->a[i].pExpr); + } + } + if( pHaving ) sqliteExprResolveInSelect(pParse, pHaving); + + /* Resolve the field names and do a semantics check on all the expressions. + */ for(i=0; inExpr; i++){ if( sqliteExprResolveIds(pParse, pTabList, pEList->a[i].pExpr) ){ return 1; } - if( sqliteExprCheck(pParse, pEList->a[i].pExpr, 1, &pEList->a[i].isAgg) ){ + if( sqliteExprCheck(pParse, pEList->a[i].pExpr, 1, &isAgg) ){ return 1; } } - if( pEList->nExpr>0 ){ - isAgg = pEList->a[0].isAgg; - for(i=1; inExpr; i++){ - if( pEList->a[i].isAgg!=isAgg ){ - sqliteSetString(&pParse->zErrMsg, "some selected items are aggregates " - "and others are not", 0); - pParse->nErr++; - return 1; - } - } - } if( pWhere ){ if( sqliteExprResolveIds(pParse, pTabList, pWhere) ){ return 1; @@ -203,25 +314,59 @@ int sqliteSelect( } if( pOrderBy ){ for(i=0; inExpr; i++){ - if( sqliteExprResolveIds(pParse, pTabList, pOrderBy->a[i].pExpr) ){ + Expr *pE = pOrderBy->a[i].pExpr; + if( sqliteExprResolveIds(pParse, pTabList, pE) ){ return 1; } - if( sqliteExprCheck(pParse, pOrderBy->a[i].pExpr, 0, 0) ){ + if( sqliteExprCheck(pParse, pE, isAgg, 0) ){ return 1; } } } - - /* ORDER BY is ignored if we are not invoking callbacks. - */ - if( isAgg || eDest!=SRT_Callback ){ - pOrderBy = 0; + if( pGroupBy ){ + for(i=0; inExpr; i++){ + Expr *pE = pGroupBy->a[i].pExpr; + if( sqliteExprResolveIds(pParse, pTabList, pE) ){ + return 1; + } + if( sqliteExprCheck(pParse, pE, isAgg, 0) ){ + return 1; + } + } + } + if( pHaving ){ + if( pGroupBy==0 ){ + sqliteSetString(&pParse->zErrMsg, "a GROUP BY clause is required to " + "use HAVING", 0); + pParse->nErr++; + return 1; + } + if( sqliteExprResolveIds(pParse, pTabList, pHaving) ){ + return 1; + } + if( sqliteExprCheck(pParse, pHaving, 0, 0) ){ + return 1; + } } - /* Turn off distinct if this is an aggregate or writing to memory. + /* Do an analysis of aggregate expressions. */ - if( isAgg || eDest==SRT_Mem ){ - isDistinct = 0; + if( isAgg ){ + for(i=0; inExpr; i++){ + if( sqliteExprAnalyzeAggregates(pParse, pEList->a[i].pExpr) ){ + return 1; + } + } + if( pGroupBy ){ + for(i=0; inExpr; i++){ + if( sqliteExprAnalyzeAggregates(pParse, pGroupBy->a[i].pExpr) ){ + return 1; + } + } + } + if( pHaving && sqliteExprAnalyzeAggregates(pParse, pHaving) ){ + return 1; + } } /* Begin generating code. @@ -239,7 +384,7 @@ int sqliteSelect( sqliteVdbeAddOp(v, OP_SortOpen, 0, 0, 0, 0); } - /* Identify column names if we will be using a callback. This + /* Identify column names if we will be using in the callback. This ** step is skipped if the output is going to a table or a memory cell. */ if( eDest==SRT_Callback ){ @@ -279,23 +424,10 @@ int sqliteSelect( } } - /* Initialize the stack to contain aggregate seed values + /* Reset the aggregator */ if( isAgg ){ - for(i=0; inExpr; i++){ - Expr *p = pEList->a[i].pExpr; - switch( sqliteFuncId(&p->token) ){ - case FN_Min: - case FN_Max: { - sqliteVdbeAddOp(v, OP_Null, 0, 0, 0, 0); - break; - } - default: { - sqliteVdbeAddOp(v, OP_Integer, 0, 0, 0, 0); - break; - } - } - } + sqliteVdbeAddOp(v, OP_AggReset, 0, pParse->nAgg, 0, 0); } /* Initialize the memory cell to NULL @@ -313,83 +445,98 @@ int sqliteSelect( pWInfo = sqliteWhereBegin(pParse, pTabList, pWhere, 0); if( pWInfo==0 ) return 1; - /* Pull the requested fields. + /* Use the standard inner loop if we are not dealing with + ** aggregates */ if( !isAgg ){ - for(i=0; inExpr; i++){ - sqliteExprCode(pParse, pEList->a[i].pExpr); + if( selectInnerLoop(pParse, pEList, pOrderBy, distinct, eDest, iParm, + pWInfo->iContinue, pWInfo->iBreak) ){ + return 1; } } - /* If the current result is not distinct, script the remainder - ** of this processing. + /* If we are dealing with aggregates, then to the special aggregate + ** processing. */ - if( isDistinct ){ - int lbl = sqliteVdbeMakeLabel(v); - sqliteVdbeAddOp(v, OP_MakeKey, pEList->nExpr, 1, 0, 0); - sqliteVdbeAddOp(v, OP_Distinct, distinct, lbl, 0, 0); - sqliteVdbeAddOp(v, OP_Pop, pEList->nExpr+1, 0, 0, 0); - sqliteVdbeAddOp(v, OP_Goto, 0, pWInfo->iContinue, 0, 0); - sqliteVdbeAddOp(v, OP_String, 0, 0, "", lbl); - sqliteVdbeAddOp(v, OP_Put, distinct, 0, 0, 0); - } - - /* If there is no ORDER BY clause, then we can invoke the callback - ** right away. If there is an ORDER BY, then we need to put the - ** data into an appropriate sorter record. - */ - if( pOrderBy ){ - char *zSortOrder; - sqliteVdbeAddOp(v, OP_SortMakeRec, pEList->nExpr, 0, 0, 0); - zSortOrder = sqliteMalloc( pOrderBy->nExpr + 1 ); - if( zSortOrder==0 ) return 1; - for(i=0; inExpr; i++){ - zSortOrder[i] = pOrderBy->a[i].idx ? '-' : '+'; - sqliteExprCode(pParse, pOrderBy->a[i].pExpr); + else{ + int doFocus; + if( pGroupBy ){ + for(i=0; inExpr; i++){ + sqliteExprCode(pParse, pGroupBy->a[i].pExpr); + } + sqliteVdbeAddOp(v, OP_MakeKey, pGroupBy->nExpr, 0, 0, 0); + doFocus = 1; + }else{ + doFocus = 0; + for(i=0; inAgg; i++){ + if( !pParse->aAgg[i].isAgg ){ + doFocus = 1; + break; + } + } + if( doFocus ){ + sqliteVdbeAddOp(v, OP_String, 0, 0, "", 0); + } } - zSortOrder[pOrderBy->nExpr] = 0; - sqliteVdbeAddOp(v, OP_SortMakeKey, pOrderBy->nExpr, 0, zSortOrder, 0); - sqliteVdbeAddOp(v, OP_SortPut, 0, 0, 0, 0); - }else if( isAgg ){ - int n = pEList->nExpr; - for(i=0; ia[i].pExpr; - int id = sqliteFuncId(&p->token); - int op, p1; - if( n>1 ){ - sqliteVdbeAddOp(v, OP_Pull, n-1, 0, 0, 0); + if( doFocus ){ + int lbl1 = sqliteVdbeMakeLabel(v); + sqliteVdbeAddOp(v, OP_AggFocus, 0, lbl1, 0, 0); + for(i=0; inAgg; i++){ + if( pParse->aAgg[i].isAgg ) continue; + sqliteExprCode(pParse, pParse->aAgg[i].pExpr); + sqliteVdbeAddOp(v, OP_AggSet, 0, i, 0, 0); } - if( id!=FN_Count && p->pList && p->pList->nExpr>=1 ){ - sqliteExprCode(pParse, p->pList->a[0].pExpr); - sqliteVdbeAddOp(v, OP_Concat, 1, 0, 0, 0); - } - switch( sqliteFuncId(&p->token) ){ - case FN_Count: op = OP_AddImm; p1 = 1; break; - case FN_Sum: op = OP_Add; p1 = 0; break; - case FN_Min: op = OP_Min; p1 = 1; break; - case FN_Max: op = OP_Max; p1 = 0; break; - } - sqliteVdbeAddOp(v, op, p1, 0, 0, 0); + sqliteVdbeResolveLabel(v, lbl1); + } + for(i=0; inAgg; i++){ + Expr *pE; + int op; + if( !pParse->aAgg[i].isAgg ) continue; + pE = pParse->aAgg[i].pExpr; + if( pE==0 ){ + sqliteVdbeAddOp(v, OP_AggIncr, 1, i, 0, 0); + continue; + } + assert( pE->op==TK_AGG_FUNCTION ); + assert( pE->pList!=0 && pE->pList->nExpr==1 ); + sqliteExprCode(pParse, pE->pList->a[0].pExpr); + sqliteVdbeAddOp(v, OP_AggGet, 0, i, 0, 0); + switch( pE->iField ){ + case FN_Min: op = OP_Min; break; + case FN_Max: op = OP_Max; break; + case FN_Avg: op = OP_Add; break; + case FN_Sum: op = OP_Add; break; + } + sqliteVdbeAddOp(v, op, 0, 0, 0, 0); + sqliteVdbeAddOp(v, OP_AggSet, 0, i, 0, 0); } - }else if( eDest==SRT_Table ){ - sqliteVdbeAddOp(v, OP_MakeRecord, pEList->nExpr, 0, 0, 0); - sqliteVdbeAddOp(v, OP_New, iParm, 0, 0, 0); - sqliteVdbeAddOp(v, OP_Pull, 1, 0, 0, 0); - sqliteVdbeAddOp(v, OP_Put, iParm, 0, 0, 0); - }else if( eDest==SRT_Set ){ - sqliteVdbeAddOp(v, OP_String, 0, 0, "", 0); - sqliteVdbeAddOp(v, OP_Put, iParm, 0, 0, 0); - }else if( eDest==SRT_Mem ){ - sqliteVdbeAddOp(v, OP_MemStore, iParm, 0, 0, 0); - sqliteVdbeAddOp(v, OP_Goto, 0, pWInfo->iBreak, 0, 0); - }else{ - sqliteVdbeAddOp(v, OP_Callback, pEList->nExpr, 0, 0, 0); } + /* End the database scan loop. */ sqliteWhereEnd(pWInfo); + /* If we are processing aggregates, we need to set up a second loop + ** over all of the aggregate values and process them. + */ + if( isAgg ){ + int endagg = sqliteVdbeMakeLabel(v); + int startagg; + startagg = sqliteVdbeAddOp(v, OP_AggNext, 0, endagg, 0, 0); + pParse->useAgg = 1; + if( pHaving ){ + sqliteExprIfFalse(pParse, pHaving, startagg); + } + if( selectInnerLoop(pParse, pEList, pOrderBy, distinct, eDest, iParm, + startagg, endagg) ){ + return 1; + } + sqliteVdbeAddOp(v, OP_Goto, 0, startagg, 0, 0); + sqliteVdbeAddOp(v, OP_Noop, 0, 0, 0, endagg); + pParse->useAgg = 0; + } + /* If there is an ORDER BY clause, then we need to sort the results ** and send them to the callback one by one. */ @@ -402,24 +549,5 @@ int sqliteSelect( sqliteVdbeAddOp(v, OP_Goto, 0, addr, 0, 0); sqliteVdbeAddOp(v, OP_SortClose, 0, 0, 0, end); } - - /* If this is an aggregate, then we need to invoke the callback - ** exactly once. - */ - if( isAgg ){ - if( eDest==SRT_Table ){ - sqliteVdbeAddOp(v, OP_MakeRecord, pEList->nExpr, 0, 0, 0); - sqliteVdbeAddOp(v, OP_New, iParm, 0, 0, 0); - sqliteVdbeAddOp(v, OP_Pull, 1, 0, 0, 0); - sqliteVdbeAddOp(v, OP_Put, iParm, 0, 0, 0); - }else if( eDest==SRT_Set ){ - sqliteVdbeAddOp(v, OP_String, 0, 0, "", 0); - sqliteVdbeAddOp(v, OP_Put, iParm, 0, 0, 0); - }else if( eDest==SRT_Mem ){ - sqliteVdbeAddOp(v, OP_MemStore, iParm, 0, 0, 0); - }else{ - sqliteVdbeAddOp(v, OP_Callback, pEList->nExpr, 0, 0, 0); - } - } return 0; } diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 42590f8281..8b1f94f972 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -23,7 +23,7 @@ ************************************************************************* ** Internal interface definitions for SQLite. ** -** @(#) $Id: sqliteInt.h,v 1.17 2000/06/06 13:54:15 drh Exp $ +** @(#) $Id: sqliteInt.h,v 1.18 2000/06/06 17:27:05 drh Exp $ */ #include "sqlite.h" #include "dbbe.h" @@ -88,6 +88,7 @@ typedef struct Token Token; typedef struct IdList IdList; typedef struct WhereInfo WhereInfo; typedef struct Select Select; +typedef struct AggExpr AggExpr; /* ** Each database is an instance of the following structure @@ -161,7 +162,10 @@ struct Expr { ExprList *pList; /* A list of expressions used as a function argument */ Token token; /* An operand token */ int iTable, iField; /* When op==TK_FIELD, then this node means the - ** iField-th field of the iTable-th table */ + ** iField-th field of the iTable-th table. When + ** op==TK_FUNCTION, iField holds the function id */ + int iAgg; /* When op==TK_FIELD and pParse->useAgg==TRUE, pull + ** value from these element of the aggregator */ Select *pSelect; /* When the expression is a sub-select */ }; @@ -234,6 +238,30 @@ struct Select { #define SRT_Set 3 /* Store result in a table for use with "IN" */ #define SRT_Table 4 /* Store result in a regular table */ +/* +** When a SELECT uses aggregate functions (like "count(*)" or "avg(f1)") +** we have to do some additional analysis of expressions. An instance +** of the following structure holds information about a single subexpression +** somewhere in the SELECT statement. An array of these structures holds +** all the information we need to generate code for aggregate +** expressions. +** +** Note that when analyzing a SELECT containing aggregates, both +** non-aggregate field variables and aggregate functions are stored +** in the AggExpr array of the Parser structure. +** +** The pExpr field points to an expression that is part of either the +** field list, the GROUP BY clause, the HAVING clause or the ORDER BY +** clause. The expression will be freed when those clauses are cleaned +** up. Do not try to delete the expression attached to AggExpr.pExpr. +** +** If AggExpr.pExpr==0, that means the expression is "count(*)". +*/ +struct AggExpr { + int isAgg; /* if TRUE contains an aggregate function */ + Expr *pExpr; /* The expression */ +}; + /* ** An SQL parser context */ @@ -253,6 +281,11 @@ struct Parse { int nTab; /* Number of previously allocated cursors */ int nMem; /* Number of memory cells used so far */ int nSet; /* Number of sets used so far */ + int nAgg; /* Number of aggregate expressions */ + AggExpr *aAgg; /* An array of aggregate expressions */ + int iAggCount; /* Index of the count(*) aggregate in aAgg[] */ + int useAgg; /* If true, extract field values from the aggregator + ** while generating expressions. Normally false */ }; /* @@ -315,3 +348,5 @@ int sqliteExprCheck(Parse*, Expr*, int, int*); int sqliteFuncId(Token*); int sqliteExprResolveIds(Parse*, IdList*, Expr*); void sqliteExprResolveInSelect(Parse*, Expr*); +int sqliteExprAnalyzeAggregates(Parse*, Expr*); +void sqlitePArseInfoReset(Parse*); diff --git a/src/tokenize.c b/src/tokenize.c index 989010e049..b64e32507a 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -27,7 +27,7 @@ ** individual tokens and sends those tokens one-by-one over to the ** parser for analysis. ** -** $Id: tokenize.c,v 1.6 2000/06/06 01:50:43 drh Exp $ +** $Id: tokenize.c,v 1.7 2000/06/06 17:27:06 drh Exp $ */ #include "sqliteInt.h" #include @@ -68,6 +68,8 @@ static Keyword aKeywordTable[] = { { "EXPLAIN", 0, TK_EXPLAIN, 0 }, { "FROM", 0, TK_FROM, 0 }, { "GLOB", 0, TK_GLOB, 0 }, + { "GROUP", 0, TK_GROUP, 0 }, + { "HAVING", 0, TK_HAVING, 0 }, { "IN", 0, TK_IN, 0 }, { "INDEX", 0, TK_INDEX, 0 }, { "INSERT", 0, TK_INSERT, 0 }, @@ -298,6 +300,7 @@ int sqliteRunParser(Parse *pParse, char *zSql, char **pzErrMsg){ extern void sqliteParserTrace(FILE*, char *); i = 0; + sqliteParseInfoReset(pParse); pEngine = sqliteParserAlloc((void*(*)(int))malloc); if( pEngine==0 ){ sqliteSetString(pzErrMsg, "out of memory", 0); @@ -382,5 +385,6 @@ int sqliteRunParser(Parse *pParse, char *zSql, char **pzErrMsg){ sqliteDeleteTable(pParse->db, pParse->pNewTable); pParse->pNewTable = 0; } + sqliteParseInfoReset(pParse); return nErr; } diff --git a/src/vdbe.c b/src/vdbe.c index 6d31c1b9b1..e80a1d4e74 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -41,7 +41,7 @@ ** But other routines are also provided to help in building up ** a program instruction by instruction. ** -** $Id: vdbe.c,v 1.20 2000/06/06 13:54:16 drh Exp $ +** $Id: vdbe.c,v 1.21 2000/06/06 17:27:06 drh Exp $ */ #include "sqliteInt.h" #include @@ -423,7 +423,8 @@ static void AggRehash(Agg *p, int nHash){ */ static int AggInsert(Agg *p, char *zKey){ AggElem *pElem; - if( p->nHash < p->nElem*2 ){ + int i; + if( p->nHash <= p->nElem*2 ){ AggRehash(p, p->nElem*2 + 103); } if( p->nHash==0 ) return 1; @@ -437,6 +438,9 @@ static int AggInsert(Agg *p, char *zKey){ p->pFirst = pElem; p->nElem++; p->pCurrent = pElem; + for(i=0; inMem; i++){ + pElem->aMem[i].s.flags = STK_Null; + } return 0; } @@ -450,7 +454,7 @@ static AggElem *_AggInFocus(Agg *p){ p->pCurrent = pFocus; }else{ AggInsert(p,""); - pFocus = p->pCurrent; + pFocus = p->pCurrent = p->pFirst; } return pFocus; } @@ -1145,8 +1149,7 @@ int sqliteVdbeExec( ** next on stack) ** and push the result back onto the stack. If either element ** is a string then it is converted to a double using the atof() - ** function before the division. Division by zero causes the - ** program to abort with an error. + ** function before the division. Division by zero returns NULL. */ case OP_Add: case OP_Subtract: @@ -1164,11 +1167,7 @@ int sqliteVdbeExec( case OP_Subtract: b -= a; break; case OP_Multiply: b *= a; break; default: { - if( a==0 ){ - sqliteSetString(pzErrMsg, "division by zero", 0); - rc = SQLITE_ERROR; - goto cleanup; - } + if( a==0 ) goto divide_by_zero; b /= a; break; } @@ -1188,11 +1187,7 @@ int sqliteVdbeExec( case OP_Subtract: b -= a; break; case OP_Multiply: b *= a; break; default: { - if( a==0.0 ){ - sqliteSetString(pzErrMsg, "division by zero", 0); - rc = SQLITE_ERROR; - goto cleanup; - } + if( a==0.0 ) goto divide_by_zero; b /= a; break; } @@ -1203,6 +1198,12 @@ int sqliteVdbeExec( p->aStack[nos].flags = STK_Real; } break; + + divide_by_zero: + PopStack(p, 2); + p->tos = nos; + p->aStack[nos].flags = STK_Null; + break; } /* Opcode: Max * * * @@ -1248,9 +1249,6 @@ int sqliteVdbeExec( ** ** Pop the top two elements from the stack then push back the ** smaller of the two. - ** - ** If P1==1, always choose TOS for the min and decrement P1. - ** This is self-altering code... */ case OP_Min: { int tos = p->tos; @@ -1260,11 +1258,10 @@ int sqliteVdbeExec( if( nos<0 ) goto not_enough_stack; ft = p->aStack[tos].flags; fn = p->aStack[nos].flags; - if( pOp->p1 ){ - copy = 1; - pOp->p1 = 0; - }else if( fn & STK_Null ){ + if( fn & STK_Null ){ copy = 1; + }else if( ft & STK_Null ){ + copy = 0; }else if( (ft & fn & STK_Int)==STK_Int ){ copy = p->aStack[nos].i>p->aStack[tos].i; }else if( ( (ft|fn) & (STK_Int|STK_Real) ) !=0 ){ @@ -2728,6 +2725,7 @@ int sqliteVdbeExec( }else{ AggInsert(&p->agg, zKey); } + PopStack(p, 1); break; } diff --git a/test/select1.test b/test/select1.test index a2d064d268..c3cd08dd88 100644 --- a/test/select1.test +++ b/test/select1.test @@ -23,25 +23,25 @@ # This file implements regression tests for SQLite library. The # focus of this file is testing the SELECT statement. # -# $Id: select1.test,v 1.2 2000/05/31 18:20:14 drh Exp $ +# $Id: select1.test,v 1.3 2000/06/06 17:27:06 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl # Try to select on a non-existant table. # -do_test select-1.1 { +do_test select1-1.1 { set v [catch {execsql {SELECT * FROM test1}} msg] lappend v $msg } {1 {no such table: test1}} execsql {CREATE TABLE test1(f1 int, f2 int)} -do_test select-1.2 { +do_test select1-1.2 { set v [catch {execsql {SELECT * FROM test1, test2}} msg] lappend v $msg } {1 {no such table: test2}} -do_test select-1.3 { +do_test select1-1.3 { set v [catch {execsql {SELECT * FROM test2, test1}} msg] lappend v $msg } {1 {no such table: test2}} @@ -51,39 +51,39 @@ execsql {INSERT INTO test1(f1,f2) VALUES(11,22)} # Make sure the fields are extracted correctly. # -do_test select-1.4 { +do_test select1-1.4 { execsql {SELECT f1 FROM test1} } {11} -do_test select-1.5 { +do_test select1-1.5 { execsql {SELECT f2 FROM test1} } {22} -do_test select-1.6 { +do_test select1-1.6 { execsql {SELECT f2, f1 FROM test1} } {22 11} -do_test select-1.7 { +do_test select1-1.7 { execsql {SELECT f1, f2 FROM test1} } {11 22} -do_test select-1.8 { +do_test select1-1.8 { execsql {SELECT * FROM test1} } {11 22} execsql {CREATE TABLE test2(r1 real, r2 real)} execsql {INSERT INTO test2(r1,r2) VALUES(1.1,2.2)} -do_test select-1.9 { +do_test select1-1.9 { execsql {SELECT * FROM test1, test2} } {11 22 1.1 2.2} -do_test select-1.10 { +do_test select1-1.10 { execsql {SELECT test1.f1, test2.r1 FROM test1, test2} } {11 1.1} -do_test select-1.11 { +do_test select1-1.11 { execsql {SELECT test1.f1, test2.r1 FROM test2, test1} } {11 1.1} -do_test select-1.12 { +do_test select1-1.12 { execsql {SELECT max(test1.f1,test2.r1), min(test1.f2,test2.r2) FROM test2, test1} } {11 2.2} -do_test select-1.13 { +do_test select1-1.13 { execsql {SELECT min(test1.f1,test2.r1), max(test1.f2,test2.r2) FROM test1, test2} } {1.1 22} @@ -95,148 +95,148 @@ execsql {INSERT INTO test1 VALUES(33,44)} # Error messges from sqliteExprCheck # -do_test select-2.1 { +do_test select1-2.1 { set v [catch {execsql {SELECT count(f1,f2) FROM test1}} msg] lappend v $msg } {1 {too many arguments to function count()}} -do_test select-2.2 { +do_test select1-2.2 { set v [catch {execsql {SELECT count(f1) FROM test1}} msg] lappend v $msg } {0 2} -do_test select-2.3 { +do_test select1-2.3 { set v [catch {execsql {SELECT Count() FROM test1}} msg] lappend v $msg } {0 2} -do_test select-2.4 { +do_test select1-2.4 { set v [catch {execsql {SELECT COUNT(*) FROM test1}} msg] lappend v $msg } {0 2} -do_test select-2.5 { +do_test select1-2.5 { set v [catch {execsql {SELECT COUNT(*)+1 FROM test1}} msg] lappend v $msg -} {1 {no such function: COUNT}} -do_test select-2.6 { +} {0 3} +do_test select1-2.6 { set v [catch {execsql {SELECT min(*) FROM test1}} msg] lappend v $msg } {1 {too few arguments to function min()}} -do_test select-2.7 { +do_test select1-2.7 { set v [catch {execsql {SELECT Min(f1) FROM test1}} msg] lappend v $msg } {0 11} -do_test select-2.8 { +do_test select1-2.8 { set v [catch {execsql {SELECT MIN(f1,f2) FROM test1}} msg] lappend v [lsort $msg] } {0 {11 33}} -do_test select-2.9 { +do_test select1-2.9 { set v [catch {execsql {SELECT MAX(*) FROM test1}} msg] lappend v $msg } {1 {too few arguments to function MAX()}} -do_test select-2.10 { +do_test select1-2.10 { set v [catch {execsql {SELECT Max(f1) FROM test1}} msg] lappend v $msg } {0 33} -do_test select-2.11 { +do_test select1-2.11 { set v [catch {execsql {SELECT max(f1,f2) FROM test1}} msg] lappend v [lsort $msg] } {0 {22 44}} -do_test select-2.12 { +do_test select1-2.12 { set v [catch {execsql {SELECT MAX(f1,f2)+1 FROM test1}} msg] lappend v [lsort $msg] } {0 {23 45}} -do_test select-2.13 { +do_test select1-2.13 { set v [catch {execsql {SELECT MAX(f1)+1 FROM test1}} msg] lappend v $msg -} {1 {too few arguments to function MAX()}} -do_test select-2.14 { +} {0 34} +do_test select1-2.14 { set v [catch {execsql {SELECT SUM(*) FROM test1}} msg] lappend v $msg } {1 {too few arguments to function SUM()}} -do_test select-2.15 { +do_test select1-2.15 { set v [catch {execsql {SELECT Sum(f1) FROM test1}} msg] lappend v $msg } {0 44} -do_test select-2.16 { +do_test select1-2.16 { set v [catch {execsql {SELECT sum(f1,f2) FROM test1}} msg] lappend v $msg } {1 {too many arguments to function sum()}} -do_test select-2.17 { +do_test select1-2.17 { set v [catch {execsql {SELECT SUM(f1)+1 FROM test1}} msg] lappend v $msg -} {1 {no such function: SUM}} -do_test select-2.18 { +} {0 45} +do_test select1-2.18 { set v [catch {execsql {SELECT XYZZY(f1) FROM test1}} msg] lappend v $msg } {1 {no such function: XYZZY}} -do_test select-2.19 { +do_test select1-2.19 { set v [catch {execsql {SELECT SUM(min(f1,f2)) FROM test1}} msg] lappend v $msg } {0 44} -do_test select-2.20 { +do_test select1-2.20 { set v [catch {execsql {SELECT SUM(min(f1)) FROM test1}} msg] lappend v $msg } {1 {too few arguments to function min()}} # WHERE clause expressions # -do_test select-3.1 { +do_test select1-3.1 { set v [catch {execsql {SELECT f1 FROM test1 WHERE f1<11}} msg] lappend v $msg } {0 {}} -do_test select-3.2 { +do_test select1-3.2 { set v [catch {execsql {SELECT f1 FROM test1 WHERE f1<=11}} msg] lappend v $msg } {0 11} -do_test select-3.3 { +do_test select1-3.3 { set v [catch {execsql {SELECT f1 FROM test1 WHERE f1=11}} msg] lappend v $msg } {0 11} -do_test select-3.4 { +do_test select1-3.4 { set v [catch {execsql {SELECT f1 FROM test1 WHERE f1>=11}} msg] lappend v [lsort $msg] } {0 {11 33}} -do_test select-3.5 { +do_test select1-3.5 { set v [catch {execsql {SELECT f1 FROM test1 WHERE f1>11}} msg] lappend v [lsort $msg] } {0 33} -do_test select-3.6 { +do_test select1-3.6 { set v [catch {execsql {SELECT f1 FROM test1 WHERE f1!=11}} msg] lappend v [lsort $msg] } {0 33} -do_test select-3.7 { +do_test select1-3.7 { set v [catch {execsql {SELECT f1 FROM test1 WHERE min(f1,f2)!=11}} msg] lappend v [lsort $msg] } {0 33} -do_test select-3.8 { +do_test select1-3.8 { set v [catch {execsql {SELECT f1 FROM test1 WHERE max(f1,f2)!=11}} msg] lappend v [lsort $msg] } {0 {11 33}} -do_test select-3.9 { +do_test select1-3.9 { set v [catch {execsql {SELECT f1 FROM test1 WHERE count(f1,f2)!=11}} msg] lappend v $msg } {1 {no such function: count}} # ORDER BY expressions # -do_test select-4.1 { +do_test select1-4.1 { set v [catch {execsql {SELECT f1 FROM test1 ORDER BY f1}} msg] lappend v $msg } {0 {11 33}} -do_test select-4.2 { +do_test select1-4.2 { set v [catch {execsql {SELECT f1 FROM test1 ORDER BY -f1}} msg] lappend v $msg } {0 {33 11}} -do_test select-4.3 { +do_test select1-4.3 { set v [catch {execsql {SELECT f1 FROM test1 ORDER BY min(f1,f2)}} msg] lappend v $msg } {0 {11 33}} -do_test select-4.4 { +do_test select1-4.4 { set v [catch {execsql {SELECT f1 FROM test1 ORDER BY min(f1)}} msg] lappend v $msg } {1 {too few arguments to function min()}} # ORDER BY ignored on an aggregate query # -do_test select-5.1 { +do_test select1-5.1 { set v [catch {execsql {SELECT max(f1) FROM test1 ORDER BY f2}} msg] lappend v $msg } {0 33} @@ -246,47 +246,47 @@ execsql {INSERT INTO test2 VALUES('abc','xyz')} # Check for field naming # -do_test select-6.1 { +do_test select1-6.1 { set v [catch {execsql2 {SELECT f1 FROM test1 ORDER BY f2}} msg] lappend v $msg } {0 {f1 11 f1 33}} -do_test select-6.2 { +do_test select1-6.2 { set v [catch {execsql2 {SELECT f1 as xyzzy FROM test1 ORDER BY f2}} msg] lappend v $msg } {0 {xyzzy 11 xyzzy 33}} -do_test select-6.3 { +do_test select1-6.3 { set v [catch {execsql2 {SELECT f1 as "xyzzy" FROM test1 ORDER BY f2}} msg] lappend v $msg } {0 {xyzzy 11 xyzzy 33}} -do_test select-6.4 { +do_test select1-6.4 { set v [catch {execsql2 {SELECT f1+F2 as xyzzy FROM test1 ORDER BY f2}} msg] lappend v $msg } {0 {xyzzy 33 xyzzy 77}} -do_test select-6.5 { +do_test select1-6.5 { set v [catch {execsql2 {SELECT test1.f1+F2 FROM test1 ORDER BY f2}} msg] lappend v $msg } {0 {field1 33 field1 77}} -do_test select-6.6 { +do_test select1-6.6 { set v [catch {execsql2 {SELECT test1.f1+F2, t1 FROM test1, test2 ORDER BY f2}} msg] lappend v $msg } {0 {field1 33 test2.t1 abc field1 77 test2.t1 abc}} -do_test select-6.7 { +do_test select1-6.7 { set v [catch {execsql2 {SELECT A.f1, t1 FROM test1 as A, test2 ORDER BY f2}} msg] lappend v $msg } {0 {A.f1 11 test2.t1 abc A.f1 33 test2.t1 abc}} -do_test select-6.8 { +do_test select1-6.8 { set v [catch {execsql2 {SELECT A.f1, f1 FROM test1 as A, test1 as B ORDER BY f2}} msg] lappend v $msg } {1 {ambiguous field name: f1}} -do_test select-6.8 { +do_test select1-6.8 { set v [catch {execsql2 {SELECT A.f1, B.f1 FROM test1 as A, test1 as B ORDER BY f2}} msg] lappend v $msg } {1 {ambiguous field name: f2}} -do_test select-6.9 { +do_test select1-6.9 { set v [catch {execsql2 {SELECT A.f1, B.f1 FROM test1 as A, test1 as B ORDER BY A.f1, B.f1}} msg] lappend v $msg