From 5cf590c128509b27de24da8f66b717f0c8d8afee Mon Sep 17 00:00:00 2001 From: drh Date: Thu, 24 Apr 2003 01:45:04 +0000 Subject: [PATCH] Fix some issues with INSTEAD OF triggers. (CVS 930) FossilOrigin-Name: 206b17397b1d2b55179c935927ff1d8215728c32 --- manifest | 40 ++++----- manifest.uuid | 2 +- src/auth.c | 17 ++-- src/btree_rb.c | 10 +-- src/copy.c | 6 +- src/delete.c | 114 +++++++++++++++---------- src/insert.c | 56 ++++++------- src/os.h | 5 ++ src/select.c | 21 +++-- src/shell.c | 4 +- src/sqlite.h.in | 7 +- src/sqliteInt.h | 5 +- src/trigger.c | 155 +++------------------------------- src/update.c | 205 +++++++++++++++++++++++++-------------------- src/where.c | 6 +- test/trigger2.test | 89 ++++++++++++++++++++ test/view.test | 8 +- 17 files changed, 381 insertions(+), 369 deletions(-) diff --git a/manifest b/manifest index 37c39bc388..46e2059505 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Remove\sthe\sbegin_hook\sand\scommit_hook\sAPIs.\s\sThey\swere\sa\sbad\sidea.\s\sAdd\sa\n"trace"\smethod\sto\sthe\sTCL\sinterface.\s(CVS\s929) -D 2003-04-23T12:25:24 +C Fix\ssome\sissues\swith\sINSTEAD\sOF\striggers.\s(CVS\s930) +D 2003-04-24T01:45:04 F Makefile.in 004acec253ecdde985c8ecd5b7c9accdb210378f F Makefile.linux-gcc b86a99c493a5bfb402d1d9178dcdc4bd4b32f906 F README f1de682fbbd94899d50aca13d387d1b3fd3be2dd @@ -20,34 +20,34 @@ F spec.template 238f7db425a78dc1bb7682e56e3834c7270a3f5e F sqlite.1 83f4a9d37bdf2b7ef079a82d54eaf2e3509ee6ea F sqlite.pc.in 30552343140c53304c2a658c080fbe810cd09ca2 F src/attach.c 7ebc7487de43e357a64226f8abef81f2669f2183 -F src/auth.c 7b0a72a649989461d36eced6ff1214f32af436c5 +F src/auth.c a4afd27964fb9f661147115790c8ae2ee230ebcc F src/btree.c b9487cceb9ea78af9cbae9def34114902f511736 F src/btree.h 529c98cb0715c62214544fbbe50b946f99a85540 -F src/btree_rb.c 97375d44bc2cf93b6312acd0f3276177c20e77bb +F src/btree_rb.c 85cbf6720db54b844747ce4dba4c3fcc8fd6f0a8 F src/build.c d5a26baeffa5bc49b4b7009a7723c6ab7e1b02d9 -F src/copy.c 6bafc19598daef79d80d16214260611d758a53a1 -F src/delete.c c5c26039cfdf1eadabff698eb329e3880189795e +F src/copy.c 44b13fd4d2444fb53bff8a5ecee1c5f6f86a8263 +F src/delete.c 23d33fd8967c6cc492943bbecea93be6491edc6a F src/encode.c faf03741efe921755ec371cf4a6984536de00042 F src/expr.c 46e2bb93abd6c70e67c8cdc5d92fdcd0b95498f3 F src/func.c 882c3ed5a02be18cd904715c7ec62947a34a3605 F src/hash.c 4fc39feb7b7711f6495ee9f2159559bedb043e1f F src/hash.h cd0433998bc1a3759d244e1637fe5a3c13b53bf8 -F src/insert.c 350167db53b779a8d402d00ec5153410a8003931 +F src/insert.c 19882be1edc4b1629b8f3097e2615164f2c9cecb F src/main.c 5e4d4d081d82840a743c57269ca3c32640cefc06 F src/md5.c fe4f9c9c6f71dfc26af8da63e4d04489b1430565 F src/os.c 7274951ed6894f383cb889342267ded07caf339b -F src/os.h aa52f0c9da321ff6134d19f2ca959e18e33615d0 +F src/os.h 9e5bbddff123187295e3d00d49af06192cd1cd49 F src/pager.c df4c81350cbd80c1ab48341ae0768ba78d99ad49 F src/pager.h e3702f7d384921f6cd5ce0b3ed589185433e9f6c F src/parse.y 15ae47e7dd84304c1c6ae9205558405127977541 F src/pragma.c 3b1e8da84304d5efa1db5802c67261335b663327 F src/printf.c fc5fdef6e92ad205005263661fe9716f55a49f3e F src/random.c 19e8e00fe0df32a742f115773f57651be327cabe -F src/select.c 92a66f0122f321688569e108feceaf74f5f4e63a -F src/shell.c a0b7043713713ff45f666ce6b3c03a64109a8bb5 +F src/select.c dfc13cb62ba658c4463179713c40ee25a062b2ba +F src/shell.c e0b3da1f44a2cc72daf41a4559b1c5f0545944a5 F src/shell.tcl 27ecbd63dd88396ad16d81ab44f73e6c0ea9d20e -F src/sqlite.h.in b4799af223dfc82f47d27c34b14f43b27509a49a -F src/sqliteInt.h cd2952587a8d3bff5d6cc7a0cfaf0e89c3bef0ad +F src/sqlite.h.in eec06462cba262c0ee03f38462a18a4bc66dda4e +F src/sqliteInt.h 5d15d1dea3f0c497a78c6a123eec5b4b92811c1c F src/table.c eed2098c9b577aa17f8abe89313a9c4413f57d63 F src/tclsqlite.c 9e25f98f1765afa0716144ef57abda75c88f688d F src/test1.c 4484806861a3099670188a09e12f858ec65aa56c @@ -55,13 +55,13 @@ F src/test2.c 5014337d8576b731cce5b5a14bec4f0daf432700 F src/test3.c 30985ebdfaf3ee1462a9b0652d3efbdc8d9798f5 F src/threadtest.c d641a5219e718e18a1a80a50eb9bb549f451f42e F src/tokenize.c 067d1a477a94af7712ca74e09aaa6bd0f7299527 -F src/trigger.c 21ad1677bb0f0625348a01e92d1e0c6d794185a1 -F src/update.c 3301448786205a7ec2d035c7cb7bd8ae5128c2b0 +F src/trigger.c e763f4015c96e06b694184ead5754985c1dfdae0 +F src/update.c b7fa7c427b74aee6db56ecfa09e5e151e6f9fa6a F src/util.c 87635cfdfffa056a8d3147719357aa442374f78c F src/vacuum.c e24781e38db36d1c9f578b6b3613bf0989ebd63c F src/vdbe.c f0868ac926d98395d28c2a29119364ff11b77852 F src/vdbe.h 985c24f312d10f9ef8f9a8b8ea62fcdf68e82f21 -F src/where.c c0709e5cf402f30026b597dce9dc3e74f1d07f8e +F src/where.c f632cd30f013163484a4d60c249d36fe31f5be12 F test/all.test 569a92a8ee88f5300c057cc4a8f50fbbc69a3242 F test/attach.test b311c83e370e6b22b79a8279317039440ce64862 F test/auth.test d25a76f21494b61483787caa7b28c713bc7c7c7f @@ -120,14 +120,14 @@ F test/temptable.test 6feff1960c707e924d5462356c5303943dac4a8e F test/tester.tcl d7a5835edaf118539241145d8188f0822b673488 F test/trans.test 75e7a171b5d2d94ee56766459113e2ad0e5f809d F test/trigger1.test 61ef41666f066ac417730dc26056053a7c36cd11 -F test/trigger2.test ab4c743bb96cee96ab5a17c5edfd57a9134329d6 +F test/trigger2.test adf6a9cfd735bd4be4f7be19da629b0968703744 F test/trigger3.test 870afef7997a5b86bf3ea893ce0c2e85d6356c72 F test/trigger4.test 9a5c1406344d743020c2753ae8d6dfe6eb75f818 F test/unique.test 22a46df72a3e0a3fd1a2d39e96fb59f18448dd5f F test/update.test 198360dfa14e65354dbcc66d5b98d8070780e42b F test/vacuum.test 059871b312eb910bbe49dafde1d01490cc2c6bbe F test/version.test 605fd0d7e7d571370c32b12dbf395b58953de246 -F test/view.test c64fa39ea57f3c2066c854290f032ad13b23b83d +F test/view.test d356f445d481c04ffa6036a4c61cb8ba70289f69 F test/where.test d719129a052280fe245a2ddcbd09bcc0b8c17ce4 F tool/diffdb.c 7524b1b5df217c20cd0431f6789851a4e0cb191b F tool/lemon.c 14fedcde9cf70aa6040b89de164cf8f56f92a4b9 @@ -165,7 +165,7 @@ F www/speed.tcl cb4c10a722614aea76d2c51f32ee43400d5951be F www/sqlite.tcl ae3dcfb077e53833b59d4fcc94d8a12c50a44098 F www/tclsqlite.tcl 1db15abeb446aad0caf0b95b8b9579720e4ea331 F www/vdbe.tcl 2013852c27a02a091d39a766bc87cff329f21218 -P c675a5504138f34cae6def782b5d3add2c67d2bc -R e42e1c0805bb96ad9734d493fd4b89bd +P 6289b863590ecc5de3d1efaaa60aa6f3f64fefb3 +R 566c0486d0b4bc8e9732c5c29c3b5dcc U drh -Z 6cfe8cf5cfb533faee03d9b06ef89d27 +Z d31c46a8e175a3b83b9efc55c7de92d9 diff --git a/manifest.uuid b/manifest.uuid index 465ce41a0b..a442ea316d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -6289b863590ecc5de3d1efaaa60aa6f3f64fefb3 \ No newline at end of file +206b17397b1d2b55179c935927ff1d8215728c32 \ No newline at end of file diff --git a/src/auth.c b/src/auth.c index 47b6600a5e..4703ba0132 100644 --- a/src/auth.c +++ b/src/auth.c @@ -14,7 +14,7 @@ ** systems that do not need this facility may omit it by recompiling ** the library with -DSQLITE_OMIT_AUTHORIZATION=1 ** -** $Id: auth.c,v 1.6 2003/04/22 20:30:38 drh Exp $ +** $Id: auth.c,v 1.7 2003/04/24 01:45:04 drh Exp $ */ #include "sqliteInt.h" @@ -95,11 +95,7 @@ void sqliteAuthRead( const char *zCol; /* Name of the column of the table */ int iSrc; /* Index in pTabList->a[] of table being read */ const char *zDBase; /* Name of database being accessed */ - const char *zTrig; /* Name of the trigger doing the accessing */ - TriggerStack *pStack; /* The stack of current triggers */ - pStack = pParse->trigStack; - zTrig = pStack ? pStack->pTrigger->name : 0; if( db->xAuth==0 ) return; assert( pExpr->op==TK_COLUMN ); iSrc = pExpr->iTable - base; @@ -109,6 +105,8 @@ void sqliteAuthRead( /* This must be an attempt to read the NEW or OLD pseudo-tables ** of a trigger. */ + TriggerStack *pStack; /* The stack of current triggers */ + pStack = pParse->trigStack; assert( pStack!=0 ); assert( pExpr->iTable==pStack->newIdx || pExpr->iTable==pStack->oldIdx ); pTab = pStack->pTab; @@ -123,9 +121,10 @@ void sqliteAuthRead( }else{ zCol = "ROWID"; } - assert( pExpr->iDb>=0 && pExpr->iDbnDb ); + assert( pExpr->iDbnDb ); zDBase = db->aDb[pExpr->iDb].zName; - rc = db->xAuth(db->pAuthArg, SQLITE_READ, pTab->zName, zCol, zDBase, zTrig); + rc = db->xAuth(db->pAuthArg, SQLITE_READ, pTab->zName, zCol, zDBase, + pParse->zAuthContext); if( rc==SQLITE_IGNORE ){ pExpr->op = TK_NULL; }else if( rc==SQLITE_DENY ){ @@ -158,13 +157,11 @@ int sqliteAuthCheck( ){ sqlite *db = pParse->db; int rc; - const char *zTrigName; if( db->xAuth==0 ){ return SQLITE_OK; } - zTrigName = pParse->trigStack ? pParse->trigStack->pTrigger->name : 0; - rc = db->xAuth(db->pAuthArg, code, zArg1, zArg2, zArg3, zTrigName); + rc = db->xAuth(db->pAuthArg, code, zArg1, zArg2, zArg3, pParse->zAuthContext); if( rc==SQLITE_DENY ){ sqliteSetString(&pParse->zErrMsg, "not authorized", 0); pParse->rc = SQLITE_AUTH; diff --git a/src/btree_rb.c b/src/btree_rb.c index 1a0db6d7e4..35c09e3aff 100644 --- a/src/btree_rb.c +++ b/src/btree_rb.c @@ -9,7 +9,7 @@ ** May you share freely, never taking more than you give. ** ************************************************************************* -** $Id: btree_rb.c,v 1.6 2003/04/20 17:29:24 drh Exp $ +** $Id: btree_rb.c,v 1.7 2003/04/24 01:45:04 drh Exp $ ** ** This file implements an in-core database using Red-Black balanced ** binary trees. @@ -1082,10 +1082,10 @@ static int memBtreeKey(BtCursor* pCur, int offset, int amt, char *zBuf) { if( !pCur->pNode ) return 0; if( !pCur->pNode->pKey || ((amt + offset) <= pCur->pNode->nKey) ){ - memcpy(zBuf, pCur->pNode->pKey+offset, amt); + memcpy(zBuf, ((char*)pCur->pNode->pKey)+offset, amt); return amt; }else{ - memcpy(zBuf, pCur->pNode->pKey+offset ,pCur->pNode->nKey-offset); + memcpy(zBuf, ((char*)pCur->pNode->pKey)+offset, pCur->pNode->nKey-offset); return pCur->pNode->nKey-offset; } assert(0); @@ -1105,10 +1105,10 @@ static int memBtreeData(BtCursor *pCur, int offset, int amt, char *zBuf) { if( !pCur->pNode ) return 0; if( (amt + offset) <= pCur->pNode->nData ){ - memcpy(zBuf, pCur->pNode->pData+offset, amt); + memcpy(zBuf, ((char*)pCur->pNode->pData)+offset, amt); return amt; }else{ - memcpy(zBuf, pCur->pNode->pData+offset ,pCur->pNode->nData-offset); + memcpy(zBuf, ((char*)pCur->pNode->pData)+offset ,pCur->pNode->nData-offset); return pCur->pNode->nData-offset; } assert(0); diff --git a/src/copy.c b/src/copy.c index 69711dca6d..44fd471421 100644 --- a/src/copy.c +++ b/src/copy.c @@ -11,7 +11,7 @@ ************************************************************************* ** This file contains code used to implement the COPY command. ** -** $Id: copy.c,v 1.3 2003/04/22 20:30:39 drh Exp $ +** $Id: copy.c,v 1.4 2003/04/24 01:45:04 drh Exp $ */ #include "sqliteInt.h" @@ -46,10 +46,10 @@ void sqliteCopy( if( sqlite_malloc_failed ) goto copy_cleanup; assert( pTableName->nSrc==1 ); pTab = sqliteSrcListLookup(pParse, pTableName); - if( pTab==0 || sqliteIsReadOnly(pParse, pTab) ) goto copy_cleanup; + if( pTab==0 || sqliteIsReadOnly(pParse, pTab, 0) ) goto copy_cleanup; zFile = sqliteStrNDup(pFilename->z, pFilename->n); sqliteDequote(zFile); - assert( pTab->iDb>=0 && pTab->iDbnDb ); + assert( pTab->iDbnDb ); zDb = db->aDb[pTab->iDb].zName; if( sqliteAuthCheck(pParse, SQLITE_INSERT, pTab->zName, 0, zDb) || sqliteAuthCheck(pParse, SQLITE_COPY, pTab->zName, zFile, zDb) ){ diff --git a/src/delete.c b/src/delete.c index 8899e717cf..7d0003578e 100644 --- a/src/delete.c +++ b/src/delete.c @@ -12,7 +12,7 @@ ** This file contains C code routines that are called by the parser ** to handle DELETE FROM statements. ** -** $Id: delete.c,v 1.53 2003/04/22 20:30:39 drh Exp $ +** $Id: delete.c,v 1.54 2003/04/24 01:45:04 drh Exp $ */ #include "sqliteInt.h" @@ -38,11 +38,13 @@ Table *sqliteSrcListLookup(Parse *pParse, SrcList *pSrc){ ** writable, generate an error message and return 1. If it is ** writable return 0; */ -int sqliteIsReadOnly(Parse *pParse, Table *pTab){ - if( pTab->readOnly || pTab->pSelect ){ - sqliteErrorMsg(pParse, "%s %s may not be modified", - pTab->pSelect ? "view" : "table", - pTab->zName); +int sqliteIsReadOnly(Parse *pParse, Table *pTab, int viewOk){ + if( pTab->readOnly ){ + sqliteErrorMsg(pParse, "table %s may not be modified", pTab->zName); + return 1; + } + if( !viewOk && pTab->pSelect ){ + sqliteErrorMsg(pParse, "cannot modify %s because it is a view",pTab->zName); return 1; } return 0; @@ -65,6 +67,7 @@ void sqliteDeleteFrom( Index *pIdx; /* For looping over indices of the table */ int base; /* Index of the first available table cursor */ sqlite *db; /* Main database structure */ + int isView; /* True if attempting to delete from a view */ int row_triggers_exist = 0; /* True if any triggers exist */ int before_triggers; /* True if there are BEFORE triggers */ @@ -90,20 +93,22 @@ void sqliteDeleteFrom( after_triggers = sqliteTriggersExist(pParse, pTab->pTrigger, TK_DELETE, TK_AFTER, TK_ROW, 0); row_triggers_exist = before_triggers || after_triggers; - if( row_triggers_exist && pTab->pSelect ){ - /* Just fire VIEW triggers */ - sqliteSrcListDelete(pTabList); - sqliteViewTriggers(pParse, pTab, pWhere, OE_Replace, 0); - return; + isView = pTab->pSelect!=0; + if( sqliteIsReadOnly(pParse, pTab, before_triggers) ){ + goto delete_from_cleanup; } - if( sqliteIsReadOnly(pParse, pTab) ) goto delete_from_cleanup; - assert( pTab->pSelect==0 ); /* This table is not a view */ assert( pTab->iDbnDb ); zDb = db->aDb[pTab->iDb].zName; if( sqliteAuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, zDb) ){ goto delete_from_cleanup; } + /* If pTab is really a view, make sure it has been initialized. + */ + if( isView && sqliteViewGetColumnNames(pParse, pTab) ){ + goto delete_from_cleanup; + } + /* Allocate a cursor used to store the old.* data for a trigger. */ if( row_triggers_exist ){ @@ -131,6 +136,15 @@ void sqliteDeleteFrom( sqliteBeginWriteOperation(pParse, row_triggers_exist, !row_triggers_exist && pTab->iDb==1); + /* If we are trying to delete from a view, construct that view into + ** a temporary table. + */ + if( isView ){ + Select *pView = sqliteSelectDup(pTab->pSelect); + sqliteSelect(pParse, pView, SRT_TempTable, base, 0, 0, 0); + sqliteSelectDelete(pView); + } + /* Initialize the counter of the number of rows deleted, if ** we are counting rows. */ @@ -148,17 +162,21 @@ void sqliteDeleteFrom( ** entries in the table. */ int endOfLoop = sqliteVdbeMakeLabel(v); int addr; - sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0); - sqliteVdbeAddOp(v, OP_OpenRead, base, pTab->tnum); + if( !isView ){ + sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0); + sqliteVdbeAddOp(v, OP_OpenRead, base, pTab->tnum); + } sqliteVdbeAddOp(v, OP_Rewind, base, sqliteVdbeCurrentAddr(v)+2); addr = sqliteVdbeAddOp(v, OP_AddImm, 1, 0); sqliteVdbeAddOp(v, OP_Next, base, addr); sqliteVdbeResolveLabel(v, endOfLoop); sqliteVdbeAddOp(v, OP_Close, base, 0); } - sqliteVdbeAddOp(v, OP_Clear, pTab->tnum, pTab->iDb); - for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - sqliteVdbeAddOp(v, OP_Clear, pIdx->tnum, pIdx->iDb); + if( !isView ){ + sqliteVdbeAddOp(v, OP_Clear, pTab->tnum, pTab->iDb); + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + sqliteVdbeAddOp(v, OP_Clear, pIdx->tnum, pIdx->iDb); + } } } @@ -201,51 +219,59 @@ void sqliteDeleteFrom( if( row_triggers_exist ){ addr = sqliteVdbeAddOp(v, OP_ListRead, 0, end); sqliteVdbeAddOp(v, OP_Dup, 0, 0); - sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0); - sqliteVdbeAddOp(v, OP_OpenRead, base, pTab->tnum); + if( !isView ){ + sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0); + sqliteVdbeAddOp(v, OP_OpenRead, base, pTab->tnum); + } sqliteVdbeAddOp(v, OP_MoveTo, base, 0); sqliteVdbeAddOp(v, OP_Recno, base, 0); sqliteVdbeAddOp(v, OP_RowData, base, 0); sqliteVdbeAddOp(v, OP_PutIntKey, oldIdx, 0); - sqliteVdbeAddOp(v, OP_Close, base, 0); + if( !isView ){ + sqliteVdbeAddOp(v, OP_Close, base, 0); + } sqliteCodeRowTrigger(pParse, TK_DELETE, 0, TK_BEFORE, pTab, -1, oldIdx, (pParse->trigStack)?pParse->trigStack->orconf:OE_Default, addr); } - /* Open cursors for the table we are deleting from and all its - ** indices. If there are row triggers, this happens inside the - ** OP_ListRead loop because the cursor have to all be closed - ** before the trigger fires. If there are no row triggers, the - ** cursors are opened only once on the outside the loop. - */ - pParse->nTab = base + 1; - sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0); - sqliteVdbeAddOp(v, OP_OpenWrite, base, pTab->tnum); - for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ - sqliteVdbeAddOp(v, OP_Integer, pIdx->iDb, 0); - sqliteVdbeAddOp(v, OP_OpenWrite, pParse->nTab++, pIdx->tnum); - } + if( !isView ){ + /* Open cursors for the table we are deleting from and all its + ** indices. If there are row triggers, this happens inside the + ** OP_ListRead loop because the cursor have to all be closed + ** before the trigger fires. If there are no row triggers, the + ** cursors are opened only once on the outside the loop. + */ + pParse->nTab = base + 1; + sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0); + sqliteVdbeAddOp(v, OP_OpenWrite, base, pTab->tnum); + for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ + sqliteVdbeAddOp(v, OP_Integer, pIdx->iDb, 0); + sqliteVdbeAddOp(v, OP_OpenWrite, pParse->nTab++, pIdx->tnum); + } - /* This is the beginning of the delete loop when there are no - ** row triggers */ - if( !row_triggers_exist ){ - addr = sqliteVdbeAddOp(v, OP_ListRead, 0, end); - } + /* This is the beginning of the delete loop when there are no + ** row triggers */ + if( !row_triggers_exist ){ + addr = sqliteVdbeAddOp(v, OP_ListRead, 0, end); + } - /* Delete the row */ - sqliteGenerateRowDelete(db, v, pTab, base, pParse->trigStack==0); + /* Delete the row */ + sqliteGenerateRowDelete(db, v, pTab, base, pParse->trigStack==0); + } /* If there are row triggers, close all cursors then invoke ** the AFTER triggers */ if( row_triggers_exist ){ - for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ - sqliteVdbeAddOp(v, OP_Close, base + i, pIdx->tnum); + if( !isView ){ + for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ + sqliteVdbeAddOp(v, OP_Close, base + i, pIdx->tnum); + } + sqliteVdbeAddOp(v, OP_Close, base, 0); } - sqliteVdbeAddOp(v, OP_Close, base, 0); sqliteCodeRowTrigger(pParse, TK_DELETE, 0, TK_AFTER, pTab, -1, oldIdx, (pParse->trigStack)?pParse->trigStack->orconf:OE_Default, addr); diff --git a/src/insert.c b/src/insert.c index 940536f56f..e83224a2f8 100644 --- a/src/insert.c +++ b/src/insert.c @@ -12,7 +12,7 @@ ** This file contains C code routines that are called by the parser ** to handle INSERT statements in SQLite. ** -** $Id: insert.c,v 1.81 2003/04/22 20:30:39 drh Exp $ +** $Id: insert.c,v 1.82 2003/04/24 01:45:04 drh Exp $ */ #include "sqliteInt.h" @@ -109,6 +109,7 @@ void sqliteInsert( int iCleanup; /* Address of the cleanup code */ int iInsertBlock; /* Address of the subroutine used to insert data */ int iCntMem; /* Memory cell used for the row counter */ + int isView; /* True if attempting to insert into a view */ int row_triggers_exist = 0; /* True if there are FOR EACH ROW triggers */ int before_triggers; /* True if there are BEFORE triggers */ @@ -142,21 +143,16 @@ void sqliteInsert( after_triggers = sqliteTriggersExist(pParse, pTab->pTrigger, TK_INSERT, TK_AFTER, TK_ROW, 0); row_triggers_exist = before_triggers || after_triggers; - if( pTab->readOnly || (pTab->pSelect && !row_triggers_exist) ){ - sqliteErrorMsg(pParse, "%s %s may not be modified", - pTab->pSelect ? "view" : "table", - zTab); + isView = pTab->pSelect!=0; + if( sqliteIsReadOnly(pParse, pTab, before_triggers) ){ goto insert_cleanup; } - if( pTab==0 ) goto insert_cleanup; /* If pTab is really a view, make sure it has been initialized. */ - if( pTab->pSelect ){ - if( sqliteViewGetColumnNames(pParse, pTab) ){ - goto insert_cleanup; - } + if( isView && sqliteViewGetColumnNames(pParse, pTab) ){ + goto insert_cleanup; } /* Allocate a VDBE @@ -356,7 +352,7 @@ void sqliteInsert( sqliteVdbeResolveLabel(v, iInsertBlock); } - /* Run the BEFORE triggers, if there are any + /* Run the BEFORE and INSTEAD OF triggers, if there are any */ endOfLoop = sqliteVdbeMakeLabel(v); if( before_triggers ){ @@ -405,7 +401,7 @@ void sqliteInsert( sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0); sqliteVdbeAddOp(v, OP_PutIntKey, newIdx, 0); - /* Fire BEFORE triggers */ + /* Fire BEFORE or INSTEAD OF triggers */ if( sqliteCodeRowTrigger(pParse, TK_INSERT, 0, TK_BEFORE, pTab, newIdx, -1, onError, endOfLoop) ){ goto insert_cleanup; @@ -415,19 +411,17 @@ void sqliteInsert( /* If any triggers exists, the opening of tables and indices is deferred ** until now. */ - if( row_triggers_exist ){ - if( !pTab->pSelect ){ - base = pParse->nTab; - sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0); - sqliteVdbeAddOp(v, OP_OpenWrite, base, pTab->tnum); - sqliteVdbeChangeP3(v, -1, pTab->zName, P3_STATIC); - for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){ - sqliteVdbeAddOp(v, OP_Integer, pIdx->iDb, 0); - sqliteVdbeAddOp(v, OP_OpenWrite, idx+base, pIdx->tnum); - sqliteVdbeChangeP3(v, -1, pIdx->zName, P3_STATIC); - } - pParse->nTab += idx; + if( row_triggers_exist && !isView ){ + base = pParse->nTab; + sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0); + sqliteVdbeAddOp(v, OP_OpenWrite, base, pTab->tnum); + sqliteVdbeChangeP3(v, -1, pTab->zName, P3_STATIC); + for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){ + sqliteVdbeAddOp(v, OP_Integer, pIdx->iDb, 0); + sqliteVdbeAddOp(v, OP_OpenWrite, idx+base, pIdx->tnum); + sqliteVdbeChangeP3(v, -1, pIdx->zName, P3_STATIC); } + pParse->nTab += idx; } /* Push the record number for the new entry onto the stack. The @@ -435,7 +429,7 @@ void sqliteInsert( ** except when the table has an INTEGER PRIMARY KEY column, in which ** case the record number is the same as that column. */ - if( !pTab->pSelect ){ + if( !isView ){ if( keyColumn>=0 ){ if( useTempTable ){ sqliteVdbeAddOp(v, OP_Column, srcTab, keyColumn); @@ -492,17 +486,17 @@ void sqliteInsert( sqliteGenerateConstraintChecks(pParse, pTab, base, 0,0,0,onError,endOfLoop); sqliteCompleteInsertion(pParse, pTab, base, 0,0,0, after_triggers ? newIdx : -1); + } - /* Update the count of rows that are inserted - */ - if( (db->flags & SQLITE_CountRows)!=0 ){ - sqliteVdbeAddOp(v, OP_MemIncr, iCntMem, 0); - } + /* Update the count of rows that are inserted + */ + if( (db->flags & SQLITE_CountRows)!=0 ){ + sqliteVdbeAddOp(v, OP_MemIncr, iCntMem, 0); } if( row_triggers_exist ){ /* Close all tables opened */ - if( !pTab->pSelect ){ + if( !isView ){ sqliteVdbeAddOp(v, OP_Close, base, 0); for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){ sqliteVdbeAddOp(v, OP_Close, idx+base, 0); diff --git a/src/os.h b/src/os.h index f9784ef525..d7674267d7 100644 --- a/src/os.h +++ b/src/os.h @@ -17,6 +17,11 @@ #ifndef _SQLITE_OS_H_ #define _SQLITE_OS_H_ +/* +** Helpful hint: To get this to compile on HP/UX, add -D_INCLUDE_POSIX_SOURCE +** to the compiler command line. +*/ + /* ** These #defines should enable >2GB file support on Posix if the ** underlying operating system supports it. If the OS lacks diff --git a/src/select.c b/src/select.c index f311fa7654..52a10d8da9 100644 --- a/src/select.c +++ b/src/select.c @@ -12,7 +12,7 @@ ** This file contains C code routines that are called by the parser ** to handle SELECT statements in SQLite. ** -** $Id: select.c,v 1.133 2003/04/22 20:30:39 drh Exp $ +** $Id: select.c,v 1.134 2003/04/24 01:45:04 drh Exp $ */ #include "sqliteInt.h" @@ -904,6 +904,10 @@ static int fillInColumnList(Parse *pParse, Select *p){ if( pTab==0 ){ return 1; } + /* The isTransient flag indicates that the Table structure has been + ** dynamically allocated and may be freed at any time. In other words, + ** pTab is not pointing to a persistent table structure that defines + ** part of the schema. */ pTab->isTransient = 1; }else{ /* An ordinary table or view name in the FROM clause */ @@ -1048,6 +1052,9 @@ static int fillInColumnList(Parse *pParse, Select *p){ ** This routine is called on the Select structure that defines a ** VIEW in order to undo any bindings to tables. This is necessary ** because those tables might be DROPed by a subsequent SQL command. +** If the bindings are not removed, then the Select.pSrc->a[].pTab field +** will be left pointing to a deallocated Table structure after the +** DROP and a coredump will occur the next time the VIEW is used. */ void sqliteSelectUnbind(Select *p){ int i; @@ -1058,10 +1065,6 @@ void sqliteSelectUnbind(Select *p){ if( (pTab = pSrc->a[i].pTab)!=0 ){ if( pTab->isTransient ){ sqliteDeleteTable(0, pTab); -#if 0 - sqliteSelectDelete(pSrc->a[i].pSelect); - pSrc->a[i].pSelect = 0; -#endif } pSrc->a[i].pTab = 0; if( pSrc->a[i].pSelect ){ @@ -2113,9 +2116,17 @@ int sqliteSelect( /* Generate code for all sub-queries in the FROM clause */ for(i=0; inSrc; i++){ + const char *zSavedAuthContext; if( pTabList->a[i].pSelect==0 ) continue; + if( pTabList->a[i].zName!=0 ){ + zSavedAuthContext = pParse->zAuthContext; + pParse->zAuthContext = pTabList->a[i].zName; + } sqliteSelect(pParse, pTabList->a[i].pSelect, SRT_TempTable, base+i, p, i, &isAgg); + if( pTabList->a[i].zName!=0 ){ + pParse->zAuthContext = zSavedAuthContext; + } pTabList = p->pSrc; pWhere = p->pWhere; if( eDest==SRT_Callback ){ diff --git a/src/shell.c b/src/shell.c index 4873d35df4..e8efbe3b45 100644 --- a/src/shell.c +++ b/src/shell.c @@ -12,7 +12,7 @@ ** This file contains code to implement the "sqlite" command line ** utility for accessing SQLite databases. ** -** $Id: shell.c,v 1.70 2003/04/17 22:57:54 drh Exp $ +** $Id: shell.c,v 1.71 2003/04/24 01:45:04 drh Exp $ */ #include #include @@ -1183,7 +1183,7 @@ int main(int argc, char **argv){ } }else{ extern int isatty(); - if( isatty(fileno(stdout)) ){ + if( isatty(fileno(stdout)) && isatty(fileno(stdin)) ){ char *zHome; char *zHistory = 0; printf( diff --git a/src/sqlite.h.in b/src/sqlite.h.in index cae7814dd2..6ebbc3dd73 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -12,7 +12,7 @@ ** This header file defines the interface that the SQLite library ** presents to client programs. ** -** @(#) $Id: sqlite.h.in,v 1.46 2003/04/23 12:25:24 drh Exp $ +** @(#) $Id: sqlite.h.in,v 1.47 2003/04/24 01:45:04 drh Exp $ */ #ifndef _SQLITE_H_ #define _SQLITE_H_ @@ -524,8 +524,9 @@ int sqlite_set_authorizer( ** function will be parameters or NULL depending on which of the following ** codes is used as the second parameter. The 5th parameter is the name ** of the database ("main", "temp", etc.) if applicable. The 6th parameter -** is the name of the trigger that is responsible for the access attempt, -** or NULL if this access attempt is directly from input SQL code. +** is the name of the inner-most trigger or view that is responsible for +** the access attempt or NULL if this access attempt is directly from +** input SQL code. ** ** Arg-3 Arg-4 */ diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 09209c2c8b..7027b30a96 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -11,7 +11,7 @@ ************************************************************************* ** Internal interface definitions for SQLite. ** -** @(#) $Id: sqliteInt.h,v 1.179 2003/04/23 12:25:24 drh Exp $ +** @(#) $Id: sqliteInt.h,v 1.180 2003/04/24 01:45:04 drh Exp $ */ #include "config.h" #include "sqlite.h" @@ -829,6 +829,7 @@ struct Parse { int nSet; /* Number of sets used so far */ int nAgg; /* Number of aggregate expressions */ AggExpr *aAgg; /* An array of aggregate expressions */ + const char *zAuthContext; /* The 6th parameter to db->xAuth callbacks */ Trigger *pNewTrigger; /* Trigger under construct by a CREATE TRIGGER */ TriggerStack *trigStack; /* Trigger actions being coded */ }; @@ -1039,7 +1040,7 @@ Select *sqliteSelectNew(ExprList*,SrcList*,Expr*,ExprList*,Expr*,ExprList*, void sqliteSelectDelete(Select*); void sqliteSelectUnbind(Select*); Table *sqliteSrcListLookup(Parse*, SrcList*); -int sqliteIsReadOnly(Parse*, Table*); +int sqliteIsReadOnly(Parse*, Table*, int); void sqliteDeleteFrom(Parse*, SrcList*, Expr*); void sqliteUpdate(Parse*, SrcList*, ExprList*, Expr*, int); WhereInfo *sqliteWhereBegin(Parse*, int, SrcList*, Expr*, int, ExprList**); diff --git a/src/trigger.c b/src/trigger.c index a2fc91f518..0abe7ee0d6 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -41,7 +41,7 @@ void sqliteDeleteTriggerStep(TriggerStep *pTriggerStep){ void sqliteBeginTrigger( Parse *pParse, /* The parse context of the CREATE TRIGGER statement */ Token *pName, /* The name of the trigger */ - int tr_tm, /* One of TK_BEFORE, TK_AFTER , TK_INSTEAD */ + int tr_tm, /* One of TK_BEFORE, TK_AFTER, TK_INSTEAD */ int op, /* One of TK_INSERT, TK_UPDATE, TK_DELETE */ IdList *pColumns, /* column list if this is an UPDATE OF trigger */ SrcList *pTableName,/* The name of the table/view the trigger applies to */ @@ -110,6 +110,11 @@ void sqliteBeginTrigger( } #endif + /* INSTEAD OF triggers can only appear on views and BEGIN triggers + ** cannot appear on views. So we might as well translate every + ** INSTEAD OF trigger into a BEFORE trigger. It simplifies code + ** elsewhere. + */ if (tr_tm == TK_INSTEAD){ tr_tm = TK_BEFORE; } @@ -627,7 +632,7 @@ int sqliteCodeRowTrigger( TriggerStack * pTriggerStack; assert(op == TK_UPDATE || op == TK_INSERT || op == TK_DELETE); - assert(tr_tm == TK_BEFORE || tr_tm == TK_AFTER); + assert(tr_tm == TK_BEFORE || tr_tm == TK_AFTER ); assert(newIdx != -1 || oldIdx != -1); @@ -656,6 +661,7 @@ int sqliteCodeRowTrigger( int endTrigger; SrcList dummyTablist; Expr * whenExpr; + const char *zSavedAuthContext; dummyTablist.nSrc = 0; @@ -667,6 +673,8 @@ int sqliteCodeRowTrigger( pTriggerStack->pNext = pParse->trigStack; pTriggerStack->ignoreJump = ignoreJump; pParse->trigStack = pTriggerStack; + zSavedAuthContext = pParse->zAuthContext; + pParse->zAuthContext = pTrigger->name; /* code the WHEN clause */ endTrigger = sqliteVdbeMakeLabel(pParse->pVdbe); @@ -684,6 +692,7 @@ int sqliteCodeRowTrigger( /* Pop the entry off the trigger stack */ pParse->trigStack = pParse->trigStack->pNext; + pParse->zAuthContext = zSavedAuthContext; sqliteFree(pTriggerStack); sqliteVdbeResolveLabel(pParse->pVdbe, endTrigger); @@ -693,145 +702,3 @@ int sqliteCodeRowTrigger( return 0; } - -/* - * This function is called to code ON UPDATE and ON DELETE triggers on - * views. - * - * This function deletes the data pointed at by the pWhere and pChanges - * arguments before it completes. - */ -void sqliteViewTriggers( - Parse *pParse, - Table *pTab, /* The view to code triggers on */ - Expr *pWhere, /* The WHERE clause of the statement causing triggers*/ - int orconf, /* The ON CONFLICT policy specified as part of the - statement causing these triggers */ - ExprList *pChanges /* If this is an statement causing triggers to fire - is an UPDATE, then this list holds the columns - to update and the expressions to update them to. - See comments for sqliteUpdate(). */ -){ - int oldIdx = -1; - int newIdx = -1; - int *aXRef = 0; - Vdbe *v; - int endOfLoop; - int startOfLoop; - Select theSelect; - Token tblNameToken; - - assert(pTab->pSelect); - - tblNameToken.z = pTab->zName; - tblNameToken.n = strlen(pTab->zName); - - theSelect.isDistinct = 0; - theSelect.pEList = sqliteExprListAppend(0, sqliteExpr(TK_ALL, 0, 0, 0), 0); - theSelect.pSrc = sqliteSrcListAppend(0, &tblNameToken, 0); - theSelect.pWhere = pWhere; pWhere = 0; - theSelect.pGroupBy = 0; - theSelect.pHaving = 0; - theSelect.pOrderBy = 0; - theSelect.op = TK_SELECT; /* ?? */ - theSelect.pPrior = 0; - theSelect.nLimit = -1; - theSelect.nOffset = -1; - theSelect.zSelect = 0; - theSelect.base = 0; - - v = sqliteGetVdbe(pParse); - assert(v); - sqliteBeginWriteOperation(pParse, 1, 0); - - /* Allocate temp tables */ - oldIdx = pParse->nTab++; - sqliteVdbeAddOp(v, OP_OpenPseudo, oldIdx, 0); - if( pChanges ){ - newIdx = pParse->nTab++; - sqliteVdbeAddOp(v, OP_OpenPseudo, newIdx, 0); - } - - /* Snapshot the view */ - if( sqliteSelect(pParse, &theSelect, SRT_Table, oldIdx, 0, 0, 0) ){ - goto trigger_cleanup; - } - - /* loop thru the view snapshot, executing triggers for each row */ - endOfLoop = sqliteVdbeMakeLabel(v); - sqliteVdbeAddOp(v, OP_Rewind, oldIdx, endOfLoop); - - /* Loop thru the view snapshot, executing triggers for each row */ - startOfLoop = sqliteVdbeCurrentAddr(v); - - /* Build the updated row if required */ - if( pChanges ){ - int ii; - - aXRef = sqliteMalloc( sizeof(int) * pTab->nCol ); - if( aXRef==0 ) goto trigger_cleanup; - for(ii = 0; ii < pTab->nCol; ii++){ - aXRef[ii] = -1; - } - - for(ii=0; iinExpr; ii++){ - int jj; - if( sqliteExprResolveIds(pParse, oldIdx, theSelect.pSrc , 0, - pChanges->a[ii].pExpr) ){ - goto trigger_cleanup; - } - - if( sqliteExprCheck(pParse, pChanges->a[ii].pExpr, 0, 0) ) - goto trigger_cleanup; - - for(jj=0; jjnCol; jj++){ - if( sqliteStrICmp(pTab->aCol[jj].zName, pChanges->a[ii].zName)==0 ){ - aXRef[jj] = ii; - break; - } - } - if( jj>=pTab->nCol ){ - sqliteErrorMsg(pParse, "no such column: %s", pChanges->a[ii].zName); - goto trigger_cleanup; - } - } - - sqliteVdbeAddOp(v, OP_Integer, 13, 0); - - for(ii = 0; iinCol; ii++){ - if( aXRef[ii] < 0 ){ - sqliteVdbeAddOp(v, OP_Column, oldIdx, ii); - }else{ - sqliteExprCode(pParse, pChanges->a[aXRef[ii]].pExpr); - } - } - - sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0); - sqliteVdbeAddOp(v, OP_PutIntKey, newIdx, 0); - sqliteVdbeAddOp(v, OP_Rewind, newIdx, 0); - - sqliteCodeRowTrigger(pParse, TK_UPDATE, pChanges, TK_BEFORE, - pTab, newIdx, oldIdx, orconf, endOfLoop); - sqliteCodeRowTrigger(pParse, TK_UPDATE, pChanges, TK_AFTER, - pTab, newIdx, oldIdx, orconf, endOfLoop); - }else{ - sqliteCodeRowTrigger(pParse, TK_DELETE, 0, TK_BEFORE, pTab, -1, oldIdx, - orconf, endOfLoop); - sqliteCodeRowTrigger(pParse, TK_DELETE, 0, TK_AFTER, pTab, -1, oldIdx, - orconf, endOfLoop); - } - - sqliteVdbeAddOp(v, OP_Next, oldIdx, startOfLoop); - - sqliteVdbeResolveLabel(v, endOfLoop); - sqliteEndWriteOperation(pParse); - -trigger_cleanup: - sqliteFree(aXRef); - sqliteExprListDelete(pChanges); - sqliteExprDelete(pWhere); - sqliteExprListDelete(theSelect.pEList); - sqliteSrcListDelete(theSelect.pSrc); - sqliteExprDelete(theSelect.pWhere); - return; -} diff --git a/src/update.c b/src/update.c index d320033f67..c1961426cf 100644 --- a/src/update.c +++ b/src/update.c @@ -12,7 +12,7 @@ ** This file contains C code routines that are called by the parser ** to handle UPDATE statements. ** -** $Id: update.c,v 1.62 2003/04/22 20:30:40 drh Exp $ +** $Id: update.c,v 1.63 2003/04/24 01:45:05 drh Exp $ */ #include "sqliteInt.h" @@ -48,6 +48,7 @@ void sqliteUpdate( int chngRecno; /* True if the record number is being changed */ Expr *pRecnoExpr; /* Expression defining the new record number */ int openAll; /* True if all indices need to be opened */ + int isView; /* Trying to update a view */ int before_triggers; /* True if there are any BEFORE triggers */ int after_triggers; /* True if there are any AFTER triggers */ @@ -69,14 +70,13 @@ void sqliteUpdate( after_triggers = sqliteTriggersExist(pParse, pTab->pTrigger, TK_UPDATE, TK_AFTER, TK_ROW, pChanges); row_triggers_exist = before_triggers || after_triggers; - if( row_triggers_exist && pTab->pSelect ){ - /* Just fire VIEW triggers */ - sqliteSrcListDelete(pTabList); - sqliteViewTriggers(pParse, pTab, pWhere, onError, pChanges); - return; + isView = pTab->pSelect!=0; + if( sqliteIsReadOnly(pParse, pTab, before_triggers) ){ + goto update_cleanup; + } + if( isView && sqliteViewGetColumnNames(pParse, pTab) ){ + goto update_cleanup; } - if( sqliteIsReadOnly(pParse, pTab) ) goto update_cleanup; - assert( pTab->pSelect==0 ); /* This table is not a VIEW */ aXRef = sqliteMalloc( sizeof(int) * pTab->nCol ); if( aXRef==0 ) goto update_cleanup; for(i=0; inCol; i++) aXRef[i] = -1; @@ -191,6 +191,15 @@ void sqliteUpdate( if( v==0 ) goto update_cleanup; sqliteBeginWriteOperation(pParse, 1, !row_triggers_exist && pTab->iDb==1); + /* If we are trying to update a view, construct that view into + ** a temporary table. + */ + if( isView ){ + Select *pView = sqliteSelectDup(pTab->pSelect); + sqliteSelect(pParse, pView, SRT_TempTable, base, 0, 0, 0); + sqliteSelectDelete(pView); + } + /* Begin the database scan */ pWInfo = sqliteWhereBegin(pParse, base, pTabList, pWhere, 1, 0); @@ -226,8 +235,10 @@ void sqliteUpdate( ** being updated. */ sqliteVdbeAddOp(v, OP_Dup, 0, 0); - sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0); - sqliteVdbeAddOp(v, OP_OpenRead, base, pTab->tnum); + if( !isView ){ + sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0); + sqliteVdbeAddOp(v, OP_OpenRead, base, pTab->tnum); + } sqliteVdbeAddOp(v, OP_MoveTo, base, 0); /* Generate the OLD table @@ -257,9 +268,11 @@ void sqliteUpdate( } sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0); sqliteVdbeAddOp(v, OP_PutIntKey, newIdx, 0); - sqliteVdbeAddOp(v, OP_Close, base, 0); + if( !isView ){ + sqliteVdbeAddOp(v, OP_Close, base, 0); + } - /* Fire the BEFORE triggers + /* Fire the BEFORE and INSTEAD OF triggers */ if( sqliteCodeRowTrigger(pParse, TK_UPDATE, pChanges, TK_BEFORE, pTab, newIdx, oldIdx, onError, addr) ){ @@ -267,103 +280,109 @@ void sqliteUpdate( } } - /* Rewind the list of records that need to be updated and - ** open every index that needs updating. Note that if any - ** index could potentially invoke a REPLACE conflict resolution - ** action, then we need to open all indices because we might need - ** to be deleting some records. - */ - sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0); - sqliteVdbeAddOp(v, OP_OpenWrite, base, pTab->tnum); - if( onError==OE_Replace ){ - openAll = 1; - }else{ - openAll = 0; - for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - if( pIdx->onError==OE_Replace ){ - openAll = 1; - break; + if( !isView ){ + /* + ** Open every index that needs updating. Note that if any + ** index could potentially invoke a REPLACE conflict resolution + ** action, then we need to open all indices because we might need + ** to be deleting some records. + */ + sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0); + sqliteVdbeAddOp(v, OP_OpenWrite, base, pTab->tnum); + if( onError==OE_Replace ){ + openAll = 1; + }else{ + openAll = 0; + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( pIdx->onError==OE_Replace ){ + openAll = 1; + break; + } } } - } - for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ - if( openAll || aIdxUsed[i] ){ - sqliteVdbeAddOp(v, OP_Integer, pIdx->iDb, 0); - sqliteVdbeAddOp(v, OP_OpenWrite, base+i+1, pIdx->tnum); - assert( pParse->nTab>base+i+1 ); + for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ + if( openAll || aIdxUsed[i] ){ + sqliteVdbeAddOp(v, OP_Integer, pIdx->iDb, 0); + sqliteVdbeAddOp(v, OP_OpenWrite, base+i+1, pIdx->tnum); + assert( pParse->nTab>base+i+1 ); + } } - } - /* Loop over every record that needs updating. We have to load - ** the old data for each record to be updated because some columns - ** might not change and we will need to copy the old value. - ** Also, the old data is needed to delete the old index entires. - ** So make the cursor point at the old record. - */ - if( !row_triggers_exist ){ - sqliteVdbeAddOp(v, OP_ListRewind, 0, 0); - addr = sqliteVdbeAddOp(v, OP_ListRead, 0, 0); - sqliteVdbeAddOp(v, OP_Dup, 0, 0); - } - sqliteVdbeAddOp(v, OP_NotExists, base, addr); - - /* If the record number will change, push the record number as it - ** will be after the update. (The old record number is currently - ** on top of the stack.) - */ - if( chngRecno ){ - sqliteExprCode(pParse, pRecnoExpr); - sqliteVdbeAddOp(v, OP_MustBeInt, 0, 0); - } - - /* Compute new data for this record. - */ - for(i=0; inCol; i++){ - if( i==pTab->iPKey ){ - sqliteVdbeAddOp(v, OP_String, 0, 0); - continue; + /* Loop over every record that needs updating. We have to load + ** the old data for each record to be updated because some columns + ** might not change and we will need to copy the old value. + ** Also, the old data is needed to delete the old index entires. + ** So make the cursor point at the old record. + */ + if( !row_triggers_exist ){ + sqliteVdbeAddOp(v, OP_ListRewind, 0, 0); + addr = sqliteVdbeAddOp(v, OP_ListRead, 0, 0); + sqliteVdbeAddOp(v, OP_Dup, 0, 0); } - j = aXRef[i]; - if( j<0 ){ - sqliteVdbeAddOp(v, OP_Column, base, i); - }else{ - sqliteExprCode(pParse, pChanges->a[j].pExpr); + sqliteVdbeAddOp(v, OP_NotExists, base, addr); + + /* If the record number will change, push the record number as it + ** will be after the update. (The old record number is currently + ** on top of the stack.) + */ + if( chngRecno ){ + sqliteExprCode(pParse, pRecnoExpr); + sqliteVdbeAddOp(v, OP_MustBeInt, 0, 0); } + + /* Compute new data for this record. + */ + for(i=0; inCol; i++){ + if( i==pTab->iPKey ){ + sqliteVdbeAddOp(v, OP_String, 0, 0); + continue; + } + j = aXRef[i]; + if( j<0 ){ + sqliteVdbeAddOp(v, OP_Column, base, i); + }else{ + sqliteExprCode(pParse, pChanges->a[j].pExpr); + } + } + + /* Do constraint checks + */ + sqliteGenerateConstraintChecks(pParse, pTab, base, aIdxUsed, chngRecno, 1, + onError, addr); + + /* Delete the old indices for the current record. + */ + sqliteGenerateRowIndexDelete(db, v, pTab, base, aIdxUsed); + + /* If changing the record number, delete the old record. + */ + if( chngRecno ){ + sqliteVdbeAddOp(v, OP_Delete, base, 0); + } + + /* Create the new index entries and the new record. + */ + sqliteCompleteInsertion(pParse, pTab, base, aIdxUsed, chngRecno, 1, -1); } - /* Do constraint checks - */ - sqliteGenerateConstraintChecks(pParse, pTab, base, aIdxUsed, chngRecno, 1, - onError, addr); - - /* Delete the old indices for the current record. - */ - sqliteGenerateRowIndexDelete(db, v, pTab, base, aIdxUsed); - - /* If changing the record number, delete the old record. - */ - if( chngRecno ){ - sqliteVdbeAddOp(v, OP_Delete, base, 0); - } - - /* Create the new index entries and the new record. - */ - sqliteCompleteInsertion(pParse, pTab, base, aIdxUsed, chngRecno, 1, -1); - /* Increment the row counter */ if( db->flags & SQLITE_CountRows && !pParse->trigStack){ sqliteVdbeAddOp(v, OP_AddImm, 1, 0); } + /* If there are triggers, close all the cursors after each iteration + ** through the loop. The fire the after triggers. + */ if( row_triggers_exist ){ - for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ - if( openAll || aIdxUsed[i] ) - sqliteVdbeAddOp(v, OP_Close, base+i+1, 0); + if( !isView ){ + for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ + if( openAll || aIdxUsed[i] ) + sqliteVdbeAddOp(v, OP_Close, base+i+1, 0); + } + sqliteVdbeAddOp(v, OP_Close, base, 0); + pParse->nTab = base; } - sqliteVdbeAddOp(v, OP_Close, base, 0); - pParse->nTab = base; - if( sqliteCodeRowTrigger(pParse, TK_UPDATE, pChanges, TK_AFTER, pTab, newIdx, oldIdx, onError, addr) ){ goto update_cleanup; diff --git a/src/where.c b/src/where.c index 6e6fc289f8..82903047fe 100644 --- a/src/where.c +++ b/src/where.c @@ -12,7 +12,7 @@ ** This module contains C code that generates VDBE code used to process ** the WHERE clause of SQL statements. ** -** $Id: where.c,v 1.76 2003/04/20 17:29:24 drh Exp $ +** $Id: where.c,v 1.77 2003/04/24 01:45:05 drh Exp $ */ #include "sqliteInt.h" @@ -1144,7 +1144,9 @@ void sqliteWhereEnd(WhereInfo *pWInfo){ } sqliteVdbeResolveLabel(v, pWInfo->iBreak); for(i=0; inSrc; i++){ - if( pTabList->a[i].pTab->isTransient ) continue; + Table *pTab = pTabList->a[i].pTab; + assert( pTab!=0 ); + if( pTab->isTransient || pTab->pSelect ) continue; pLevel = &pWInfo->a[i]; sqliteVdbeAddOp(v, OP_Close, base+i, 0); if( pLevel->pIdx!=0 ){ diff --git a/test/trigger2.test b/test/trigger2.test index 1709bf9d1f..8cefb75674 100644 --- a/test/trigger2.test +++ b/test/trigger2.test @@ -587,6 +587,7 @@ do_test trigger2-7.1 { } } {} +#explain {delete from abcd where a=1;} do_test trigger2-7.2 { execsql { UPDATE abcd SET a = 100, b = 5*5 WHERE a = 1; @@ -601,4 +602,92 @@ do_test trigger2-7.2 { 5 0 0 0 0 10 20 30 40 \ 6 0 0 0 0 10 20 30 40 ] +do_test trigger2-7.3 { + execsql { + DELETE FROM tlog; + INSERT INTO abcd VALUES(10, 20, 30, 40); + UPDATE abcd SET a = 100, b = 5*5 WHERE a = 1; + DELETE FROM abcd WHERE a = 1; + SELECT * FROM tlog; + } +} [ list \ + 1 0 0 0 0 10 20 30 40 \ + 2 0 0 0 0 10 20 30 40 \ + 3 1 2 3 4 100 25 3 4 \ + 4 1 2 3 4 100 25 3 4 \ + 5 1 2 3 4 0 0 0 0 \ + 6 1 2 3 4 0 0 0 0 \ +] +do_test trigger2-7.4 { + execsql { + DELETE FROM tlog; + DELETE FROM abcd WHERE a = 1; + INSERT INTO abcd VALUES(10, 20, 30, 40); + UPDATE abcd SET a = 100, b = 5*5 WHERE a = 1; + SELECT * FROM tlog; + } +} [ list \ + 1 1 2 3 4 0 0 0 0 \ + 2 1 2 3 4 0 0 0 0 \ + 3 0 0 0 0 10 20 30 40 \ + 4 0 0 0 0 10 20 30 40 \ + 5 1 2 3 4 100 25 3 4 \ + 6 1 2 3 4 100 25 3 4 \ +] + +do_test trigger2-8.1 { + execsql { + CREATE TABLE t1(a,b,c); + INSERT INTO t1 VALUES(1,2,3); + CREATE VIEW v1 AS + SELECT a+b AS x, b+c AS y, a+c AS z FROM t1; + SELECT * FROM v1; + } +} {3 5 4} +do_test trigger2-8.2 { + execsql { + CREATE TABLE v1log(a,b,c,d,e,f); + CREATE TRIGGER r1 INSTEAD OF DELETE ON v1 BEGIN + INSERT INTO v1log VALUES(OLD.x,NULL,OLD.y,NULL,OLD.z,NULL); + END; + DELETE FROM v1 WHERE x=1; + SELECT * FROM v1log; + } +} {} +do_test trigger2-8.3 { + execsql { + DELETE FROM v1 WHERE x=3; + SELECT * FROM v1log; + } +} {3 {} 5 {} 4 {}} +do_test trigger2-8.4 { + execsql { + INSERT INTO t1 VALUES(4,5,6); + DELETE FROM v1log; + DELETE FROM v1 WHERE y=11; + SELECT * FROM v1log; + } +} {9 {} 11 {} 10 {}} +do_test trigger2-8.5 { + execsql { + CREATE TRIGGER r2 INSTEAD OF INSERT ON v1 BEGIN + INSERT INTO v1log VALUES(NULL,NEW.x,NULL,NEW.y,NULL,NEW.z); + END; + DELETE FROM v1log; + INSERT INTO v1 VALUES(1,2,3); + SELECT * FROM v1log; + } +} {{} 1 {} 2 {} 3} +do_test trigger2-8.6 { + execsql { + CREATE TRIGGER r3 INSTEAD OF UPDATE ON v1 BEGIN + INSERT INTO v1log VALUES(OLD.x,NEW.x,OLD.y,NEW.y,OLD.z,NEW.z); + END; + DELETE FROM v1log; + UPDATE v1 SET x=x+100, y=y+200, z=z+300; + SELECT * FROM v1log; + } +} {3 103 5 205 4 304 9 109 11 211 10 310} + + finish_test diff --git a/test/view.test b/test/view.test index 2d6952d2ea..3e088d169c 100644 --- a/test/view.test +++ b/test/view.test @@ -11,7 +11,7 @@ # This file implements regression tests for SQLite library. The # focus of this file is testing VIEW statements. # -# $Id: view.test,v 1.12 2002/12/03 02:22:53 drh Exp $ +# $Id: view.test,v 1.13 2003/04/24 01:45:05 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -98,17 +98,17 @@ do_test view-2.2 { catchsql { INSERT INTO v2 VALUES(1,2,3,4); } -} {1 {view v2 may not be modified}} +} {1 {cannot modify v2 because it is a view}} do_test view-2.3 { catchsql { UPDATE v2 SET a=10 WHERE a=5; } -} {1 {view v2 may not be modified}} +} {1 {cannot modify v2 because it is a view}} do_test view-2.4 { catchsql { DELETE FROM v2; } -} {1 {view v2 may not be modified}} +} {1 {cannot modify v2 because it is a view}} do_test view-2.5 { execsql { INSERT INTO t1 VALUES(11,12,13,14);