From cce7d1761422e206689062685c79664e469a57ed Mon Sep 17 00:00:00 2001 From: drh Date: Wed, 31 May 2000 15:34:51 +0000 Subject: [PATCH] added aggregate functions like count(*) (CVS 21) FossilOrigin-Name: dee7a8be88a95014534b90b96716d9e2e6b16579 --- Makefile.in | 19 +- manifest | 31 ++- manifest.uuid | 2 +- src/build.c | 715 +----------------------------------------------- src/delete.c | 132 +++++++++ src/expr.c | 527 +++++++++++++++++++++++++++++++++++ src/insert.c | 158 +++++++++++ src/parse.y | 9 +- src/select.c | 271 ++++++++++++++++++ src/sqliteInt.h | 16 +- src/update.c | 225 +++++++++++++++ src/vdbe.c | 17 +- src/where.c | 239 +--------------- www/changes.tcl | 5 + www/index.tcl | 24 +- 15 files changed, 1395 insertions(+), 995 deletions(-) create mode 100644 src/delete.c create mode 100644 src/expr.c create mode 100644 src/insert.c create mode 100644 src/select.c create mode 100644 src/update.c diff --git a/Makefile.in b/Makefile.in index 8c277993d1..887254411e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -47,7 +47,9 @@ LIBREADLINE = @TARGET_READLINE_LIBS@ # Object files for the SQLite library. # -LIBOBJ = build.o dbbe.o main.o parse.o tokenize.o util.o vdbe.o where.o +LIBOBJ = build.o dbbe.o delete.o expr.o insert.o \ + main.o parse.o select.o tokenize.o update.o \ + util.o vdbe.o where.o # This is the default Makefile target. The objects listed here # are what get build when you type just "make" with no arguments. @@ -110,6 +112,21 @@ vdbe.o: $(TOP)/src/vdbe.c $(HDR) where.o: $(TOP)/src/where.c $(HDR) $(TCC) $(GDBM_FLAGS) -c $(TOP)/src/where.c +delete.o: $(TOP)/src/delete.c $(HDR) + $(TCC) $(GDBM_FLAGS) -c $(TOP)/src/delete.c + +expr.o: $(TOP)/src/expr.c $(HDR) + $(TCC) $(GDBM_FLAGS) -c $(TOP)/src/expr.c + +insert.o: $(TOP)/src/insert.c $(HDR) + $(TCC) $(GDBM_FLAGS) -c $(TOP)/src/insert.c + +select.o: $(TOP)/src/select.c $(HDR) + $(TCC) $(GDBM_FLAGS) -c $(TOP)/src/select.c + +update.o: $(TOP)/src/update.c $(HDR) + $(TCC) $(GDBM_FLAGS) -c $(TOP)/src/update.c + gdbmdump: $(TOP)/tool/gdbmdump.c $(TCC) $(GDBM_FLAGS) -o gdbmdump $(TOP)/tool/gdbmdump.c $(LIBGDBM) diff --git a/manifest b/manifest index e1a4a8ad4b..b09128221f 100644 --- a/manifest +++ b/manifest @@ -1,25 +1,30 @@ -C :-)\s(CVS\s20) -D 2000-05-31T02:27:49 +C added\saggregate\sfunctions\slike\scount(*)\s(CVS\s21) +D 2000-05-31T15:34:52 F COPYRIGHT 74a8a6531a42e124df07ab5599aad63870fa0bd4 -F Makefile.in dd79c78825935c5711ce45c372b0ac0f194b6d43 +F Makefile.in 7ac2fef265940d93a544cb454efa836451559a71 F README 6b5960603c7f8bf42fc022b4b6436f242f238dbb F configure 00a5b5c82147a576fa6e82d7c1b0d55c321d6d2c x F configure.in 6ccfd5fc80517f7cfe605a7fc7e0f62d962a233c F doc/lemon.html e233a3e97a779c7a87e1bc4528c664a58e49dd47 -F src/build.c c6b500077913cde55871d1f9c0d17d4ee4a86828 +F src/build.c 03f83e95d46e328a2ac08aace102b142ea38e6d7 F src/dbbe.c dc9439f839d13e633158808e352056b531f17e1b F src/dbbe.h b678e31c32fa252e6fba830ad16ed8978d1521a9 +F src/delete.c 16ef3418b19be9ab39db836c693970ca7bbff605 +F src/expr.c 91970700e3e39b2b725b028c166f588a5bb0c038 +F src/insert.c bd34716d0bba5561f6b55101adbf16fa75f872e8 F src/main.c 25cce7bce0eb3ba10bada7c05f4b38dc6dbbc86f -F src/parse.y 05de7dec046dd8bd11f8cc3513ff8b27624618c8 +F src/parse.y bdfcd0a3fe7d6ad4b41dc2cbc0d04c4302f609b0 +F src/select.c 540fae91639d93ea1ef348882197554896841a2f F src/shell.c c5752d32cdeaa7d548d4f91177b697b023a00381 F src/sqlite.h 2397c17a8f4ca90c09acab0100dc7e2f8f441b69 -F src/sqliteInt.h 9ac3f9e05bbc5913531473c86d4742343ae670c5 +F src/sqliteInt.h 81552acdedb0c3b256510a66c0f656d35d2ea2bd F src/tclsqlite.c 9efd29f79ded6a900aa3d142169c8bfe03b7affd F src/tokenize.c 5b066f314646d6c5396a253315e5e95d107e1800 +F src/update.c 9194f548dafc9884d79489874e22974ed67cd6a2 F src/util.c 6b4327d7fbf684f8635155d4acb847ae991b3ebc -F src/vdbe.c 74ff55bc2910e25bd811638233f6c02fb1a17fbc +F src/vdbe.c 11d8e4f6e25044ceace5e7a5c160b74b0537492c F src/vdbe.h 02b470d344caed04451c896be7a775068dbdf076 -F src/where.c fd9faea693083c1bde83f824b98f7eb81c4762cc +F src/where.c bed9a8360cbfbf712bdc397c8e22216a5e5f9800 F test/all.test 66a8a5b8291a472157944edcdce51a320ebd1f35 F test/copy.test 641bd3cfaab61c4ee32889587e21e4c70788a97a F test/delete.test 814d53e3b0d2d7069fb17e005d4041454d6585d4 @@ -36,10 +41,10 @@ F tool/opNames.awk 2bd9071a138e4e2be13dc98fe066398a61219e1e F tool/opcodeDoc.awk b3a2a3d5d3075b8bd90b7afe24283efdd586659c F tool/renumberOps.awk 6d067177ad5f8d711b79577b462da9b3634bd0a9 F www/c_interface.tcl f875864edf7974157d1c257ca08de854660882a5 -F www/changes.tcl 38ff869ccbf99388d53abd08aeea4e24e2bb23b7 -F www/index.tcl 57a97afafe04ab53d1996ba3a61ac41fa8453f5a +F www/changes.tcl f6d75118ac266313ebcdafa79b779b82dde44cc0 +F www/index.tcl 600e85c207929bedb9c6fd221aa7875fd8f43edf F www/sqlite.tcl 7deb564df188ad4523adecfe2365de6d09f6dfd9 -P 03725ce5ae871247789ece0f2c3426f74ba575e7 -R bab7f299b23f92a0096c17a89a463018 +P 01d85b35e9c4ca5619ad21a4232a8f8bf9ec3538 +R 05da1ee70a5c666d9e2d499f8f42ff62 U drh -Z e30b944ebaee88defeb667112902d5c8 +Z d21127e72e51c417dbc3dc88c1114902 diff --git a/manifest.uuid b/manifest.uuid index 1b7f2dfd75..a38dfab2c3 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -01d85b35e9c4ca5619ad21a4232a8f8bf9ec3538 \ No newline at end of file +dee7a8be88a95014534b90b96716d9e2e6b16579 \ No newline at end of file diff --git a/src/build.c b/src/build.c index fd0aaeef31..380ce94a06 100644 --- a/src/build.c +++ b/src/build.c @@ -24,7 +24,7 @@ ** This file contains C code routines that are called by the parser ** when syntax rules are reduced. ** -** $Id: build.c,v 1.10 2000/05/31 02:27:49 drh Exp $ +** $Id: build.c,v 1.11 2000/05/31 15:34:52 drh Exp $ */ #include "sqliteInt.h" @@ -188,7 +188,7 @@ void sqliteDeleteTable(sqlite *db, Table *pTable){ ** Space to hold the name is obtained from sqliteMalloc() and must ** be freed by the calling function. */ -static char *sqliteTableNameFromToken(Token *pName){ +char *sqliteTableNameFromToken(Token *pName){ char *zName = 0; sqliteSetNString(&zName, pName->z, pName->n, 0); sqliteDequote(zName); @@ -316,7 +316,7 @@ void sqliteEndTable(Parse *pParse, Token *pEnd){ ** Given a token, look up a table with that name. If not found, leave ** an error for the parser to find and return NULL. */ -static Table *sqliteTableFromToken(Parse *pParse, Token *pTok){ +Table *sqliteTableFromToken(Parse *pParse, Token *pTok){ char *zName = sqliteTableNameFromToken(pTok); Table *pTab = sqliteFindTable(pParse->db, zName); sqliteFree(zName); @@ -736,715 +736,6 @@ void sqliteIdListDelete(IdList *pList){ sqliteFree(pList); } -/* -** This routine is call to handle SQL of the following form: -** -** insert into TABLE (IDLIST) values(EXPRLIST) -** -** The parameters are the table name and the expression list. -*/ -void sqliteInsert( - Parse *pParse, /* Parser context */ - Token *pTableName, /* Name of table into which we are inserting */ - ExprList *pList, /* List of values to be inserted */ - IdList *pField /* Field name corresponding to pList. Might be NULL */ -){ - Table *pTab; - char *zTab; - int i, j; - Vdbe *v; - - zTab = sqliteTableNameFromToken(pTableName); - pTab = sqliteFindTable(pParse->db, zTab); - sqliteFree(zTab); - if( pTab==0 ){ - sqliteSetNString(&pParse->zErrMsg, "no such table: ", 0, - pTableName->z, pTableName->n, 0); - pParse->nErr++; - goto insert_cleanup; - } - if( pTab->readOnly ){ - sqliteSetString(&pParse->zErrMsg, "table ", pTab->zName, - " may not be modified", 0); - pParse->nErr++; - goto insert_cleanup; - } - if( pField==0 && pList->nExpr!=pTab->nCol ){ - char zNum1[30]; - char zNum2[30]; - sprintf(zNum1,"%d", pList->nExpr); - sprintf(zNum2,"%d", pTab->nCol); - sqliteSetString(&pParse->zErrMsg, "table ", pTab->zName, - " has ", zNum2, " columns but ", - zNum1, " values were supplied", 0); - pParse->nErr++; - goto insert_cleanup; - } - if( pField!=0 && pList->nExpr!=pField->nId ){ - char zNum1[30]; - char zNum2[30]; - sprintf(zNum1,"%d", pList->nExpr); - sprintf(zNum2,"%d", pField->nId); - sqliteSetString(&pParse->zErrMsg, zNum1, " values for ", - zNum2, " columns", 0); - pParse->nErr++; - goto insert_cleanup; - } - if( pField ){ - for(i=0; inId; i++){ - pField->a[i].idx = -1; - } - for(i=0; inId; i++){ - for(j=0; jnCol; j++){ - if( sqliteStrICmp(pField->a[i].zName, pTab->azCol[j])==0 ){ - pField->a[i].idx = j; - break; - } - } - if( j>=pTab->nCol ){ - sqliteSetString(&pParse->zErrMsg, "table ", pTab->zName, - " has no column named ", pField->a[i].zName, 0); - pParse->nErr++; - goto insert_cleanup; - } - } - } - v = pParse->pVdbe = sqliteVdbeCreate(pParse->db->pBe); - if( v ){ - Index *pIdx; - sqliteVdbeAddOp(v, OP_Open, 0, 0, pTab->zName, 0); - sqliteVdbeAddOp(v, OP_New, 0, 0, 0, 0); - if( pTab->pIndex ){ - sqliteVdbeAddOp(v, OP_Dup, 0, 0, 0, 0); - } - for(i=0; inCol; i++){ - if( pField==0 ){ - j = i; - }else{ - for(j=0; jnId; j++){ - if( pField->a[j].idx==i ) break; - } - } - if( pField && j>=pField->nId ){ - sqliteVdbeAddOp(v, OP_String, 0, 0, "", 0); - }else{ - sqliteExprCode(pParse, pList->a[j].pExpr); - } - } - sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0, 0, 0); - sqliteVdbeAddOp(v, OP_Put, 0, 0, 0, 0); - sqliteVdbeAddOp(v, OP_Close, 0, 0, 0, 0); - for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - if( pIdx->pNext ){ - sqliteVdbeAddOp(v, OP_Dup, 0, 0, 0, 0); - } - sqliteVdbeAddOp(v, OP_Open, 0, 0, pIdx->zName, 0); - for(i=0; inField; i++){ - int idx = pIdx->aiField[i]; - if( pField==0 ){ - j = idx; - }else{ - for(j=0; jnId; j++){ - if( pField->a[j].idx==idx ) break; - } - } - if( pField && j>=pField->nId ){ - sqliteVdbeAddOp(v, OP_String, 0, 0, "", 0); - }else{ - sqliteExprCode(pParse, pList->a[j].pExpr); - } - } - sqliteVdbeAddOp(v, OP_MakeKey, pIdx->nField, 0, 0, 0); - sqliteVdbeAddOp(v, OP_PutIdx, 0, 0, 0, 0); - sqliteVdbeAddOp(v, OP_Close, 0, 0, 0, 0); - } - } - -insert_cleanup: - sqliteExprListDelete(pList); - sqliteIdListDelete(pField); -} - -/* -** This routine walks an expression tree and resolves references to -** table fields. Nodes of the form ID.ID or ID resolve into an -** index to the table in the table list and a field offset. The opcode -** for such nodes is changed to TK_FIELD. The iTable value is changed -** to the index of the referenced table in pTabList, and the iField value -** is changed to the index of the field of the referenced table. -** -** Unknown fields or tables provoke an error. The function returns -** the number of errors seen and leaves an error message on pParse->zErrMsg. -*/ -int sqliteExprResolveIds(Parse *pParse, IdList *pTabList, Expr *pExpr){ - if( pExpr==0 ) return 0; - switch( pExpr->op ){ - /* A lone identifier */ - case TK_ID: { - int cnt = 0; /* Number of matches */ - int i; /* Loop counter */ - char *z = 0; - sqliteSetNString(&z, pExpr->token.z, pExpr->token.n, 0); - for(i=0; inId; i++){ - int j; - Table *pTab = pTabList->a[i].pTab; - if( pTab==0 ) continue; - for(j=0; jnCol; j++){ - if( sqliteStrICmp(pTab->azCol[j], z)==0 ){ - cnt++; - pExpr->iTable = i; - pExpr->iField = j; - } - } - } - sqliteFree(z); - if( cnt==0 ){ - sqliteSetNString(&pParse->zErrMsg, "no such field: ", -1, - pExpr->token.z, pExpr->token.n, 0); - pParse->nErr++; - return 1; - }else if( cnt>1 ){ - sqliteSetNString(&pParse->zErrMsg, "ambiguous field name: ", -1, - pExpr->token.z, pExpr->token.n, 0); - pParse->nErr++; - return 1; - } - pExpr->op = TK_FIELD; - break; - } - - /* A table name and field name: ID.ID */ - case TK_DOT: { - int cnt = 0; /* Number of matches */ - int i; /* Loop counter */ - Expr *pLeft, *pRight; /* Left and right subbranches of the expr */ - int n; /* Length of an identifier */ - char *zLeft, *zRight; /* Text of an identifier */ - - pLeft = pExpr->pLeft; - pRight = pExpr->pRight; - assert( pLeft && pLeft->op==TK_ID ); - assert( pRight && pRight->op==TK_ID ); - zLeft = 0; - sqliteSetNString(&zLeft, pLeft->token.z, pLeft->token.n, 0); - zRight = 0; - sqliteSetNString(&zRight, pRight->token.z, pRight->token.n, 0); - for(i=0; inId; i++){ - int j; - char *zTab; - Table *pTab = pTabList->a[i].pTab; - if( pTab==0 ) continue; - if( pTabList->a[i].zAlias ){ - zTab = pTabList->a[i].zAlias; - }else{ - zTab = pTab->zName; - } - if( sqliteStrICmp(zTab, zLeft)!=0 ) continue; - for(j=0; jnCol; j++){ - if( sqliteStrICmp(pTab->azCol[j], zRight)==0 ){ - cnt++; - pExpr->iTable = i; - pExpr->iField = j; - } - } - } - sqliteFree(zLeft); - sqliteFree(zRight); - if( cnt==0 ){ - sqliteSetNString(&pParse->zErrMsg, "no such field: ", -1, - pLeft->token.z, pLeft->token.n, ".", 1, - pRight->token.z, pRight->token.n, 0); - pParse->nErr++; - return 1; - }else if( cnt>1 ){ - sqliteSetNString(&pParse->zErrMsg, "ambiguous field name: ", -1, - pLeft->token.z, pLeft->token.n, ".", 1, - pRight->token.z, pRight->token.n, 0); - pParse->nErr++; - return 1; - } - sqliteExprDelete(pLeft); - pExpr->pLeft = 0; - sqliteExprDelete(pRight); - pExpr->pRight = 0; - pExpr->op = TK_FIELD; - break; - } - - /* For all else, just recursively walk the tree */ - default: { - if( pExpr->pLeft - && sqliteExprResolveIds(pParse, pTabList, pExpr->pLeft) ){ - return 1; - } - if( pExpr->pRight - && sqliteExprResolveIds(pParse, pTabList, pExpr->pRight) ){ - return 1; - } - if( pExpr->pList ){ - int i; - ExprList *pList = pExpr->pList; - for(i=0; inExpr; i++){ - if( sqliteExprResolveIds(pParse, pTabList, pList->a[i].pExpr) ){ - return 1; - } - } - } - } - } - return 0; -} - -/* -** Process a SELECT statement. -*/ -void sqliteSelect( - Parse *pParse, /* The parser context */ - ExprList *pEList, /* List of fields to extract. NULL means "*" */ - 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 */ -){ - int i, j; - WhereInfo *pWInfo; - Vdbe *v; - - if( pParse->nErr>0 ) goto select_cleanup; - - /* Look up every table in the table list. - */ - for(i=0; inId; i++){ - pTabList->a[i].pTab = sqliteFindTable(pParse->db, pTabList->a[i].zName); - if( pTabList->a[i].pTab==0 ){ - sqliteSetString(&pParse->zErrMsg, "no such table: ", - pTabList->a[i].zName, 0); - pParse->nErr++; - goto select_cleanup; - } - } - - /* If the list of fields to retrieve is "*" then replace it with - ** a list of all fields from all tables. - */ - if( pEList==0 ){ - for(i=0; inId; i++){ - Table *pTab = pTabList->a[i].pTab; - for(j=0; jnCol; j++){ - Expr *pExpr = sqliteExpr(TK_FIELD, 0, 0, 0); - pExpr->iTable = i; - pExpr->iField = j; - pEList = sqliteExprListAppend(pEList, pExpr, 0); - } - } - } - - /* Resolve the field names in all the expressions. - */ - for(i=0; inExpr; i++){ - if( sqliteExprResolveIds(pParse, pTabList, pEList->a[i].pExpr) ){ - goto select_cleanup; - } - } - if( pWhere && sqliteExprResolveIds(pParse, pTabList, pWhere) ){ - goto select_cleanup; - } - if( pOrderBy ){ - for(i=0; inExpr; i++){ - if( sqliteExprResolveIds(pParse, pTabList, pOrderBy->a[i].pExpr) ){ - goto select_cleanup; - } - } - } - - /* Begin generating code. - */ - v = pParse->pVdbe; - if( v==0 ){ - v = pParse->pVdbe = sqliteVdbeCreate(pParse->db->pBe); - } - if( v==0 ) goto select_cleanup; - if( pOrderBy ){ - sqliteVdbeAddOp(v, OP_SortOpen, 0, 0, 0, 0); - } - - - /* Identify column names - */ - sqliteVdbeAddOp(v, OP_ColumnCount, pEList->nExpr, 0, 0, 0); - for(i=0; inExpr; i++){ - Expr *p; - if( pEList->a[i].zName ){ - char *zName = pEList->a[i].zName; - int addr = sqliteVdbeAddOp(v, OP_ColumnName, i, 0, zName, 0); - if( zName[0]=='\'' || zName[0]=='"' ){ - sqliteVdbeDequoteP3(v, addr); - } - continue; - } - p = pEList->a[i].pExpr; - if( p->op!=TK_FIELD ){ - char zName[30]; - sprintf(zName, "field%d", i+1); - sqliteVdbeAddOp(v, OP_ColumnName, i, 0, zName, 0); - }else{ - if( pTabList->nId>1 ){ - char *zName = 0; - Table *pTab = pTabList->a[p->iTable].pTab; - sqliteSetString(&zName, pTab->zName, ".", - pTab->azCol[p->iField], 0); - sqliteVdbeAddOp(v, OP_ColumnName, i, 0, zName, 0); - sqliteFree(zName); - }else{ - Table *pTab = pTabList->a[0].pTab; - sqliteVdbeAddOp(v, OP_ColumnName, i, 0, pTab->azCol[p->iField], 0); - } - } - } - - /* Begin the database scan - */ - pWInfo = sqliteWhereBegin(pParse, pTabList, pWhere, 0); - if( pWInfo==0 ) goto select_cleanup; - - /* Pull the requested fields. - */ - for(i=0; inExpr; i++){ - sqliteExprCode(pParse, pEList->a[i].pExpr); - } - - /* 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==0 ){ - sqliteVdbeAddOp(v, OP_Callback, pEList->nExpr, 0, 0, 0); - }else{ - char *zSortOrder; - sqliteVdbeAddOp(v, OP_SortMakeRec, pEList->nExpr, 0, 0, 0); - zSortOrder = sqliteMalloc( pOrderBy->nExpr + 1 ); - if( zSortOrder==0 ) goto select_cleanup; - 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); - } - - /* End the database scan loop. - */ - sqliteWhereEnd(pWInfo); - - /* If there is an ORDER BY clause, then we need to sort the results - ** and send them to the callback one by one. - */ - if( pOrderBy ){ - int end = sqliteVdbeMakeLabel(v); - int addr; - sqliteVdbeAddOp(v, OP_Sort, 0, 0, 0, 0); - addr = sqliteVdbeAddOp(v, OP_SortNext, 0, end, 0, 0); - sqliteVdbeAddOp(v, OP_SortCallback, pEList->nExpr, 0, 0, 0); - sqliteVdbeAddOp(v, OP_Goto, 0, addr, 0, 0); - sqliteVdbeAddOp(v, OP_Noop, 0, 0, 0, end); - } - - /* Always execute the following code before exiting, in order to - ** release resources. - */ -select_cleanup: - sqliteExprListDelete(pEList); - sqliteIdListDelete(pTabList); - sqliteExprDelete(pWhere); - sqliteExprListDelete(pOrderBy); - return; -} - -/* -** Process a DELETE FROM statement. -*/ -void sqliteDeleteFrom( - Parse *pParse, /* The parser context */ - Token *pTableName, /* The table from which we should delete things */ - Expr *pWhere /* The WHERE clause. May be null */ -){ - Vdbe *v; /* The virtual database engine */ - Table *pTab; /* The table from which records will be deleted */ - IdList *pTabList; /* An ID list holding pTab and nothing else */ - int end, addr; /* A couple addresses of generated code */ - int i; /* Loop counter */ - WhereInfo *pWInfo; /* Information about the WHERE clause */ - Index *pIdx; /* For looping over indices of the table */ - - /* Locate the table which we want to update. This table has to be - ** put in an IdList structure because some of the subroutines will - ** will be calling are designed to work with multiple tables and expect - ** an IdList* parameter instead of just a Table* parameger. - */ - pTabList = sqliteIdListAppend(0, pTableName); - for(i=0; inId; i++){ - pTabList->a[i].pTab = sqliteFindTable(pParse->db, pTabList->a[i].zName); - if( pTabList->a[i].pTab==0 ){ - sqliteSetString(&pParse->zErrMsg, "no such table: ", - pTabList->a[i].zName, 0); - pParse->nErr++; - goto delete_from_cleanup; - } - if( pTabList->a[i].pTab->readOnly ){ - sqliteSetString(&pParse->zErrMsg, "table ", pTabList->a[i].zName, - " may not be modified", 0); - pParse->nErr++; - goto delete_from_cleanup; - } - } - pTab = pTabList->a[0].pTab; - - /* Resolve the field names in all the expressions. - */ - if( pWhere && sqliteExprResolveIds(pParse, pTabList, pWhere) ){ - goto delete_from_cleanup; - } - - /* Begin generating code. - */ - v = pParse->pVdbe; - if( v==0 ){ - v = pParse->pVdbe = sqliteVdbeCreate(pParse->db->pBe); - } - if( v==0 ) goto delete_from_cleanup; - - /* Begin the database scan - */ - sqliteVdbeAddOp(v, OP_ListOpen, 0, 0, 0, 0); - pWInfo = sqliteWhereBegin(pParse, pTabList, pWhere, 1); - if( pWInfo==0 ) goto delete_from_cleanup; - - /* Remember the index of every item to be deleted. - */ - sqliteVdbeAddOp(v, OP_ListWrite, 0, 0, 0, 0); - - /* End the database scan loop. - */ - sqliteWhereEnd(pWInfo); - - /* Delete every item identified in the list. - */ - sqliteVdbeAddOp(v, OP_ListRewind, 0, 0, 0, 0); - for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ - sqliteVdbeAddOp(v, OP_Open, i, 0, pIdx->zName, 0); - } - end = sqliteVdbeMakeLabel(v); - addr = sqliteVdbeAddOp(v, OP_ListRead, 0, end, 0, 0); - if( pTab->pIndex ){ - sqliteVdbeAddOp(v, OP_Dup, 0, 0, 0, 0); - sqliteVdbeAddOp(v, OP_Fetch, 0, 0, 0, 0); - for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ - int j; - sqliteVdbeAddOp(v, OP_Dup, 0, 0, 0, 0); - for(j=0; jnField; j++){ - sqliteVdbeAddOp(v, OP_Field, 0, pIdx->aiField[j], 0, 0); - } - sqliteVdbeAddOp(v, OP_MakeKey, pIdx->nField, 0, 0, 0); - sqliteVdbeAddOp(v, OP_DeleteIdx, i, 0, 0, 0); - } - } - sqliteVdbeAddOp(v, OP_Delete, 0, 0, 0, 0); - sqliteVdbeAddOp(v, OP_Goto, 0, addr, 0, 0); - sqliteVdbeAddOp(v, OP_ListClose, 0, 0, 0, end); - -delete_from_cleanup: - sqliteIdListDelete(pTabList); - sqliteExprDelete(pWhere); - return; -} - -/* -** Process an UPDATE statement. -*/ -void sqliteUpdate( - Parse *pParse, /* The parser context */ - Token *pTableName, /* The table in which we should change things */ - ExprList *pChanges, /* Things to be changed */ - Expr *pWhere /* The WHERE clause. May be null */ -){ - int i, j; /* Loop counters */ - Table *pTab; /* The table to be updated */ - IdList *pTabList = 0; /* List containing only pTab */ - int end, addr; /* A couple of addresses in the generated code */ - WhereInfo *pWInfo; /* Information about the WHERE clause */ - Vdbe *v; /* The virtual database engine */ - Index *pIdx; /* For looping over indices */ - int nIdx; /* Number of indices that need updating */ - Index **apIdx = 0; /* An array of indices that need updating too */ - int *aXRef = 0; /* aXRef[i] is the index in pChanges->a[] of the - ** an expression for the i-th field of the table. - ** aXRef[i]==-1 if the i-th field is not changed. */ - - /* Locate the table which we want to update. This table has to be - ** put in an IdList structure because some of the subroutines will - ** will be calling are designed to work with multiple tables and expect - ** an IdList* parameter instead of just a Table* parameger. - */ - pTabList = sqliteIdListAppend(0, pTableName); - for(i=0; inId; i++){ - pTabList->a[i].pTab = sqliteFindTable(pParse->db, pTabList->a[i].zName); - if( pTabList->a[i].pTab==0 ){ - sqliteSetString(&pParse->zErrMsg, "no such table: ", - pTabList->a[i].zName, 0); - pParse->nErr++; - goto update_cleanup; - } - if( pTabList->a[i].pTab->readOnly ){ - sqliteSetString(&pParse->zErrMsg, "table ", pTabList->a[i].zName, - " may not be modified", 0); - pParse->nErr++; - goto update_cleanup; - } - } - pTab = pTabList->a[0].pTab; - aXRef = sqliteMalloc( sizeof(int) * pTab->nCol ); - if( aXRef==0 ) goto update_cleanup; - for(i=0; inCol; i++) aXRef[i] = -1; - - /* Resolve the field names in all the expressions in both the - ** WHERE clause and in the new values. Also find the field index - ** for each field to be updated in the pChanges array. - */ - if( pWhere && sqliteExprResolveIds(pParse, pTabList, pWhere) ){ - goto update_cleanup; - } - for(i=0; inExpr; i++){ - if( sqliteExprResolveIds(pParse, pTabList, pChanges->a[i].pExpr) ){ - goto update_cleanup; - } - for(j=0; jnCol; j++){ - if( strcmp(pTab->azCol[j], pChanges->a[i].zName)==0 ){ - pChanges->a[i].idx = j; - aXRef[j] = i; - break; - } - } - if( j>=pTab->nCol ){ - sqliteSetString(&pParse->zErrMsg, "no such field: ", - pChanges->a[i].zName, 0); - pParse->nErr++; - goto update_cleanup; - } - } - - /* Allocate memory for the array apIdx[] and fill it pointers to every - ** index that needs to be updated. Indices only need updating if their - ** key includes one of the fields named in pChanges. - */ - for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - for(i=0; inField; i++){ - if( aXRef[pIdx->aiField[i]]>=0 ) break; - } - if( inField ) nIdx++; - } - apIdx = sqliteMalloc( sizeof(Index*) * nIdx ); - if( apIdx==0 ) goto update_cleanup; - for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - for(i=0; inField; i++){ - if( aXRef[pIdx->aiField[i]]>=0 ) break; - } - if( inField ) apIdx[nIdx++] = pIdx; - } - - /* Begin generating code. - */ - v = pParse->pVdbe; - if( v==0 ){ - v = pParse->pVdbe = sqliteVdbeCreate(pParse->db->pBe); - } - if( v==0 ) goto update_cleanup; - - /* Begin the database scan - */ - sqliteVdbeAddOp(v, OP_ListOpen, 0, 0, 0, 0); - pWInfo = sqliteWhereBegin(pParse, pTabList, pWhere, 1); - if( pWInfo==0 ) goto update_cleanup; - - /* Remember the index of every item to be updated. - */ - sqliteVdbeAddOp(v, OP_ListWrite, 0, 0, 0, 0); - - /* End the database scan loop. - */ - sqliteWhereEnd(pWInfo); - - /* Rewind the list of records that need to be updated and - ** open every index that needs updating. - */ - sqliteVdbeAddOp(v, OP_ListRewind, 0, 0, 0, 0); - for(i=0; izName, 0); - } - - /* Loop over every record that needs updating. We have to load - ** the old data for each record to be updated because some fields - ** might not change and we will need to copy the old value, therefore. - ** Also, the old data is needed to delete the old index entires. - */ - end = sqliteVdbeMakeLabel(v); - addr = sqliteVdbeAddOp(v, OP_ListRead, 0, end, 0, 0); - sqliteVdbeAddOp(v, OP_Dup, 0, 0, 0, 0); - sqliteVdbeAddOp(v, OP_Fetch, 0, 0, 0, 0); - - /* Delete the old indices for the current record. - */ - for(i=0; inField; j++){ - sqliteVdbeAddOp(v, OP_Field, 0, pIdx->aiField[j], 0, 0); - } - sqliteVdbeAddOp(v, OP_MakeKey, pIdx->nField, 0, 0, 0); - sqliteVdbeAddOp(v, OP_DeleteIdx, i+1, 0, 0, 0); - } - - /* Compute a completely new data for this record. - */ - for(i=0; inCol; i++){ - j = aXRef[i]; - if( j<0 ){ - sqliteVdbeAddOp(v, OP_Field, 0, i, 0, 0); - }else{ - sqliteExprCode(pParse, pChanges->a[j].pExpr); - } - } - - /* Insert new index entries that correspond to the new data - */ - for(i=0; inCol, 0, 0, 0); /* The KEY */ - pIdx = apIdx[i]; - for(j=0; jnField; j++){ - sqliteVdbeAddOp(v, OP_Dup, j+pTab->nCol-pIdx->aiField[j], 0, 0, 0); - } - sqliteVdbeAddOp(v, OP_MakeKey, pIdx->nField, 0, 0, 0); - sqliteVdbeAddOp(v, OP_PutIdx, i+1, 0, 0, 0); - } - - /* Write the new data back into the database. - */ - sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0, 0, 0); - sqliteVdbeAddOp(v, OP_Put, 0, 0, 0, 0); - - /* Repeat the above with the next record to be updated, until - ** all record selected by the WHERE clause have been updated. - */ - sqliteVdbeAddOp(v, OP_Goto, 0, addr, 0, 0); - sqliteVdbeAddOp(v, OP_ListClose, 0, 0, 0, end); - -update_cleanup: - sqliteFree(apIdx); - sqliteFree(aXRef); - sqliteIdListDelete(pTabList); - sqliteExprListDelete(pChanges); - sqliteExprDelete(pWhere); - return; -} /* ** The COPY command is for compatibility with PostgreSQL and specificially diff --git a/src/delete.c b/src/delete.c new file mode 100644 index 0000000000..3ab5f59404 --- /dev/null +++ b/src/delete.c @@ -0,0 +1,132 @@ +/* +** Copyright (c) 1999, 2000 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public +** License as published by the Free Software Foundation; either +** version 2 of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** General Public License for more details. +** +** You should have received a copy of the GNU General Public +** License along with this library; if not, write to the +** Free Software Foundation, Inc., 59 Temple Place - Suite 330, +** Boston, MA 02111-1307, USA. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +************************************************************************* +** This file contains C code routines that are called by the parser +** to handle DELETE FROM statements. +** +** $Id: delete.c,v 1.1 2000/05/31 15:34:53 drh Exp $ +*/ +#include "sqliteInt.h" + +/* +** Process a DELETE FROM statement. +*/ +void sqliteDeleteFrom( + Parse *pParse, /* The parser context */ + Token *pTableName, /* The table from which we should delete things */ + Expr *pWhere /* The WHERE clause. May be null */ +){ + Vdbe *v; /* The virtual database engine */ + Table *pTab; /* The table from which records will be deleted */ + IdList *pTabList; /* An ID list holding pTab and nothing else */ + int end, addr; /* A couple addresses of generated code */ + int i; /* Loop counter */ + WhereInfo *pWInfo; /* Information about the WHERE clause */ + Index *pIdx; /* For looping over indices of the table */ + + /* Locate the table which we want to update. This table has to be + ** put in an IdList structure because some of the subroutines will + ** will be calling are designed to work with multiple tables and expect + ** an IdList* parameter instead of just a Table* parameger. + */ + pTabList = sqliteIdListAppend(0, pTableName); + for(i=0; inId; i++){ + pTabList->a[i].pTab = sqliteFindTable(pParse->db, pTabList->a[i].zName); + if( pTabList->a[i].pTab==0 ){ + sqliteSetString(&pParse->zErrMsg, "no such table: ", + pTabList->a[i].zName, 0); + pParse->nErr++; + goto delete_from_cleanup; + } + if( pTabList->a[i].pTab->readOnly ){ + sqliteSetString(&pParse->zErrMsg, "table ", pTabList->a[i].zName, + " may not be modified", 0); + pParse->nErr++; + goto delete_from_cleanup; + } + } + pTab = pTabList->a[0].pTab; + + /* Resolve the field names in all the expressions. + */ + if( pWhere ){ + if( sqliteExprResolveIds(pParse, pTabList, pWhere) ){ + goto delete_from_cleanup; + } + if( sqliteExprCheck(pParse, pWhere, 0, 0) ){ + goto delete_from_cleanup; + } + } + + /* Begin generating code. + */ + v = pParse->pVdbe; + if( v==0 ){ + v = pParse->pVdbe = sqliteVdbeCreate(pParse->db->pBe); + } + if( v==0 ) goto delete_from_cleanup; + + /* Begin the database scan + */ + sqliteVdbeAddOp(v, OP_ListOpen, 0, 0, 0, 0); + pWInfo = sqliteWhereBegin(pParse, pTabList, pWhere, 1); + if( pWInfo==0 ) goto delete_from_cleanup; + + /* Remember the index of every item to be deleted. + */ + sqliteVdbeAddOp(v, OP_ListWrite, 0, 0, 0, 0); + + /* End the database scan loop. + */ + sqliteWhereEnd(pWInfo); + + /* Delete every item identified in the list. + */ + sqliteVdbeAddOp(v, OP_ListRewind, 0, 0, 0, 0); + for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ + sqliteVdbeAddOp(v, OP_Open, i, 0, pIdx->zName, 0); + } + end = sqliteVdbeMakeLabel(v); + addr = sqliteVdbeAddOp(v, OP_ListRead, 0, end, 0, 0); + if( pTab->pIndex ){ + sqliteVdbeAddOp(v, OP_Dup, 0, 0, 0, 0); + sqliteVdbeAddOp(v, OP_Fetch, 0, 0, 0, 0); + for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ + int j; + sqliteVdbeAddOp(v, OP_Dup, 0, 0, 0, 0); + for(j=0; jnField; j++){ + sqliteVdbeAddOp(v, OP_Field, 0, pIdx->aiField[j], 0, 0); + } + sqliteVdbeAddOp(v, OP_MakeKey, pIdx->nField, 0, 0, 0); + sqliteVdbeAddOp(v, OP_DeleteIdx, i, 0, 0, 0); + } + } + sqliteVdbeAddOp(v, OP_Delete, 0, 0, 0, 0); + sqliteVdbeAddOp(v, OP_Goto, 0, addr, 0, 0); + sqliteVdbeAddOp(v, OP_ListClose, 0, 0, 0, end); + +delete_from_cleanup: + sqliteIdListDelete(pTabList); + sqliteExprDelete(pWhere); + return; +} diff --git a/src/expr.c b/src/expr.c new file mode 100644 index 0000000000..01ed63d1cf --- /dev/null +++ b/src/expr.c @@ -0,0 +1,527 @@ +/* +** Copyright (c) 1999, 2000 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public +** License as published by the Free Software Foundation; either +** version 2 of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** General Public License for more details. +** +** You should have received a copy of the GNU General Public +** License along with this library; if not, write to the +** Free Software Foundation, Inc., 59 Temple Place - Suite 330, +** Boston, MA 02111-1307, USA. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +************************************************************************* +** This file contains C code routines used for processing expressions +** +** $Id: expr.c,v 1.1 2000/05/31 15:34:53 drh Exp $ +*/ +#include "sqliteInt.h" + +/* +** This routine walks an expression tree and resolves references to +** table fields. Nodes of the form ID.ID or ID resolve into an +** index to the table in the table list and a field offset. The opcode +** for such nodes is changed to TK_FIELD. The iTable value is changed +** to the index of the referenced table in pTabList, and the iField value +** is changed to the index of the field of the referenced table. +** +** Unknown fields or tables provoke an error. The function returns +** the number of errors seen and leaves an error message on pParse->zErrMsg. +*/ +int sqliteExprResolveIds(Parse *pParse, IdList *pTabList, Expr *pExpr){ + if( pExpr==0 ) return 0; + switch( pExpr->op ){ + /* A lone identifier */ + case TK_ID: { + int cnt = 0; /* Number of matches */ + int i; /* Loop counter */ + char *z = 0; + sqliteSetNString(&z, pExpr->token.z, pExpr->token.n, 0); + for(i=0; inId; i++){ + int j; + Table *pTab = pTabList->a[i].pTab; + if( pTab==0 ) continue; + for(j=0; jnCol; j++){ + if( sqliteStrICmp(pTab->azCol[j], z)==0 ){ + cnt++; + pExpr->iTable = i; + pExpr->iField = j; + } + } + } + sqliteFree(z); + if( cnt==0 ){ + sqliteSetNString(&pParse->zErrMsg, "no such field: ", -1, + pExpr->token.z, pExpr->token.n, 0); + pParse->nErr++; + return 1; + }else if( cnt>1 ){ + sqliteSetNString(&pParse->zErrMsg, "ambiguous field name: ", -1, + pExpr->token.z, pExpr->token.n, 0); + pParse->nErr++; + return 1; + } + pExpr->op = TK_FIELD; + break; + } + + /* A table name and field name: ID.ID */ + case TK_DOT: { + int cnt = 0; /* Number of matches */ + int i; /* Loop counter */ + Expr *pLeft, *pRight; /* Left and right subbranches of the expr */ + int n; /* Length of an identifier */ + char *zLeft, *zRight; /* Text of an identifier */ + + pLeft = pExpr->pLeft; + pRight = pExpr->pRight; + assert( pLeft && pLeft->op==TK_ID ); + assert( pRight && pRight->op==TK_ID ); + zLeft = 0; + sqliteSetNString(&zLeft, pLeft->token.z, pLeft->token.n, 0); + zRight = 0; + sqliteSetNString(&zRight, pRight->token.z, pRight->token.n, 0); + for(i=0; inId; i++){ + int j; + char *zTab; + Table *pTab = pTabList->a[i].pTab; + if( pTab==0 ) continue; + if( pTabList->a[i].zAlias ){ + zTab = pTabList->a[i].zAlias; + }else{ + zTab = pTab->zName; + } + if( sqliteStrICmp(zTab, zLeft)!=0 ) continue; + for(j=0; jnCol; j++){ + if( sqliteStrICmp(pTab->azCol[j], zRight)==0 ){ + cnt++; + pExpr->iTable = i; + pExpr->iField = j; + } + } + } + sqliteFree(zLeft); + sqliteFree(zRight); + if( cnt==0 ){ + sqliteSetNString(&pParse->zErrMsg, "no such field: ", -1, + pLeft->token.z, pLeft->token.n, ".", 1, + pRight->token.z, pRight->token.n, 0); + pParse->nErr++; + return 1; + }else if( cnt>1 ){ + sqliteSetNString(&pParse->zErrMsg, "ambiguous field name: ", -1, + pLeft->token.z, pLeft->token.n, ".", 1, + pRight->token.z, pRight->token.n, 0); + pParse->nErr++; + return 1; + } + sqliteExprDelete(pLeft); + pExpr->pLeft = 0; + sqliteExprDelete(pRight); + pExpr->pRight = 0; + pExpr->op = TK_FIELD; + break; + } + + /* For all else, just recursively walk the tree */ + default: { + if( pExpr->pLeft + && sqliteExprResolveIds(pParse, pTabList, pExpr->pLeft) ){ + return 1; + } + if( pExpr->pRight + && sqliteExprResolveIds(pParse, pTabList, pExpr->pRight) ){ + return 1; + } + if( pExpr->pList ){ + int i; + ExprList *pList = pExpr->pList; + for(i=0; inExpr; i++){ + if( sqliteExprResolveIds(pParse, pTabList, pList->a[i].pExpr) ){ + return 1; + } + } + } + } + } + return 0; +} + +#if 0 /* NOT USED */ +/* +** Compare a token against a string. Return TRUE if they match. +*/ +static int sqliteTokenCmp(Token *pToken, const char *zStr){ + int n = strlen(zStr); + if( n!=pToken->n ) return 0; + return sqliteStrNICmp(pToken->z, zStr, n)==0; +} +#endif + +/* +** Convert a function name into its integer identifier. Return the +** identifier. Return FN_Unknown if the function name is unknown. +*/ +int sqliteFuncId(Token *pToken){ + static const struct { + char *zName; + int len; + int id; + } aFunc[] = { + { "count", 5, FN_Count }, + { "min", 3, FN_Min }, + { "max", 3, FN_Max }, + { "sum", 3, FN_Sum }, + }; + int i; + for(i=0; in + && sqliteStrNICmp(pToken->z, aFunc[i].zName, aFunc[i].len)==0 ){ + return aFunc[i].id; + } + } + return FN_Unknown; +} + +/* +** Error check the functions in an expression. Make sure all +** function names are recognized and all functions have the correct +** number of arguments. Leave an error message in pParse->zErrMsg +** if anything is amiss. Return the number of errors. +** +** if pIsAgg is not null and this expression is an aggregate function +** (like count(*) or max(value)) then write a 1 into *pIsAgg. +*/ +int sqliteExprCheck(Parse *pParse, Expr *pExpr, int allowAgg, int *pIsAgg){ + int nErr = 0; + if( pExpr==0 ) return 0; + if( pIsAgg ) *pIsAgg = 0; + switch( pExpr->op ){ + case TK_FUNCTION: { + int id = sqliteFuncId(&pExpr->token); + int n = pExpr->pList ? pExpr->pList->nExpr : 0; + int no_such_func = 0; + int too_many_args = 0; + int too_few_args = 0; + int is_agg = 0; + int i; + switch( id ){ + case FN_Unknown: { + no_such_func = 1; + break; + } + case FN_Count: { + no_such_func = !allowAgg; + too_many_args = n>1; + is_agg = 1; + break; + } + case FN_Max: + case FN_Min: { + too_few_args = allowAgg ? n<1 : n<2; + is_agg = n==1; + break; + } + case FN_Sum: { + no_such_func = !allowAgg; + too_many_args = n>1; + too_few_args = n<1; + is_agg = 1; + break; + } + default: break; + } + if( no_such_func ){ + sqliteSetNString(&pParse->zErrMsg, "no such function: ", -1, + pExpr->token.z, pExpr->token.n, 0); + pParse->nErr++; + nErr++; + }else if( too_many_args ){ + sqliteSetNString(&pParse->zErrMsg, "too many arguments to function ",-1, + pExpr->token.z, pExpr->token.n, "()", 2, 0); + pParse->nErr++; + nErr++; + }else if( too_few_args ){ + sqliteSetNString(&pParse->zErrMsg, "too few arguments to function ",-1, + pExpr->token.z, pExpr->token.n, "()", 2, 0); + pParse->nErr++; + nErr++; + } + if( is_agg && pIsAgg ) *pIsAgg = 1; + for(i=0; nErr==0 && ipList->a[i].pExpr, 0, 0); + } + } + default: { + if( pExpr->pLeft ){ + nErr = sqliteExprCheck(pParse, pExpr->pLeft, 0, 0); + } + if( nErr==0 && pExpr->pRight ){ + nErr = sqliteExprCheck(pParse, pExpr->pRight, 0, 0); + } + break; + } + } + return nErr; +} + +/* +** Generate code into the current Vdbe to evaluate the given +** expression and leave the result on the stack. +*/ +void sqliteExprCode(Parse *pParse, Expr *pExpr){ + Vdbe *v = pParse->pVdbe; + int op; + switch( pExpr->op ){ + case TK_PLUS: op = OP_Add; break; + case TK_MINUS: op = OP_Subtract; break; + case TK_STAR: op = OP_Multiply; break; + case TK_SLASH: op = OP_Divide; break; + case TK_AND: op = OP_And; break; + case TK_OR: op = OP_Or; break; + case TK_LT: op = OP_Lt; break; + case TK_LE: op = OP_Le; break; + case TK_GT: op = OP_Gt; break; + case TK_GE: op = OP_Ge; break; + case TK_NE: op = OP_Ne; break; + case TK_EQ: op = OP_Eq; break; + case TK_LIKE: op = OP_Like; break; + case TK_GLOB: op = OP_Glob; break; + case TK_ISNULL: op = OP_IsNull; break; + case TK_NOTNULL: op = OP_NotNull; break; + case TK_NOT: op = OP_Not; break; + case TK_UMINUS: op = OP_Negative; break; + default: break; + } + switch( pExpr->op ){ + case TK_FIELD: { + sqliteVdbeAddOp(v, OP_Field, pExpr->iTable, pExpr->iField, 0, 0); + break; + } + case TK_INTEGER: { + int i = atoi(pExpr->token.z); + sqliteVdbeAddOp(v, OP_Integer, i, 0, 0, 0); + break; + } + case TK_FLOAT: { + int addr = sqliteVdbeAddOp(v, OP_String, 0, 0, 0, 0); + sqliteVdbeChangeP3(v, addr, pExpr->token.z, pExpr->token.n); + break; + } + case TK_STRING: { + int addr = sqliteVdbeAddOp(v, OP_String, 0, 0, 0, 0); + sqliteVdbeChangeP3(v, addr, pExpr->token.z, pExpr->token.n); + sqliteVdbeDequoteP3(v, addr); + break; + } + case TK_NULL: { + sqliteVdbeAddOp(v, OP_String, 0, 0, "", 0); + break; + } + case TK_AND: + case TK_OR: + case TK_PLUS: + case TK_STAR: + case TK_MINUS: + case TK_SLASH: { + sqliteExprCode(pParse, pExpr->pLeft); + sqliteExprCode(pParse, pExpr->pRight); + sqliteVdbeAddOp(v, op, 0, 0, 0, 0); + break; + } + case TK_LT: + case TK_LE: + case TK_GT: + case TK_GE: + case TK_NE: + case TK_EQ: + case TK_LIKE: + case TK_GLOB: { + int dest; + sqliteVdbeAddOp(v, OP_Integer, 1, 0, 0, 0); + sqliteExprCode(pParse, pExpr->pLeft); + sqliteExprCode(pParse, pExpr->pRight); + dest = sqliteVdbeCurrentAddr(v) + 2; + sqliteVdbeAddOp(v, op, 0, dest, 0, 0); + sqliteVdbeAddOp(v, OP_AddImm, -1, 0, 0, 0); + break; + } + case TK_NOT: + case TK_UMINUS: { + sqliteExprCode(pParse, pExpr->pLeft); + sqliteVdbeAddOp(v, op, 0, 0, 0, 0); + break; + } + case TK_ISNULL: + case TK_NOTNULL: { + int dest; + sqliteVdbeAddOp(v, OP_Integer, 0, 0, 0, 0); + sqliteExprCode(pParse, pExpr->pLeft); + dest = sqliteVdbeCurrentAddr(v) + 2; + sqliteVdbeAddOp(v, op, 0, dest, 0, 0); + sqliteVdbeAddOp(v, OP_AddImm, 1, 0, 0, 0); + break; + } + case TK_FUNCTION: { + int id = sqliteFuncId(&pExpr->token); + int op; + int i; + ExprList *pList = pExpr->pList; + op = id==FN_Min ? OP_Min : OP_Max; + for(i=0; inExpr; i++){ + sqliteExprCode(pParse, pList->a[i].pExpr); + if( i>0 ){ + sqliteVdbeAddOp(v, op, 0, 0, 0, 0); + } + } + break; + } + } + return; +} + +/* +** Generate code for a boolean expression such that a jump is made +** to the label "dest" if the expression is true but execution +** continues straight thru if the expression is false. +*/ +void sqliteExprIfTrue(Parse *pParse, Expr *pExpr, int dest){ + Vdbe *v = pParse->pVdbe; + int op = 0; + switch( pExpr->op ){ + case TK_LT: op = OP_Lt; break; + case TK_LE: op = OP_Le; break; + case TK_GT: op = OP_Gt; break; + case TK_GE: op = OP_Ge; break; + case TK_NE: op = OP_Ne; break; + case TK_EQ: op = OP_Eq; break; + case TK_LIKE: op = OP_Like; break; + case TK_GLOB: op = OP_Glob; break; + case TK_ISNULL: op = OP_IsNull; break; + case TK_NOTNULL: op = OP_NotNull; break; + default: break; + } + switch( pExpr->op ){ + case TK_AND: { + int d2 = sqliteVdbeMakeLabel(v); + sqliteExprIfFalse(pParse, pExpr->pLeft, d2); + sqliteExprIfTrue(pParse, pExpr->pRight, dest); + sqliteVdbeResolveLabel(v, d2); + break; + } + case TK_OR: { + sqliteExprIfTrue(pParse, pExpr->pLeft, dest); + sqliteExprIfTrue(pParse, pExpr->pRight, dest); + break; + } + case TK_NOT: { + sqliteExprIfFalse(pParse, pExpr->pLeft, dest); + break; + } + case TK_LT: + case TK_LE: + case TK_GT: + case TK_GE: + case TK_NE: + case TK_EQ: + case TK_LIKE: + case TK_GLOB: { + sqliteExprCode(pParse, pExpr->pLeft); + sqliteExprCode(pParse, pExpr->pRight); + sqliteVdbeAddOp(v, op, 0, dest, 0, 0); + break; + } + case TK_ISNULL: + case TK_NOTNULL: { + sqliteExprCode(pParse, pExpr->pLeft); + sqliteVdbeAddOp(v, op, 0, dest, 0, 0); + break; + } + default: { + sqliteExprCode(pParse, pExpr); + sqliteVdbeAddOp(v, OP_If, 0, dest, 0, 0); + break; + } + } +} + +/* +** Generate code for boolean expression such that a jump is made +** to the label "dest" if the expression is false but execution +** continues straight thru if the expression is true. +*/ +void sqliteExprIfFalse(Parse *pParse, Expr *pExpr, int dest){ + Vdbe *v = pParse->pVdbe; + int op = 0; + switch( pExpr->op ){ + case TK_LT: op = OP_Ge; break; + case TK_LE: op = OP_Gt; break; + case TK_GT: op = OP_Le; break; + case TK_GE: op = OP_Lt; break; + case TK_NE: op = OP_Eq; break; + case TK_EQ: op = OP_Ne; break; + case TK_LIKE: op = OP_Like; break; + case TK_GLOB: op = OP_Glob; break; + case TK_ISNULL: op = OP_NotNull; break; + case TK_NOTNULL: op = OP_IsNull; break; + default: break; + } + switch( pExpr->op ){ + case TK_AND: { + sqliteExprIfFalse(pParse, pExpr->pLeft, dest); + sqliteExprIfFalse(pParse, pExpr->pRight, dest); + break; + } + case TK_OR: { + int d2 = sqliteVdbeMakeLabel(v); + sqliteExprIfTrue(pParse, pExpr->pLeft, d2); + sqliteExprIfFalse(pParse, pExpr->pRight, dest); + sqliteVdbeResolveLabel(v, d2); + break; + } + case TK_NOT: { + sqliteExprIfTrue(pParse, pExpr->pLeft, dest); + break; + } + case TK_LT: + case TK_LE: + case TK_GT: + case TK_GE: + case TK_NE: + case TK_EQ: { + sqliteExprCode(pParse, pExpr->pLeft); + sqliteExprCode(pParse, pExpr->pRight); + sqliteVdbeAddOp(v, op, 0, dest, 0, 0); + break; + } + case TK_LIKE: + case TK_GLOB: { + sqliteExprCode(pParse, pExpr->pLeft); + sqliteExprCode(pParse, pExpr->pRight); + sqliteVdbeAddOp(v, op, 1, dest, 0, 0); + break; + } + case TK_ISNULL: + case TK_NOTNULL: { + sqliteExprCode(pParse, pExpr->pLeft); + sqliteVdbeAddOp(v, op, 0, dest, 0, 0); + break; + } + default: { + sqliteExprCode(pParse, pExpr); + sqliteVdbeAddOp(v, OP_Not, 0, 0, 0, 0); + sqliteVdbeAddOp(v, OP_If, 0, dest, 0, 0); + break; + } + } +} diff --git a/src/insert.c b/src/insert.c new file mode 100644 index 0000000000..071e642c7d --- /dev/null +++ b/src/insert.c @@ -0,0 +1,158 @@ +/* +** Copyright (c) 1999, 2000 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public +** License as published by the Free Software Foundation; either +** version 2 of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** General Public License for more details. +** +** You should have received a copy of the GNU General Public +** License along with this library; if not, write to the +** Free Software Foundation, Inc., 59 Temple Place - Suite 330, +** Boston, MA 02111-1307, USA. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +************************************************************************* +** This file contains C code routines that are called by the parser +** to handle INSERT statements. +** +** $Id: insert.c,v 1.1 2000/05/31 15:34:53 drh Exp $ +*/ +#include "sqliteInt.h" + +/* +** This routine is call to handle SQL of the following form: +** +** insert into TABLE (IDLIST) values(EXPRLIST) +** +** The parameters are the table name and the expression list. +*/ +void sqliteInsert( + Parse *pParse, /* Parser context */ + Token *pTableName, /* Name of table into which we are inserting */ + ExprList *pList, /* List of values to be inserted */ + IdList *pField /* Field name corresponding to pList. Might be NULL */ +){ + Table *pTab; + char *zTab; + int i, j; + Vdbe *v; + + zTab = sqliteTableNameFromToken(pTableName); + pTab = sqliteFindTable(pParse->db, zTab); + sqliteFree(zTab); + if( pTab==0 ){ + sqliteSetNString(&pParse->zErrMsg, "no such table: ", 0, + pTableName->z, pTableName->n, 0); + pParse->nErr++; + goto insert_cleanup; + } + if( pTab->readOnly ){ + sqliteSetString(&pParse->zErrMsg, "table ", pTab->zName, + " may not be modified", 0); + pParse->nErr++; + goto insert_cleanup; + } + if( pField==0 && pList->nExpr!=pTab->nCol ){ + char zNum1[30]; + char zNum2[30]; + sprintf(zNum1,"%d", pList->nExpr); + sprintf(zNum2,"%d", pTab->nCol); + sqliteSetString(&pParse->zErrMsg, "table ", pTab->zName, + " has ", zNum2, " columns but ", + zNum1, " values were supplied", 0); + pParse->nErr++; + goto insert_cleanup; + } + if( pField!=0 && pList->nExpr!=pField->nId ){ + char zNum1[30]; + char zNum2[30]; + sprintf(zNum1,"%d", pList->nExpr); + sprintf(zNum2,"%d", pField->nId); + sqliteSetString(&pParse->zErrMsg, zNum1, " values for ", + zNum2, " columns", 0); + pParse->nErr++; + goto insert_cleanup; + } + if( pField ){ + for(i=0; inId; i++){ + pField->a[i].idx = -1; + } + for(i=0; inId; i++){ + for(j=0; jnCol; j++){ + if( sqliteStrICmp(pField->a[i].zName, pTab->azCol[j])==0 ){ + pField->a[i].idx = j; + break; + } + } + if( j>=pTab->nCol ){ + sqliteSetString(&pParse->zErrMsg, "table ", pTab->zName, + " has no column named ", pField->a[i].zName, 0); + pParse->nErr++; + goto insert_cleanup; + } + } + } + v = pParse->pVdbe = sqliteVdbeCreate(pParse->db->pBe); + if( v ){ + Index *pIdx; + sqliteVdbeAddOp(v, OP_Open, 0, 0, pTab->zName, 0); + sqliteVdbeAddOp(v, OP_New, 0, 0, 0, 0); + if( pTab->pIndex ){ + sqliteVdbeAddOp(v, OP_Dup, 0, 0, 0, 0); + } + for(i=0; inCol; i++){ + if( pField==0 ){ + j = i; + }else{ + for(j=0; jnId; j++){ + if( pField->a[j].idx==i ) break; + } + } + if( pField && j>=pField->nId ){ + sqliteVdbeAddOp(v, OP_String, 0, 0, "", 0); + }else{ + sqliteExprCode(pParse, pList->a[j].pExpr); + } + } + sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0, 0, 0); + sqliteVdbeAddOp(v, OP_Put, 0, 0, 0, 0); + sqliteVdbeAddOp(v, OP_Close, 0, 0, 0, 0); + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( pIdx->pNext ){ + sqliteVdbeAddOp(v, OP_Dup, 0, 0, 0, 0); + } + sqliteVdbeAddOp(v, OP_Open, 0, 0, pIdx->zName, 0); + for(i=0; inField; i++){ + int idx = pIdx->aiField[i]; + if( pField==0 ){ + j = idx; + }else{ + for(j=0; jnId; j++){ + if( pField->a[j].idx==idx ) break; + } + } + if( pField && j>=pField->nId ){ + sqliteVdbeAddOp(v, OP_String, 0, 0, "", 0); + }else{ + sqliteExprCode(pParse, pList->a[j].pExpr); + } + } + sqliteVdbeAddOp(v, OP_MakeKey, pIdx->nField, 0, 0, 0); + sqliteVdbeAddOp(v, OP_PutIdx, 0, 0, 0, 0); + sqliteVdbeAddOp(v, OP_Close, 0, 0, 0, 0); + } + } + +insert_cleanup: + sqliteExprListDelete(pList); + sqliteIdListDelete(pField); +} diff --git a/src/parse.y b/src/parse.y index 89d003575c..750e172013 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.3 2000/05/31 02:27:49 drh Exp $ +** @(#) $Id: parse.y,v 1.4 2000/05/31 15:34:53 drh Exp $ */ %token_prefix TK_ %token_type {Token} @@ -254,8 +254,8 @@ expr(A) ::= ID(X) DOT ID(Y). {Expr *temp1 = sqliteExpr(TK_ID, 0, 0, &X); expr(A) ::= INTEGER(X). {A = sqliteExpr(TK_INTEGER, 0, 0, &X);} expr(A) ::= FLOAT(X). {A = sqliteExpr(TK_FLOAT, 0, 0, &X);} expr(A) ::= STRING(X). {A = sqliteExpr(TK_STRING, 0, 0, &X);} -// expr(A) ::= ID(X) LP exprlist(Y) RP. {A = sqliteExprFunction(Y, &X);} -// expr(A) ::= ID(X) LP STAR RP. {A = sqliteExprFunction(0, &X);} +expr(A) ::= ID(X) LP exprlist(Y) RP. {A = sqliteExprFunction(Y, &X);} +expr(A) ::= ID(X) LP STAR RP. {A = sqliteExprFunction(0, &X);} expr(A) ::= expr(X) AND expr(Y). {A = sqliteExpr(TK_AND, X, Y, 0);} expr(A) ::= expr(X) OR expr(Y). {A = sqliteExpr(TK_OR, X, Y, 0);} expr(A) ::= expr(X) LT expr(Y). {A = sqliteExpr(TK_LT, X, Y, 0);} @@ -282,13 +282,12 @@ expr(A) ::= PLUS expr(X). [NOT] {A = X;} %type expritem {Expr*} %destructor expritem {sqliteExprDelete($$);} -/* exprlist(A) ::= exprlist(X) COMMA expritem(Y). {A = sqliteExprListAppend(X,Y,0);} exprlist(A) ::= expritem(X). {A = sqliteExprListAppend(0,X,0);} expritem(A) ::= expr(X). {A = X;} expritem(A) ::= . {A = 0;} -*/ + cmd ::= CREATE(S) uniqueflag INDEX ID(X) ON ID(Y) LP idxlist(Z) RP(E). {sqliteCreateIndex(pParse, &X, &Y, Z, &S, &E);} diff --git a/src/select.c b/src/select.c new file mode 100644 index 0000000000..1a9416e348 --- /dev/null +++ b/src/select.c @@ -0,0 +1,271 @@ +/* +** Copyright (c) 1999, 2000 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public +** License as published by the Free Software Foundation; either +** version 2 of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** General Public License for more details. +** +** You should have received a copy of the GNU General Public +** License along with this library; if not, write to the +** Free Software Foundation, Inc., 59 Temple Place - Suite 330, +** Boston, MA 02111-1307, USA. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +************************************************************************* +** This file contains C code routines that are called by the parser +** to handle SELECT statements. +** +** $Id: select.c,v 1.1 2000/05/31 15:34:53 drh Exp $ +*/ +#include "sqliteInt.h" + + +/* +** Process a SELECT statement. +*/ +void sqliteSelect( + Parse *pParse, /* The parser context */ + ExprList *pEList, /* List of fields to extract. NULL means "*" */ + 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 */ +){ + int i, j; + WhereInfo *pWInfo; + Vdbe *v; + int isAgg = 0; /* True for select lists like "count(*)" */ + + if( pParse->nErr>0 ) goto select_cleanup; + + /* Look up every table in the table list. + */ + for(i=0; inId; i++){ + pTabList->a[i].pTab = sqliteFindTable(pParse->db, pTabList->a[i].zName); + if( pTabList->a[i].pTab==0 ){ + sqliteSetString(&pParse->zErrMsg, "no such table: ", + pTabList->a[i].zName, 0); + pParse->nErr++; + goto select_cleanup; + } + } + + /* If the list of fields to retrieve is "*" then replace it with + ** a list of all fields from all tables. + */ + if( pEList==0 ){ + for(i=0; inId; i++){ + Table *pTab = pTabList->a[i].pTab; + for(j=0; jnCol; j++){ + Expr *pExpr = sqliteExpr(TK_FIELD, 0, 0, 0); + pExpr->iTable = i; + pExpr->iField = j; + pEList = sqliteExprListAppend(pEList, pExpr, 0); + } + } + } + + /* 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) ){ + goto select_cleanup; + } + if( sqliteExprCheck(pParse, pEList->a[i].pExpr, 1, &pEList->a[i].isAgg) ){ + goto select_cleanup; + } + } + 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++; + goto select_cleanup; + } + } + } + if( pWhere ){ + if( sqliteExprResolveIds(pParse, pTabList, pWhere) ){ + goto select_cleanup; + } + if( sqliteExprCheck(pParse, pWhere, 0, 0) ){ + goto select_cleanup; + } + } + if( pOrderBy ){ + for(i=0; inExpr; i++){ + if( sqliteExprResolveIds(pParse, pTabList, pOrderBy->a[i].pExpr) ){ + goto select_cleanup; + } + if( sqliteExprCheck(pParse, pOrderBy->a[i].pExpr, 0, 0) ){ + goto select_cleanup; + } + } + } + + /* ORDER BY is ignored if this is an aggregate query like count(*) + ** since only one row will be returned. + */ + if( isAgg && pOrderBy ){ + sqliteExprListDelete(pOrderBy); + pOrderBy = 0; + } + + /* Begin generating code. + */ + v = pParse->pVdbe; + if( v==0 ){ + v = pParse->pVdbe = sqliteVdbeCreate(pParse->db->pBe); + } + if( v==0 ) goto select_cleanup; + if( pOrderBy ){ + sqliteVdbeAddOp(v, OP_SortOpen, 0, 0, 0, 0); + } + + /* Identify column names + */ + sqliteVdbeAddOp(v, OP_ColumnCount, pEList->nExpr, 0, 0, 0); + for(i=0; inExpr; i++){ + Expr *p; + if( pEList->a[i].zName ){ + char *zName = pEList->a[i].zName; + int addr = sqliteVdbeAddOp(v, OP_ColumnName, i, 0, zName, 0); + if( zName[0]=='\'' || zName[0]=='"' ){ + sqliteVdbeDequoteP3(v, addr); + } + continue; + } + p = pEList->a[i].pExpr; + if( p->op!=TK_FIELD ){ + char zName[30]; + sprintf(zName, "field%d", i+1); + sqliteVdbeAddOp(v, OP_ColumnName, i, 0, zName, 0); + }else{ + if( pTabList->nId>1 ){ + char *zName = 0; + Table *pTab = pTabList->a[p->iTable].pTab; + sqliteSetString(&zName, pTab->zName, ".", + pTab->azCol[p->iField], 0); + sqliteVdbeAddOp(v, OP_ColumnName, i, 0, zName, 0); + sqliteFree(zName); + }else{ + Table *pTab = pTabList->a[0].pTab; + sqliteVdbeAddOp(v, OP_ColumnName, i, 0, pTab->azCol[p->iField], 0); + } + } + } + + /* Initialize the stack to contain aggregate seed values + */ + 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_String, 0, 0, "", 0); + break; + } + default: { + sqliteVdbeAddOp(v, OP_Integer, 0, 0, 0, 0); + break; + } + } + } + } + + /* Begin the database scan + */ + pWInfo = sqliteWhereBegin(pParse, pTabList, pWhere, 0); + if( pWInfo==0 ) goto select_cleanup; + + /* Pull the requested fields. + */ + for(i=0; inExpr; i++){ + sqliteExprCode(pParse, pEList->a[i].pExpr); + } + + /* 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 ) goto select_cleanup; + 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( 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( id!=FN_Count && p->pList && p->pList->nExpr>=1 ){ + sqliteExprCode(pParse, p->pList->a[0].pExpr); + } + 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); + } + }else{ + sqliteVdbeAddOp(v, OP_Callback, pEList->nExpr, 0, 0, 0); + } + + /* End the database scan loop. + */ + sqliteWhereEnd(pWInfo); + + /* If there is an ORDER BY clause, then we need to sort the results + ** and send them to the callback one by one. + */ + if( pOrderBy ){ + int end = sqliteVdbeMakeLabel(v); + int addr; + sqliteVdbeAddOp(v, OP_Sort, 0, 0, 0, 0); + addr = sqliteVdbeAddOp(v, OP_SortNext, 0, end, 0, 0); + sqliteVdbeAddOp(v, OP_SortCallback, pEList->nExpr, 0, 0, 0); + sqliteVdbeAddOp(v, OP_Goto, 0, addr, 0, 0); + sqliteVdbeAddOp(v, OP_Noop, 0, 0, 0, end); + } + + /* If this is an aggregate, then we need to invoke the callback + ** exactly once. + */ + if( isAgg ){ + sqliteVdbeAddOp(v, OP_Callback, pEList->nExpr, 0, 0, 0); + } + + /* Always execute the following code before exiting, in order to + ** release resources. + */ +select_cleanup: + sqliteExprListDelete(pEList); + sqliteIdListDelete(pTabList); + sqliteExprDelete(pWhere); + sqliteExprListDelete(pOrderBy); + return; +} diff --git a/src/sqliteInt.h b/src/sqliteInt.h index c64a147379..2c1491d8d3 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -23,7 +23,7 @@ ************************************************************************* ** Internal interface definitions for SQLite. ** -** @(#) $Id: sqliteInt.h,v 1.4 2000/05/31 02:27:49 drh Exp $ +** @(#) $Id: sqliteInt.h,v 1.5 2000/05/31 15:34:53 drh Exp $ */ #include "sqlite.h" #include "dbbe.h" @@ -61,6 +61,16 @@ */ #define ArraySize(X) (sizeof(X)/sizeof(X[0])) +/* +** Integer identifiers for functions. +*/ +#define FN_Unknown 0 +#define FN_Count 1 +#define FN_Min 2 +#define FN_Max 3 +#define FN_Sum 4 +#define FN_Avg 5 + /* ** Forward references to structures */ @@ -151,6 +161,7 @@ struct ExprList { Expr *pExpr; /* The list of expressions */ char *zName; /* Token associated with this expression */ int idx; /* ... */ + int isAgg; /* True if this is an aggregate like count(*) */ } *a; /* One entry for each expression */ }; @@ -251,3 +262,6 @@ void sqliteCopy(Parse*, Token*, Token*, Token*); void sqliteVacuum(Parse*, Token*); int sqliteGlobCompare(const char*,const char*); int sqliteLikeCompare(const unsigned char*,const unsigned char*); +char *sqliteTableNameFromToken(Token*); +int sqliteExprCheck(Parse*, Expr*, int, int*); +int sqliteFuncId(Token*); diff --git a/src/update.c b/src/update.c new file mode 100644 index 0000000000..a19846a91d --- /dev/null +++ b/src/update.c @@ -0,0 +1,225 @@ +/* +** Copyright (c) 1999, 2000 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public +** License as published by the Free Software Foundation; either +** version 2 of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** General Public License for more details. +** +** You should have received a copy of the GNU General Public +** License along with this library; if not, write to the +** Free Software Foundation, Inc., 59 Temple Place - Suite 330, +** Boston, MA 02111-1307, USA. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +************************************************************************* +** This file contains C code routines that are called by the parser +** to handle UPDATE statements. +** +** $Id: update.c,v 1.1 2000/05/31 15:34:53 drh Exp $ +*/ +#include "sqliteInt.h" + +/* +** Process an UPDATE statement. +*/ +void sqliteUpdate( + Parse *pParse, /* The parser context */ + Token *pTableName, /* The table in which we should change things */ + ExprList *pChanges, /* Things to be changed */ + Expr *pWhere /* The WHERE clause. May be null */ +){ + int i, j; /* Loop counters */ + Table *pTab; /* The table to be updated */ + IdList *pTabList = 0; /* List containing only pTab */ + int end, addr; /* A couple of addresses in the generated code */ + WhereInfo *pWInfo; /* Information about the WHERE clause */ + Vdbe *v; /* The virtual database engine */ + Index *pIdx; /* For looping over indices */ + int nIdx; /* Number of indices that need updating */ + Index **apIdx = 0; /* An array of indices that need updating too */ + int *aXRef = 0; /* aXRef[i] is the index in pChanges->a[] of the + ** an expression for the i-th field of the table. + ** aXRef[i]==-1 if the i-th field is not changed. */ + + /* Locate the table which we want to update. This table has to be + ** put in an IdList structure because some of the subroutines will + ** will be calling are designed to work with multiple tables and expect + ** an IdList* parameter instead of just a Table* parameger. + */ + pTabList = sqliteIdListAppend(0, pTableName); + for(i=0; inId; i++){ + pTabList->a[i].pTab = sqliteFindTable(pParse->db, pTabList->a[i].zName); + if( pTabList->a[i].pTab==0 ){ + sqliteSetString(&pParse->zErrMsg, "no such table: ", + pTabList->a[i].zName, 0); + pParse->nErr++; + goto update_cleanup; + } + if( pTabList->a[i].pTab->readOnly ){ + sqliteSetString(&pParse->zErrMsg, "table ", pTabList->a[i].zName, + " may not be modified", 0); + pParse->nErr++; + goto update_cleanup; + } + } + pTab = pTabList->a[0].pTab; + aXRef = sqliteMalloc( sizeof(int) * pTab->nCol ); + if( aXRef==0 ) goto update_cleanup; + for(i=0; inCol; i++) aXRef[i] = -1; + + /* Resolve the field names in all the expressions in both the + ** WHERE clause and in the new values. Also find the field index + ** for each field to be updated in the pChanges array. + */ + if( pWhere ){ + if( sqliteExprResolveIds(pParse, pTabList, pWhere) ){ + goto update_cleanup; + } + if( sqliteExprCheck(pParse, pWhere, 0, 0) ){ + goto update_cleanup; + } + } + for(i=0; inExpr; i++){ + if( sqliteExprResolveIds(pParse, pTabList, pChanges->a[i].pExpr) ){ + goto update_cleanup; + } + if( sqliteExprCheck(pParse, pChanges->a[i].pExpr, 0, 0) ){ + goto update_cleanup; + } + for(j=0; jnCol; j++){ + if( strcmp(pTab->azCol[j], pChanges->a[i].zName)==0 ){ + pChanges->a[i].idx = j; + aXRef[j] = i; + break; + } + } + if( j>=pTab->nCol ){ + sqliteSetString(&pParse->zErrMsg, "no such field: ", + pChanges->a[i].zName, 0); + pParse->nErr++; + goto update_cleanup; + } + } + + /* Allocate memory for the array apIdx[] and fill it pointers to every + ** index that needs to be updated. Indices only need updating if their + ** key includes one of the fields named in pChanges. + */ + for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + for(i=0; inField; i++){ + if( aXRef[pIdx->aiField[i]]>=0 ) break; + } + if( inField ) nIdx++; + } + apIdx = sqliteMalloc( sizeof(Index*) * nIdx ); + if( apIdx==0 ) goto update_cleanup; + for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + for(i=0; inField; i++){ + if( aXRef[pIdx->aiField[i]]>=0 ) break; + } + if( inField ) apIdx[nIdx++] = pIdx; + } + + /* Begin generating code. + */ + v = pParse->pVdbe; + if( v==0 ){ + v = pParse->pVdbe = sqliteVdbeCreate(pParse->db->pBe); + } + if( v==0 ) goto update_cleanup; + + /* Begin the database scan + */ + sqliteVdbeAddOp(v, OP_ListOpen, 0, 0, 0, 0); + pWInfo = sqliteWhereBegin(pParse, pTabList, pWhere, 1); + if( pWInfo==0 ) goto update_cleanup; + + /* Remember the index of every item to be updated. + */ + sqliteVdbeAddOp(v, OP_ListWrite, 0, 0, 0, 0); + + /* End the database scan loop. + */ + sqliteWhereEnd(pWInfo); + + /* Rewind the list of records that need to be updated and + ** open every index that needs updating. + */ + sqliteVdbeAddOp(v, OP_ListRewind, 0, 0, 0, 0); + for(i=0; izName, 0); + } + + /* Loop over every record that needs updating. We have to load + ** the old data for each record to be updated because some fields + ** might not change and we will need to copy the old value, therefore. + ** Also, the old data is needed to delete the old index entires. + */ + end = sqliteVdbeMakeLabel(v); + addr = sqliteVdbeAddOp(v, OP_ListRead, 0, end, 0, 0); + sqliteVdbeAddOp(v, OP_Dup, 0, 0, 0, 0); + sqliteVdbeAddOp(v, OP_Fetch, 0, 0, 0, 0); + + /* Delete the old indices for the current record. + */ + for(i=0; inField; j++){ + sqliteVdbeAddOp(v, OP_Field, 0, pIdx->aiField[j], 0, 0); + } + sqliteVdbeAddOp(v, OP_MakeKey, pIdx->nField, 0, 0, 0); + sqliteVdbeAddOp(v, OP_DeleteIdx, i+1, 0, 0, 0); + } + + /* Compute a completely new data for this record. + */ + for(i=0; inCol; i++){ + j = aXRef[i]; + if( j<0 ){ + sqliteVdbeAddOp(v, OP_Field, 0, i, 0, 0); + }else{ + sqliteExprCode(pParse, pChanges->a[j].pExpr); + } + } + + /* Insert new index entries that correspond to the new data + */ + for(i=0; inCol, 0, 0, 0); /* The KEY */ + pIdx = apIdx[i]; + for(j=0; jnField; j++){ + sqliteVdbeAddOp(v, OP_Dup, j+pTab->nCol-pIdx->aiField[j], 0, 0, 0); + } + sqliteVdbeAddOp(v, OP_MakeKey, pIdx->nField, 0, 0, 0); + sqliteVdbeAddOp(v, OP_PutIdx, i+1, 0, 0, 0); + } + + /* Write the new data back into the database. + */ + sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0, 0, 0); + sqliteVdbeAddOp(v, OP_Put, 0, 0, 0, 0); + + /* Repeat the above with the next record to be updated, until + ** all record selected by the WHERE clause have been updated. + */ + sqliteVdbeAddOp(v, OP_Goto, 0, addr, 0, 0); + sqliteVdbeAddOp(v, OP_ListClose, 0, 0, 0, end); + +update_cleanup: + sqliteFree(apIdx); + sqliteFree(aXRef); + sqliteIdListDelete(pTabList); + sqliteExprListDelete(pChanges); + sqliteExprDelete(pWhere); + return; +} diff --git a/src/vdbe.c b/src/vdbe.c index 62ed2e9565..195210a30d 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.3 2000/05/31 02:27:50 drh Exp $ +** $Id: vdbe.c,v 1.4 2000/05/31 15:34:53 drh Exp $ */ #include "sqliteInt.h" @@ -879,6 +879,7 @@ int sqliteVdbeExec( sqliteFree(p->zStack[nos]); p->zStack[nos] = p->zStack[tos]; p->iStack[nos] = p->iStack[tos]; + p->zStack[tos] = 0; } } p->tos--; @@ -888,7 +889,10 @@ int sqliteVdbeExec( /* Opcode: Min * * * ** ** Pop the top two elements from the stack then push back the - ** smaller of the two. + ** 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; @@ -901,10 +905,17 @@ int sqliteVdbeExec( }else{ Stringify(p, tos); Stringify(p, nos); - if( sqliteCompare(p->zStack[nos], p->zStack[tos])>0 ){ + if( pOp->p1==1 ){ sqliteFree(p->zStack[nos]); p->zStack[nos] = p->zStack[tos]; p->iStack[nos] = p->iStack[tos]; + p->zStack[tos] = 0; + pOp->p1 = 0; + }else if( sqliteCompare(p->zStack[nos], p->zStack[tos])>0 ){ + sqliteFree(p->zStack[nos]); + p->zStack[nos] = p->zStack[tos]; + p->iStack[nos] = p->iStack[tos]; + p->zStack[tos] = 0; } } p->tos--; diff --git a/src/where.c b/src/where.c index 217bfd342b..188d172ba1 100644 --- a/src/where.c +++ b/src/where.c @@ -25,7 +25,7 @@ ** the WHERE clause of SQL statements. Also found here are subroutines ** to generate VDBE code to evaluate expressions. ** -** $Id: where.c,v 1.4 2000/05/31 02:27:50 drh Exp $ +** $Id: where.c,v 1.5 2000/05/31 15:34:54 drh Exp $ */ #include "sqliteInt.h" @@ -353,240 +353,3 @@ void sqliteWhereEnd(WhereInfo *pWInfo){ sqliteFree(pWInfo); return; } - -/* -** Generate code into the current Vdbe to evaluate the given -** expression and leave the result on the stack. -*/ -void sqliteExprCode(Parse *pParse, Expr *pExpr){ - Vdbe *v = pParse->pVdbe; - int op; - switch( pExpr->op ){ - case TK_PLUS: op = OP_Add; break; - case TK_MINUS: op = OP_Subtract; break; - case TK_STAR: op = OP_Multiply; break; - case TK_SLASH: op = OP_Divide; break; - case TK_AND: op = OP_And; break; - case TK_OR: op = OP_Or; break; - case TK_LT: op = OP_Lt; break; - case TK_LE: op = OP_Le; break; - case TK_GT: op = OP_Gt; break; - case TK_GE: op = OP_Ge; break; - case TK_NE: op = OP_Ne; break; - case TK_EQ: op = OP_Eq; break; - case TK_LIKE: op = OP_Like; break; - case TK_GLOB: op = OP_Glob; break; - case TK_ISNULL: op = OP_IsNull; break; - case TK_NOTNULL: op = OP_NotNull; break; - case TK_NOT: op = OP_Not; break; - case TK_UMINUS: op = OP_Negative; break; - default: break; - } - switch( pExpr->op ){ - case TK_FIELD: { - sqliteVdbeAddOp(v, OP_Field, pExpr->iTable, pExpr->iField, 0, 0); - break; - } - case TK_INTEGER: { - int i = atoi(pExpr->token.z); - sqliteVdbeAddOp(v, OP_Integer, i, 0, 0, 0); - break; - } - case TK_FLOAT: { - int addr = sqliteVdbeAddOp(v, OP_String, 0, 0, 0, 0); - sqliteVdbeChangeP3(v, addr, pExpr->token.z, pExpr->token.n); - break; - } - case TK_STRING: { - int addr = sqliteVdbeAddOp(v, OP_String, 0, 0, 0, 0); - sqliteVdbeChangeP3(v, addr, pExpr->token.z, pExpr->token.n); - sqliteVdbeDequoteP3(v, addr); - break; - } - case TK_NULL: { - sqliteVdbeAddOp(v, OP_String, 0, 0, "", 0); - break; - } - case TK_AND: - case TK_OR: - case TK_PLUS: - case TK_STAR: - case TK_MINUS: - case TK_SLASH: { - sqliteExprCode(pParse, pExpr->pLeft); - sqliteExprCode(pParse, pExpr->pRight); - sqliteVdbeAddOp(v, op, 0, 0, 0, 0); - break; - } - case TK_LT: - case TK_LE: - case TK_GT: - case TK_GE: - case TK_NE: - case TK_EQ: - case TK_LIKE: - case TK_GLOB: { - int dest; - sqliteVdbeAddOp(v, OP_Integer, 1, 0, 0, 0); - sqliteExprCode(pParse, pExpr->pLeft); - sqliteExprCode(pParse, pExpr->pRight); - dest = sqliteVdbeCurrentAddr(v) + 2; - sqliteVdbeAddOp(v, op, 0, dest, 0, 0); - sqliteVdbeAddOp(v, OP_AddImm, -1, 0, 0, 0); - break; - } - case TK_NOT: - case TK_UMINUS: { - sqliteExprCode(pParse, pExpr->pLeft); - sqliteVdbeAddOp(v, op, 0, 0, 0, 0); - break; - } - case TK_ISNULL: - case TK_NOTNULL: { - int dest; - sqliteVdbeAddOp(v, OP_Integer, 0, 0, 0, 0); - sqliteExprCode(pParse, pExpr->pLeft); - dest = sqliteVdbeCurrentAddr(v) + 2; - sqliteVdbeAddOp(v, op, 0, dest, 0, 0); - sqliteVdbeAddOp(v, OP_AddImm, 1, 0, 0, 0); - break; - } - } - return; -} - -/* -** Generate code for a boolean expression such that a jump is made -** to the label "dest" if the expression is true but execution -** continues straight thru if the expression is false. -*/ -void sqliteExprIfTrue(Parse *pParse, Expr *pExpr, int dest){ - Vdbe *v = pParse->pVdbe; - int op = 0; - switch( pExpr->op ){ - case TK_LT: op = OP_Lt; break; - case TK_LE: op = OP_Le; break; - case TK_GT: op = OP_Gt; break; - case TK_GE: op = OP_Ge; break; - case TK_NE: op = OP_Ne; break; - case TK_EQ: op = OP_Eq; break; - case TK_LIKE: op = OP_Like; break; - case TK_GLOB: op = OP_Glob; break; - case TK_ISNULL: op = OP_IsNull; break; - case TK_NOTNULL: op = OP_NotNull; break; - default: break; - } - switch( pExpr->op ){ - case TK_AND: { - int d2 = sqliteVdbeMakeLabel(v); - sqliteExprIfFalse(pParse, pExpr->pLeft, d2); - sqliteExprIfTrue(pParse, pExpr->pRight, dest); - sqliteVdbeResolveLabel(v, d2); - break; - } - case TK_OR: { - sqliteExprIfTrue(pParse, pExpr->pLeft, dest); - sqliteExprIfTrue(pParse, pExpr->pRight, dest); - break; - } - case TK_NOT: { - sqliteExprIfFalse(pParse, pExpr->pLeft, dest); - break; - } - case TK_LT: - case TK_LE: - case TK_GT: - case TK_GE: - case TK_NE: - case TK_EQ: - case TK_LIKE: - case TK_GLOB: { - sqliteExprCode(pParse, pExpr->pLeft); - sqliteExprCode(pParse, pExpr->pRight); - sqliteVdbeAddOp(v, op, 0, dest, 0, 0); - break; - } - case TK_ISNULL: - case TK_NOTNULL: { - sqliteExprCode(pParse, pExpr->pLeft); - sqliteVdbeAddOp(v, op, 0, dest, 0, 0); - break; - } - default: { - sqliteExprCode(pParse, pExpr); - sqliteVdbeAddOp(v, OP_If, 0, dest, 0, 0); - break; - } - } -} - -/* -** Generate code for boolean expression such that a jump is made -** to the label "dest" if the expression is false but execution -** continues straight thru if the expression is true. -*/ -void sqliteExprIfFalse(Parse *pParse, Expr *pExpr, int dest){ - Vdbe *v = pParse->pVdbe; - int op = 0; - switch( pExpr->op ){ - case TK_LT: op = OP_Ge; break; - case TK_LE: op = OP_Gt; break; - case TK_GT: op = OP_Le; break; - case TK_GE: op = OP_Lt; break; - case TK_NE: op = OP_Eq; break; - case TK_EQ: op = OP_Ne; break; - case TK_LIKE: op = OP_Like; break; - case TK_GLOB: op = OP_Glob; break; - case TK_ISNULL: op = OP_NotNull; break; - case TK_NOTNULL: op = OP_IsNull; break; - default: break; - } - switch( pExpr->op ){ - case TK_AND: { - sqliteExprIfFalse(pParse, pExpr->pLeft, dest); - sqliteExprIfFalse(pParse, pExpr->pRight, dest); - break; - } - case TK_OR: { - int d2 = sqliteVdbeMakeLabel(v); - sqliteExprIfTrue(pParse, pExpr->pLeft, d2); - sqliteExprIfFalse(pParse, pExpr->pRight, dest); - sqliteVdbeResolveLabel(v, d2); - break; - } - case TK_NOT: { - sqliteExprIfTrue(pParse, pExpr->pLeft, dest); - break; - } - case TK_LT: - case TK_LE: - case TK_GT: - case TK_GE: - case TK_NE: - case TK_EQ: { - sqliteExprCode(pParse, pExpr->pLeft); - sqliteExprCode(pParse, pExpr->pRight); - sqliteVdbeAddOp(v, op, 0, dest, 0, 0); - break; - } - case TK_LIKE: - case TK_GLOB: { - sqliteExprCode(pParse, pExpr->pLeft); - sqliteExprCode(pParse, pExpr->pRight); - sqliteVdbeAddOp(v, op, 1, dest, 0, 0); - break; - } - case TK_ISNULL: - case TK_NOTNULL: { - sqliteExprCode(pParse, pExpr->pLeft); - sqliteVdbeAddOp(v, op, 0, dest, 0, 0); - break; - } - default: { - sqliteExprCode(pParse, pExpr); - sqliteVdbeAddOp(v, OP_Not, 0, 0, 0, 0); - sqliteVdbeAddOp(v, OP_If, 0, dest, 0, 0); - break; - } - } -} diff --git a/www/changes.tcl b/www/changes.tcl index fa52187e88..dfc8c63e6d 100644 --- a/www/changes.tcl +++ b/www/changes.tcl @@ -17,6 +17,11 @@ proc chng {date desc} { puts "

    $desc

" } +chan {2000 May 31} { +
  • Added support for aggregate functions (Ex: COUNT(*), MIN(...)) +to the SELECT statement.
  • +} + chng {2000 May 30} {
  • Added the LIKE operator.
  • Added a GLOB operator: similar to LIKE diff --git a/www/index.tcl b/www/index.tcl index 24a87a44c8..93bf168cf8 100644 --- a/www/index.tcl +++ b/www/index.tcl @@ -1,7 +1,7 @@ # # Run this TCL script to generate HTML for the index.html file. # -set rcsid {$Id: index.tcl,v 1.5 2000/05/31 02:27:50 drh Exp $} +set rcsid {$Id: index.tcl,v 1.6 2000/05/31 15:34:54 drh Exp $} puts { SQLite: An SQL Frontend For GDBM @@ -50,31 +50,13 @@ see sqlite.html.

    A history of changes to SQLite is found here.

    -

    SQLite does not try to implement every feature of SQL. But it -does strive to implement to most commonly used features. SQLite -currently understands the following SQL commands:

    - -

    -

      -
    • CREATE TABLE
    • -
    • CREATE INDEX
    • -
    • DROP TABLE
    • -
    • DROP INDEX
    • -
    • INSERT INTO
    • -
    • UPDATE
    • -
    • SELECT
    • -
    • DELETE FROM
    • -
    -

    - -

    A few of the many SQL features that SQLite does not (currently) +

    SQLite does not try to implement every feature of SQL. +A few of the many SQL features that SQLite does not (currently) implement are as follows:

      -
    • ALTER TABLE
    • The GROUP BY or HAVING clauses of a SELECT
    • -
    • The COUNT(), MAX(), MIN(), and AVG() functions
    • Constraints
    • Nested queries
    • Transactions or rollback