diff --git a/main.mk b/main.mk index 6d10475aa6..8d29d83e80 100644 --- a/main.mk +++ b/main.mk @@ -54,7 +54,7 @@ TCCX = $(TCC) $(OPTS) $(THREADSAFE) $(USLEEP) -I. -I$(TOP)/src # Object files for the SQLite library. # -LIBOBJ = attach.o auth.o btree.o build.o copy.o delete.o \ +LIBOBJ = attach.o auth.o btree.o btree_rb.o build.o copy.o delete.o \ expr.o func.o hash.o insert.o \ main.o opcodes.o os.o pager.o parse.o pragma.o printf.o random.o \ select.o table.o tokenize.o trigger.o update.o util.o \ @@ -67,6 +67,7 @@ SRC = \ $(TOP)/src/auth.c \ $(TOP)/src/btree.c \ $(TOP)/src/btree.h \ + $(TOP)/src/btree_rb.c \ $(TOP)/src/build.c \ $(TOP)/src/copy.c \ $(TOP)/src/delete.c \ @@ -165,6 +166,9 @@ lemon: $(TOP)/tool/lemon.c $(TOP)/tool/lempar.c btree.o: $(TOP)/src/btree.c $(HDR) $(TOP)/src/pager.h $(TCCX) -c $(TOP)/src/btree.c +btree_rb.o: $(TOP)/src/btree_rb.c $(HDR) + $(TCCX) -c $(TOP)/src/btree_rb.c + build.o: $(TOP)/src/build.c $(HDR) $(TCCX) -c $(TOP)/src/build.c diff --git a/manifest b/manifest index 57fbd3bd03..a7e02bb3d3 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Added\s\sbtree_rb.c\s(CVS\s907) -D 2003-04-15T17:22:30 +C Get\striggers\sworking\son\stables\swith\sINTEGER\sPRIMARY\sKEYs.\s\sTicket\s#291.\nThis\smay\salso\sfix\s#159.\s\sStill\sneed\sto\sadd\stests\sso\sboth\sbugs\sremain\sopen\nfor\sthe\stime\sbeing.\s(CVS\s908) +D 2003-04-15T19:22:23 F Makefile.in df3a4db41a7450468b5fe934d9dd8f723b631249 F Makefile.linux-gcc b86a99c493a5bfb402d1d9178dcdc4bd4b32f906 F README f1de682fbbd94899d50aca13d387d1b3fd3be2dd @@ -14,25 +14,25 @@ F doc/report1.txt a031aaf37b185e4fa540223cb516d3bccec7eeac F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 F libtool bbbea7d79c23323e4100103836028e4fad0d9242 F ltmain.sh abfb9387049fff6996afc6e325736597795baf11 -F main.mk 08739679174d858a0f6ced3117ce8dcfdafa540a +F main.mk 65302cd32c459607872cae81481ccadd6a998968 F publish.sh 86b5e8535830a2588f62ce1d5d1ef00e1dede23a F spec.template 238f7db425a78dc1bb7682e56e3834c7270a3f5e F sqlite.1 83f4a9d37bdf2b7ef079a82d54eaf2e3509ee6ea F sqlite.pc.in 30552343140c53304c2a658c080fbe810cd09ca2 -F src/attach.c 8c98e2c0ca434b94deca1b8694c72bd0303a9a87 +F src/attach.c 7ebc7487de43e357a64226f8abef81f2669f2183 F src/auth.c f37bfc9451b8c1fa52f34adff474560018892729 F src/btree.c 9949031b6087e9d1b43b359b84c68a491086984f F src/btree.h 5cb871546bd6fa58396a6f033e2b29b388241e1b -F src/btree_rb.c 85727e7ba6277a55385bf86ff5da6d1093dd8978 +F src/btree_rb.c c917cdcc401df1075b257a96e8a1e3e46a506805 F src/build.c daed1dacdb70e5d4def9df2e34a1cabeeb8467c9 -F src/copy.c ddd204d5dddac09d71a07f4ceded4c9926d5512b -F src/delete.c 58d698779a6b7f819718ecd45b310a9de8537088 +F src/copy.c 8699e571994934c78f70761a1458d7b9e9e75073 +F src/delete.c 6021fd293a78ebeb35e8177bd811d752fe090f89 F src/encode.c faf03741efe921755ec371cf4a6984536de00042 -F src/expr.c b8daee83f837b24a22d889200bdd74973ca2d8db +F src/expr.c 942f535c8906ef81df00bac62223868feb78424b F src/func.c 882c3ed5a02be18cd904715c7ec62947a34a3605 F src/hash.c 4fc39feb7b7711f6495ee9f2159559bedb043e1f F src/hash.h cd0433998bc1a3759d244e1637fe5a3c13b53bf8 -F src/insert.c e2f5e7feecb507d904a7da48874595f440b715aa +F src/insert.c 45d27e3e8447bff4025db2f0dc3bb4e318e602f4 F src/main.c daf5b7c256340fb9aa77df7254865218a47d5a63 F src/md5.c fe4f9c9c6f71dfc26af8da63e4d04489b1430565 F src/os.c c33ebb320921b8df6d09ea19fe846348df86a0c9 @@ -47,7 +47,7 @@ F src/select.c 14e2e2a512f4edfc75fb310ebcb502ff3ee87402 F src/shell.c 97f397c0c108176ccbc52ea5b8ec688f995eba7a F src/shell.tcl 27ecbd63dd88396ad16d81ab44f73e6c0ea9d20e F src/sqlite.h.in f49c2cdec7d24cb03e496a1ca519e16306495ee1 -F src/sqliteInt.h 048303eafaf5811a0977528756386873931cd914 +F src/sqliteInt.h c34ae7f78cb3c2f8e7136ff091155f97f7ec9327 F src/table.c eed2098c9b577aa17f8abe89313a9c4413f57d63 F src/tclsqlite.c 7a072c3c8ba9796edc25e5ffa62b68558134e192 F src/test1.c 7ad4e6308dde0bf5a0f0775ce20cb2ec37a328f8 @@ -55,11 +55,11 @@ F src/test2.c 5014337d8576b731cce5b5a14bec4f0daf432700 F src/test3.c 30985ebdfaf3ee1462a9b0652d3efbdc8d9798f5 F src/threadtest.c d641a5219e718e18a1a80a50eb9bb549f451f42e F src/tokenize.c 675b4718d17c69fe7609dc8e85e426ef002be811 -F src/trigger.c bd5a5b234b47f28f9f21a46243dcaf1c5b2383a3 -F src/update.c b368369f1fbe6d7f56a53e5ffad3b75dae9e3e1a +F src/trigger.c 4ca4499d367548385b8e9fc67eb360cd1ca95b8a +F src/update.c a60470d07cdd4ff3c11c5418f8055f2f41b3d751 F src/util.c 8953c612a036e30f24c1c1f5a1498176173daa37 F src/vacuum.c ac65e9578506a0cdf70ece2668e5b22f4895477c -F src/vdbe.c 0fa25d177b02523dbdcb48395053ed5cc21829ee +F src/vdbe.c 6738153dc8ebd68cf5d6ccf7e1e08d608f5420c6 F src/vdbe.h 985c24f312d10f9ef8f9a8b8ea62fcdf68e82f21 F src/where.c e5733f7d5e9cc4ed3590dc3401f779e7b7bb8127 F test/all.test 569a92a8ee88f5300c057cc4a8f50fbbc69a3242 @@ -102,7 +102,7 @@ F test/pragma.test d45d130f532bfe86ebd5ba74862d88b36ded8998 F test/printf.test a29b8afa24edb4411adfe473b12ac32c84098fce F test/quick.test c527bdb899b12a8cd8ceecce45f72922099f4095 F test/quote.test 08f23385c685d3dc7914ec760d492cacea7f6e3d -F test/rowid.test 82bd07c959eb56ce14feb85cac591750af714ddf +F test/rowid.test 532b3d8aa01d05222a0e0fd503b8da89fe3764be F test/select1.test 0d708cec567104653ec9aa49fecf3444a2e7d150 F test/select2.test aceea74fd895b9d007512f72499db589735bd8e4 F test/select3.test 445a1a3dde4e2fd32541b311f55da5e2f8079d76 @@ -162,7 +162,7 @@ F www/speed.tcl cb4c10a722614aea76d2c51f32ee43400d5951be F www/sqlite.tcl ae3dcfb077e53833b59d4fcc94d8a12c50a44098 F www/tclsqlite.tcl 1db15abeb446aad0caf0b95b8b9579720e4ea331 F www/vdbe.tcl 2013852c27a02a091d39a766bc87cff329f21218 -P 96a717661a3b7108fe0cacb588d81fd8e91eb640 -R cbe58bcf88d6fb97e9a813f970d2df5a -U paul -Z df689c3d6a38a6ae7133718deb76cd51 +P 93eb6c52aca8de15a88247ec986c36245527ec7b +R 97b4c68da27841d236c7d5fe42a0a220 +U drh +Z 2af580d6307d67cd3da5d48cc8031613 diff --git a/manifest.uuid b/manifest.uuid index e136ca02f0..9cd5dc56c6 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -93eb6c52aca8de15a88247ec986c36245527ec7b \ No newline at end of file +0b996959b8d8bc2c82eab9cccc190befd0056505 \ No newline at end of file diff --git a/src/attach.c b/src/attach.c index 3e79cacbd9..e1c8a3c4d6 100644 --- a/src/attach.c +++ b/src/attach.c @@ -11,7 +11,7 @@ ************************************************************************* ** This file contains code used to implement the ATTACH and DETACH commands. ** -** $Id: attach.c,v 1.2 2003/04/13 18:26:51 paul Exp $ +** $Id: attach.c,v 1.3 2003/04/15 19:22:23 drh Exp $ */ #include "sqliteInt.h" @@ -119,6 +119,7 @@ void sqliteDetach(Parse *pParse, Token *pDbname){ } sqliteBtreeClose(db->aDb[i].pBt); db->aDb[i].pBt = 0; + sqliteFree(db->aDb[i].zName); sqliteResetInternalSchema(db, i); db->nDb--; if( inDb ){ diff --git a/src/btree_rb.c b/src/btree_rb.c index f46b92d372..d0242142e8 100644 --- a/src/btree_rb.c +++ b/src/btree_rb.c @@ -9,20 +9,26 @@ ** May you share freely, never taking more than you give. ** ************************************************************************* -** $Id: btree_rb.c,v 1.1 2003/04/15 17:22:30 paul Exp $ +** $Id: btree_rb.c,v 1.2 2003/04/15 19:22:23 drh Exp $ ** ** This file implements an in-core database using Red-Black balanced ** binary trees. ** ** It was contributed to SQLite by anonymous on 2003-Feb-04 23:24:49 UTC. */ - #define SQLITE_NO_BTREE_DEFS - #include "btree.h" #include "sqliteInt.h" #include +/* +** Omit this whole file if the SQLITE_OMIT_INMEMORYDB macro is +** defined. This allows a lot of code to be omitted for installations +** that do not need it. +*/ +#ifndef SQLITE_OMIT_INMEMORYDB + + typedef struct BtRbTree BtRbTree; typedef struct BtRbNode BtRbNode; typedef struct BtRollbackOp BtRollbackOp; @@ -565,7 +571,6 @@ static void btreeLogRollbackOp(Btree* pBtree, BtRollbackOp *pRollbackOp) int sqliteRBtreeOpen(const char *zFilename, int mode, int nPg, Btree **ppBtree) { - int tnum; *ppBtree = (Btree *)sqliteMalloc(sizeof(Btree)); sqliteHashInit(&(*ppBtree)->tblHash, SQLITE_HASH_INT, 0); @@ -590,7 +595,6 @@ int sqliteRBtreeOpen(const char *zFilename, int mode, int nPg, Btree **ppBtree) */ static int sqliteBtreeCreateTable(Btree* tree, int* n) { - BtRbTree *pNewTbl; assert( tree->eTransState != TRANS_NONE ); *n = tree->next_idx++; @@ -690,7 +694,6 @@ static int sqliteBtreeCursor(Btree* tree, int iTable, int wrFlag, BtCursor **ppC static int sqliteBtreeInsert(BtCursor* pCur, const void *pKey, int nKey, const void *pDataInput, int nData) { - BtRbNode *pNode; /* The new node that is begin inserted */ void * pData; int match; @@ -1161,7 +1164,6 @@ static int sqliteBtreeClose(Btree* tree) { HashElem *p; for(p=sqliteHashFirst(&tree->tblHash); p; p=sqliteHashNext(p)){ - BtRbTree *pTree = sqliteHashData(p); tree->eTransState = TRANS_ROLLBACK; sqliteBtreeClearTable(tree, sqliteHashKeysize(p)); sqliteFree(sqliteHashData(p)); @@ -1223,9 +1225,9 @@ static void execute_rollback_list(Btree *pBtree, BtRollbackOp *pList) { BtRollbackOp *pTmp; BtCursor cur; - cur.pBtree = pBtree; - int res; + + cur.pBtree = pBtree; while( pList ){ switch( pList->eOp ){ case ROLLBACK_INSERT: @@ -1391,3 +1393,5 @@ static BtCursorOps sqliteBtreeCursorOps = { #endif }; + +#endif /* SQLITE_OMIT_INMEMORYDB */ diff --git a/src/copy.c b/src/copy.c index 90fe200455..076b0a4781 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.1 2003/04/06 21:08:24 drh Exp $ +** $Id: copy.c,v 1.2 2003/04/15 19:22:23 drh Exp $ */ #include "sqliteInt.h" @@ -94,7 +94,7 @@ void sqliteCopy( } } sqliteGenerateConstraintChecks(pParse, pTab, 0, 0, 0, 0, onError, addr); - sqliteCompleteInsertion(pParse, pTab, 0, 0, 0, 0); + sqliteCompleteInsertion(pParse, pTab, 0, 0, 0, 0, -1); if( (db->flags & SQLITE_CountRows)!=0 ){ sqliteVdbeAddOp(v, OP_AddImm, 1, 0); /* Increment row count */ } diff --git a/src/delete.c b/src/delete.c index fecb8cc12d..c2c6d5b490 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.50 2003/03/31 02:12:47 drh Exp $ +** $Id: delete.c,v 1.51 2003/04/15 19:22:23 drh Exp $ */ #include "sqliteInt.h" @@ -71,8 +71,10 @@ void sqliteDeleteFrom( int base; /* Index of the first available table cursor */ sqlite *db; /* Main database structure */ - int row_triggers_exist = 0; - int oldIdx = -1; + int row_triggers_exist = 0; /* True if any triggers exist */ + int before_triggers; /* True if there are BEFORE triggers */ + int after_triggers; /* True if there are AFTER triggers */ + int oldIdx = -1; /* Cursor for the OLD table of AFTER triggers */ if( pParse->nErr || sqlite_malloc_failed ){ pTabList = 0; @@ -89,11 +91,11 @@ void sqliteDeleteFrom( if( zTab != 0 ){ pTab = sqliteFindTable(pParse->db, zTab, zDb); if( pTab ){ - row_triggers_exist = - sqliteTriggersExist(pParse, pTab->pTrigger, - TK_DELETE, TK_BEFORE, TK_ROW, 0) || - sqliteTriggersExist(pParse, pTab->pTrigger, - TK_DELETE, TK_AFTER, TK_ROW, 0); + before_triggers = sqliteTriggersExist(pParse, pTab->pTrigger, + TK_DELETE, TK_BEFORE, TK_ROW, 0); + 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 */ @@ -195,6 +197,12 @@ void sqliteDeleteFrom( */ sqliteWhereEnd(pWInfo); + /* Open the pseudo-table used to store OLD if there are triggers. + */ + if( row_triggers_exist ){ + sqliteVdbeAddOp(v, OP_OpenPseudo, oldIdx, 0); + } + /* Delete every item whose key was written to the list during the ** database scan. We have to delete items after the scan is complete ** because deleting an item can change the scan order. @@ -211,20 +219,11 @@ void sqliteDeleteFrom( sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0); sqliteVdbeAddOp(v, OP_OpenRead, base, pTab->tnum); sqliteVdbeAddOp(v, OP_MoveTo, base, 0); - sqliteVdbeAddOp(v, OP_OpenTemp, oldIdx, 0); - sqliteVdbeAddOp(v, OP_Integer, 13, 0); - for(i = 0; inCol; i++){ - if( i==pTab->iPKey ){ - sqliteVdbeAddOp(v, OP_Recno, base, 0); - } else { - sqliteVdbeAddOp(v, OP_Column, base, i); - } - } - sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 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); - sqliteVdbeAddOp(v, OP_Rewind, oldIdx, 0); sqliteCodeRowTrigger(pParse, TK_DELETE, 0, TK_BEFORE, pTab, -1, oldIdx, (pParse->trigStack)?pParse->trigStack->orconf:OE_Default, diff --git a/src/expr.c b/src/expr.c index caa6de11a8..432cfec58b 100644 --- a/src/expr.c +++ b/src/expr.c @@ -12,7 +12,7 @@ ** This file contains routines used for analyzing expressions and ** for generating VDBE code that evaluates expressions in SQLite. ** -** $Id: expr.c,v 1.91 2003/03/31 02:12:47 drh Exp $ +** $Id: expr.c,v 1.92 2003/04/15 19:22:23 drh Exp $ */ #include "sqliteInt.h" #include @@ -547,12 +547,8 @@ int sqliteExprResolveIds( if( sqliteStrICmp(pTab->aCol[j].zName, zRight)==0 ){ cnt++; pExpr->iTable = i + base; - if( j==pTab->iPKey ){ - /* Substitute the record number for the INTEGER PRIMARY KEY */ - pExpr->iColumn = -1; - }else{ - pExpr->iColumn = j; - } + /* Substitute the rowid (column -1) for the INTEGER PRIMARY KEY */ + pExpr->iColumn = j==pTab->iPKey ? -1 : j; pExpr->dataType = pTab->aCol[j].sortOrder & SQLITE_SO_TYPEMASK; } } @@ -580,7 +576,7 @@ int sqliteExprResolveIds( for(j=0; j < pTab->nCol; j++) { if( sqliteStrICmp(pTab->aCol[j].zName, zRight)==0 ){ cnt++; - pExpr->iColumn = j; + pExpr->iColumn = j==pTab->iPKey ? -1 : j; pExpr->dataType = pTab->aCol[j].sortOrder & SQLITE_SO_TYPEMASK; } } diff --git a/src/insert.c b/src/insert.c index 2e376fa6ac..116c646be1 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.78 2003/04/03 01:50:44 drh Exp $ +** $Id: insert.c,v 1.79 2003/04/15 19:22:23 drh Exp $ */ #include "sqliteInt.h" @@ -110,7 +110,9 @@ void sqliteInsert( int iCntMem; /* Memory cell used for the row counter */ int row_triggers_exist = 0; /* True if there are FOR EACH ROW triggers */ - int newIdx = -1; + int before_triggers; /* True if there are BEFORE triggers */ + int after_triggers; /* True if there are AFTER triggers */ + int newIdx = -1; /* Cursor for the NEW table */ if( pParse->nErr || sqlite_malloc_failed ) goto insert_cleanup; db = pParse->db; @@ -132,10 +134,11 @@ void sqliteInsert( * (a) the table is not read-only, * (b) that if it is a view then ON INSERT triggers exist */ - row_triggers_exist = - sqliteTriggersExist(pParse, pTab->pTrigger, TK_INSERT, - TK_BEFORE, TK_ROW, 0) || - sqliteTriggersExist(pParse, pTab->pTrigger, TK_INSERT, TK_AFTER, TK_ROW, 0); + before_triggers = sqliteTriggersExist(pParse, pTab->pTrigger, TK_INSERT, + TK_BEFORE, TK_ROW, 0); + 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", @@ -311,7 +314,7 @@ void sqliteInsert( /* Open the temp table for FOR EACH ROW triggers */ if( row_triggers_exist ){ - sqliteVdbeAddOp(v, OP_OpenTemp, newIdx, 0); + sqliteVdbeAddOp(v, OP_OpenPseudo, newIdx, 0); } /* Initialize the count of rows to be inserted @@ -350,11 +353,33 @@ void sqliteInsert( sqliteVdbeResolveLabel(v, iInsertBlock); } + /* Run the BEFORE triggers, if there are any + */ endOfLoop = sqliteVdbeMakeLabel(v); - if( row_triggers_exist ){ + if( before_triggers ){ - /* build the new.* reference row */ - sqliteVdbeAddOp(v, OP_Integer, 13, 0); + /* build the NEW.* reference row. Note that if there is an INTEGER + ** PRIMARY KEY into which a NULL is being inserted, that NULL will be + ** translated into a unique ID for the row. But on a BEFORE trigger, + ** we do not know what the unique ID will be (because the insert has + ** not happened yet) so we substitute a rowid of -1 + */ + if( keyColumn<0 ){ + sqliteVdbeAddOp(v, OP_Integer, -1, 0); + }else if( useTempTable ){ + sqliteVdbeAddOp(v, OP_Column, srcTab, keyColumn); + }else if( pSelect ){ + sqliteVdbeAddOp(v, OP_Dup, nColumn - keyColumn - 1, 1); + }else{ + sqliteExprCode(pParse, pList->a[keyColumn].pExpr); + sqliteVdbeAddOp(v, OP_NotNull, -1, sqliteVdbeCurrentAddr(v)+3); + sqliteVdbeAddOp(v, OP_Pop, 1, 0); + sqliteVdbeAddOp(v, OP_Integer, -1, 0); + sqliteVdbeAddOp(v, OP_MustBeInt, 0, 0); + } + + /* Create the new column data + */ for(i=0; inCol; i++){ if( pColumn==0 ){ j = i; @@ -383,8 +408,12 @@ void sqliteInsert( onError, endOfLoop) ){ goto insert_cleanup; } + } - /* Open the tables and indices for the INSERT */ + /* 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); @@ -459,7 +488,8 @@ void sqliteInsert( ** do the insertion. */ sqliteGenerateConstraintChecks(pParse, pTab, base, 0,0,0,onError,endOfLoop); - sqliteCompleteInsertion(pParse, pTab, base, 0,0,0); + sqliteCompleteInsertion(pParse, pTab, base, 0,0,0, + after_triggers ? newIdx : -1); /* Update the count of rows that are inserted */ @@ -807,7 +837,8 @@ void sqliteCompleteInsertion( int base, /* Index of a read/write cursor pointing at pTab */ char *aIdxUsed, /* Which indices are used. NULL means all are used */ int recnoChng, /* True if the record number will change */ - int isUpdate /* True for UPDATE, False for INSERT */ + int isUpdate, /* True for UPDATE, False for INSERT */ + int newIdx /* Index of NEW table for triggers. -1 if none */ ){ int i; Vdbe *v; @@ -823,6 +854,11 @@ void sqliteCompleteInsertion( sqliteVdbeAddOp(v, OP_IdxPut, base+i+1, 0); } sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0); + if( newIdx>=0 ){ + sqliteVdbeAddOp(v, OP_Dup, 1, 0); + sqliteVdbeAddOp(v, OP_Dup, 1, 0); + sqliteVdbeAddOp(v, OP_PutIntKey, newIdx, 0); + } sqliteVdbeAddOp(v, OP_PutIntKey, base, pParse->trigStack?0:1); if( isUpdate && recnoChng ){ sqliteVdbeAddOp(v, OP_Pop, 1, 0); diff --git a/src/sqliteInt.h b/src/sqliteInt.h index fe3743a015..cf39140cac 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -11,7 +11,7 @@ ************************************************************************* ** Internal interface definitions for SQLite. ** -** @(#) $Id: sqliteInt.h,v 1.172 2003/04/15 01:19:49 drh Exp $ +** @(#) $Id: sqliteInt.h,v 1.173 2003/04/15 19:22:24 drh Exp $ */ #include "config.h" #include "sqlite.h" @@ -85,7 +85,7 @@ ** a minimum. */ /* #define SQLITE_OMIT_AUTHORIZATION 1 */ -#define SQLITE_OMIT_INMEMORYDB 1 +/* #define SQLITE_OMIT_INMEMORYDB 1 */ /* #define SQLITE_OMIT_TRACE 1 */ /* #define SQLITE_OMIT_VACUUM 1 */ @@ -1088,7 +1088,7 @@ int sqliteIsRowid(const char*); void sqliteGenerateRowDelete(sqlite*, Vdbe*, Table*, int, int); void sqliteGenerateRowIndexDelete(sqlite*, Vdbe*, Table*, int, char*); void sqliteGenerateConstraintChecks(Parse*,Table*,int,char*,int,int,int,int); -void sqliteCompleteInsertion(Parse*, Table*, int, char*, int, int); +void sqliteCompleteInsertion(Parse*, Table*, int, char*, int, int, int); void sqliteBeginWriteOperation(Parse*, int, int); void sqliteEndWriteOperation(Parse*); Expr *sqliteExprDup(Expr*); diff --git a/src/trigger.c b/src/trigger.c index ec95daae66..71a784aaef 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -714,10 +714,10 @@ void sqliteViewTriggers( /* Allocate temp tables */ oldIdx = pParse->nTab++; - sqliteVdbeAddOp(v, OP_OpenTemp, oldIdx, 0); + sqliteVdbeAddOp(v, OP_OpenPseudo, oldIdx, 0); if( pChanges ){ newIdx = pParse->nTab++; - sqliteVdbeAddOp(v, OP_OpenTemp, newIdx, 0); + sqliteVdbeAddOp(v, OP_OpenPseudo, newIdx, 0); } /* Snapshot the view */ diff --git a/src/update.c b/src/update.c index 58ff54b480..721dbaa871 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.58 2003/03/31 02:12:48 drh Exp $ +** $Id: update.c,v 1.59 2003/04/15 19:22:24 drh Exp $ */ #include "sqliteInt.h" @@ -47,7 +47,9 @@ void sqliteUpdate( Expr *pRecnoExpr; /* Expression defining the new record number */ int openAll; /* True if all indices need to be opened */ - int row_triggers_exist = 0; + int before_triggers; /* True if there are any BEFORE triggers */ + int after_triggers; /* True if there are any AFTER triggers */ + int row_triggers_exist = 0; /* True if any row triggers exist */ int newIdx = -1; /* index of trigger "new" temp table */ int oldIdx = -1; /* index of trigger "old" temp table */ @@ -64,11 +66,13 @@ void sqliteUpdate( if( zTab != 0 ){ pTab = sqliteFindTable(pParse->db, zTab, zDb); if( pTab ){ - row_triggers_exist = + before_triggers = sqliteTriggersExist(pParse, pTab->pTrigger, - TK_UPDATE, TK_BEFORE, TK_ROW, pChanges) || + TK_UPDATE, TK_BEFORE, TK_ROW, pChanges); + 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 ){ @@ -219,50 +223,56 @@ void sqliteUpdate( } if( row_triggers_exist ){ - int ii; - - sqliteVdbeAddOp(v, OP_OpenTemp, oldIdx, 0); - sqliteVdbeAddOp(v, OP_OpenTemp, newIdx, 0); + /* Create pseudo-tables for NEW and OLD + */ + sqliteVdbeAddOp(v, OP_OpenPseudo, oldIdx, 0); + sqliteVdbeAddOp(v, OP_OpenPseudo, newIdx, 0); + /* The top of the update loop for when there are triggers. + */ sqliteVdbeAddOp(v, OP_ListRewind, 0, 0); addr = sqliteVdbeAddOp(v, OP_ListRead, 0, 0); sqliteVdbeAddOp(v, OP_Dup, 0, 0); + /* Open a cursor and make it point to the record that is + ** being updated. + */ sqliteVdbeAddOp(v, OP_Dup, 0, 0); sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0); sqliteVdbeAddOp(v, OP_OpenRead, base, pTab->tnum); sqliteVdbeAddOp(v, OP_MoveTo, base, 0); - sqliteVdbeAddOp(v, OP_Integer, 13, 0); - for(ii = 0; ii < pTab->nCol; ii++){ - if( ii == pTab->iPKey ){ - sqliteVdbeAddOp(v, OP_Recno, base, 0); - }else{ - sqliteVdbeAddOp(v, OP_Column, base, ii); - } - } - sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0); + /* Generate the OLD table + */ + sqliteVdbeAddOp(v, OP_Recno, base, 0); + sqliteVdbeAddOp(v, OP_RowData, base, 0); sqliteVdbeAddOp(v, OP_PutIntKey, oldIdx, 0); - sqliteVdbeAddOp(v, OP_Integer, 13, 0); - for(ii = 0; ii < pTab->nCol; ii++){ - if( aXRef[ii] < 0 ){ - if( ii == pTab->iPKey ){ - sqliteVdbeAddOp(v, OP_Recno, base, 0); - }else{ - sqliteVdbeAddOp(v, OP_Column, base, ii); - } + /* Generate the NEW table + */ + if( chngRecno ){ + sqliteExprCode(pParse, pRecnoExpr); + }else{ + sqliteVdbeAddOp(v, OP_Recno, base, 0); + } + 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[aXRef[ii]].pExpr); + sqliteExprCode(pParse, pChanges->a[j].pExpr); } } sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0); sqliteVdbeAddOp(v, OP_PutIntKey, newIdx, 0); sqliteVdbeAddOp(v, OP_Close, base, 0); - sqliteVdbeAddOp(v, OP_Rewind, oldIdx, 0); - sqliteVdbeAddOp(v, OP_Rewind, newIdx, 0); - + /* Fire the BEFORE triggers + */ if( sqliteCodeRowTrigger(pParse, TK_UPDATE, pChanges, TK_BEFORE, pTab, newIdx, oldIdx, onError, addr) ){ goto update_cleanup; @@ -350,7 +360,7 @@ void sqliteUpdate( /* Create the new index entries and the new record. */ - sqliteCompleteInsertion(pParse, pTab, base, aIdxUsed, chngRecno, 1); + sqliteCompleteInsertion(pParse, pTab, base, aIdxUsed, chngRecno, 1, -1); /* Increment the row counter */ diff --git a/src/vdbe.c b/src/vdbe.c index 0b8b2137e1..d707c9a159 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -36,7 +36,7 @@ ** in this file for details. If in doubt, do not deviate from existing ** commenting and indentation practices when changing or adding code. ** -** $Id: vdbe.c,v 1.214 2003/04/15 14:01:43 drh Exp $ +** $Id: vdbe.c,v 1.215 2003/04/15 19:22:24 drh Exp $ */ #include "sqliteInt.h" #include @@ -79,6 +79,11 @@ typedef unsigned char Bool; ** ** Every cursor that the virtual machine has open is represented by an ** instance of the following structure. +** +** If the Cursor.isTriggerRow flag is set it means that this cursor is +** really a single row that represents the NEW or OLD pseudo-table of +** a row trigger. The data for the row is stored in Cursor.pData and +** the rowid is in Cursor.iKey. */ struct Cursor { BtCursor *pCursor; /* The cursor structure of the backend */ @@ -90,7 +95,11 @@ struct Cursor { Bool useRandomRowid; /* Generate new record numbers semi-randomly */ Bool nullRow; /* True if pointing to a row with no data */ Bool nextRowidValid; /* True if the nextRowid field is valid */ + Bool pseudoTable; /* This is a NEW or OLD pseudo-tables of a trigger */ Btree *pBt; /* Separate file holding temporary table */ + int nData; /* Number of bytes in pData */ + char *pData; /* Data for a NEW or OLD pseudo-table */ + int iKey; /* Key for the NEW or OLD pseudo-table row */ }; typedef struct Cursor Cursor; @@ -1123,6 +1132,7 @@ static void cleanupCursor(Cursor *pCx){ if( pCx->pBt ){ sqliteBtreeClose(pCx->pBt); } + sqliteFree(pCx->pData); memset(pCx, 0, sizeof(Cursor)); } @@ -3522,6 +3532,29 @@ case OP_OpenTemp: { break; } +/* Opcode: OpenPseudo P1 * * +** +** Open a new cursor that points to a fake table that contains a single +** row of data. Any attempt to write a second row of data causes the +** first row to be deleted. All data is deleted when the cursor is +** closed. +** +** A pseudo-table created by this opcode is useful for holding the +** NEW or OLD tables in a trigger. +*/ +case OP_OpenPseudo: { + int i = pOp->p1; + Cursor *pCx; + VERIFY( if( i<0 ) goto bad_instruction; ) + if( expandCursorArraySize(p, i) ) goto no_mem; + pCx = &p->aCsr[i]; + cleanupCursor(pCx); + memset(pCx, 0, sizeof(*pCx)); + pCx->nullRow = 1; + pCx->pseudoTable = 1; + break; +} + /* ** Opcode: RenameCursor P1 P2 * ** @@ -3550,7 +3583,7 @@ case OP_RenameCursor: { */ case OP_Close: { int i = pOp->p1; - if( i>=0 && inCursor && p->aCsr[i].pCursor ){ + if( i>=0 && inCursor ){ cleanupCursor(&p->aCsr[i]); } break; @@ -3584,7 +3617,9 @@ case OP_MoveTo: { Cursor *pC; VERIFY( if( tos<0 ) goto not_enough_stack; ) - if( i>=0 && inCursor && (pC = &p->aCsr[i])->pCursor!=0 ){ + assert( i>=0 && inCursor ); + pC = &p->aCsr[i]; + if( pC->pCursor!=0 ){ int res, oc; if( aStack[tos].flags & STK_Int ){ int iKey = intToKey(aStack[tos].i); @@ -3926,6 +3961,8 @@ case OP_NewRecno: { ** entry is overwritten. The data is the value on the top of the ** stack. The key is the next value down on the stack. The key must ** be a string. The stack is popped twice by this instruction. +** +** P1 may not be a pseudo-table opened using the OpenPseudo opcode. */ case OP_PutIntKey: case OP_PutStrKey: { @@ -3934,7 +3971,8 @@ case OP_PutStrKey: { int i = pOp->p1; Cursor *pC; VERIFY( if( nos<0 ) goto not_enough_stack; ) - if( VERIFY( i>=0 && inCursor && ) (pC = &p->aCsr[i])->pCursor!=0 ){ + if( VERIFY( i>=0 && inCursor && ) + ((pC = &p->aCsr[i])->pCursor!=0 || pC->pseudoTable) ){ char *zKey; int nKey, iKey; if( pOp->opcode==OP_PutStrKey ){ @@ -3954,8 +3992,30 @@ case OP_PutStrKey: { pC->nextRowidValid = 0; } } - rc = sqliteBtreeInsert(pC->pCursor, zKey, nKey, - zStack[tos], aStack[tos].n); + if( pC->pseudoTable ){ + /* PutStrKey does not work for pseudo-tables. + ** The following assert makes sure we are not trying to use + ** PutStrKey on a pseudo-table + */ + assert( pOp->opcode==OP_PutIntKey ); + sqliteFree(pC->pData); + pC->iKey = iKey; + pC->nData = aStack[tos].n; + if( aStack[tos].flags & STK_Dyn ){ + pC->pData = zStack[tos]; + zStack[tos] = 0; + aStack[tos].flags = STK_Null; + }else{ + pC->pData = sqliteMallocRaw( pC->nData ); + if( pC->pData ){ + memcpy(pC->pData, zStack[tos], pC->nData); + } + } + pC->nullRow = 0; + }else{ + rc = sqliteBtreeInsert(pC->pCursor, zKey, nKey, + zStack[tos], aStack[tos].n); + } pC->recnoIsValid = 0; } POPSTACK; @@ -3974,11 +4034,15 @@ case OP_PutStrKey: { ** ** The row change counter is incremented if P2==1 and is unmodified ** if P2==0. +** +** If P1 is a pseudo-table, then this instruction is a no-op. */ case OP_Delete: { int i = pOp->p1; Cursor *pC; - if( VERIFY( i>=0 && inCursor && ) (pC = &p->aCsr[i])->pCursor!=0 ){ + assert( i>=0 && inCursor ); + pC = &p->aCsr[i]; + if( pC->pCursor!=0 ){ rc = sqliteBtreeDelete(pC->pCursor); pC->nextRowidValid = 0; } @@ -3995,8 +4059,61 @@ case OP_Delete: { */ case OP_KeyAsData: { int i = pOp->p1; - if( VERIFY( i>=0 && inCursor && ) p->aCsr[i].pCursor!=0 ){ - p->aCsr[i].keyAsData = pOp->p2; + assert( i>=0 && inCursor ); + p->aCsr[i].keyAsData = pOp->p2; + break; +} + +/* Opcode: RowData P1 * * +** +** Push onto the stack the complete row data for cursor P1. +** There is no interpretation of the data. It is just copied +** onto the stack exactly as it is found in the database file. +** +** If the cursor is not pointing to a valid row, a NULL is pushed +** onto the stack. +*/ +case OP_RowData: { + int i = pOp->p1; + int tos = ++p->tos; + Cursor *pC; + int n; + + assert( i>=0 && inCursor ); + pC = &p->aCsr[i]; + if( pC->nullRow ){ + aStack[tos].flags = STK_Null; + }else if( pC->pCursor!=0 ){ + BtCursor *pCrsr = pC->pCursor; + if( pC->nullRow ){ + aStack[tos].flags = STK_Null; + break; + }else if( pC->keyAsData ){ + sqliteBtreeKeySize(pCrsr, &n); + }else{ + sqliteBtreeDataSize(pCrsr, &n); + } + aStack[tos].n = n; + if( n<=NBFS ){ + aStack[tos].flags = STK_Str; + zStack[tos] = aStack[tos].z; + }else{ + char *z = sqliteMallocRaw( n ); + if( z==0 ) goto no_mem; + aStack[tos].flags = STK_Str | STK_Dyn; + zStack[tos] = z; + } + if( pC->keyAsData ){ + sqliteBtreeKey(pCrsr, 0, n, zStack[tos]); + }else{ + sqliteBtreeData(pCrsr, 0, n, zStack[tos]); + } + }else if( pC->pseudoTable ){ + aStack[tos].n = pC->nData; + zStack[tos] = pC->pData; + aStack[tos].flags = STK_Str|STK_Ephem; + }else{ + aStack[tos].flags = STK_Null; } break; } @@ -4031,12 +4148,13 @@ case OP_Column: { int idxWidth; unsigned char aHdr[10]; + assert( inCursor ); if( i<0 ){ VERIFY( if( tos+i<0 ) goto bad_instruction; ) VERIFY( if( (aStack[tos+i].flags & STK_Str)==0 ) goto bad_instruction; ) zRec = zStack[tos+i]; payloadSize = aStack[tos+i].n; - }else if( VERIFY( i>=0 && inCursor && ) (pC = &p->aCsr[i])->pCursor!=0 ){ + }else if( (pC = &p->aCsr[i])->pCursor!=0 ){ zRec = 0; pCrsr = pC->pCursor; if( pC->nullRow ){ @@ -4046,6 +4164,10 @@ case OP_Column: { }else{ sqliteBtreeDataSize(pCrsr, &payloadSize); } + }else if( pC->pseudoTable ){ + payloadSize = pC->nData; + zRec = pC->pData; + assert( payloadSize==0 || zRec!=0 ); }else{ payloadSize = 0; } @@ -4135,22 +4257,24 @@ case OP_Column: { case OP_Recno: { int i = pOp->p1; int tos = ++p->tos; - BtCursor *pCrsr; + Cursor *pC; + int v; - if( VERIFY( i>=0 && inCursor && ) (pCrsr = p->aCsr[i].pCursor)!=0 ){ - int v; - if( p->aCsr[i].recnoIsValid ){ - v = p->aCsr[i].lastRecno; - }else if( p->aCsr[i].nullRow ){ - aStack[tos].flags = STK_Null; - break; - }else{ - sqliteBtreeKey(pCrsr, 0, sizeof(u32), (char*)&v); - v = keyToInt(v); - } - aStack[tos].i = v; - aStack[tos].flags = STK_Int; + assert( i>=0 && inCursor ); + if( (pC = &p->aCsr[i])->recnoIsValid ){ + v = pC->lastRecno; + }else if( pC->nullRow ){ + aStack[tos].flags = STK_Null; + break; + }else if( pC->pseudoTable ){ + v = keyToInt(pC->iKey); + }else{ + assert( pC->pCursor!=0 ); + sqliteBtreeKey(pC->pCursor, 0, sizeof(u32), (char*)&v); + v = keyToInt(v); } + aStack[tos].i = v; + aStack[tos].flags = STK_Int; break; } @@ -4162,6 +4286,8 @@ case OP_Recno: { ** Compare this opcode to Recno. The Recno opcode extracts the first ** 4 bytes of the key and pushes those bytes onto the stack as an ** integer. This instruction pushes the entire key as a string. +** +** This opcode may not be used on a pseudo-table. */ case OP_FullKey: { int i = pOp->p1; @@ -4169,6 +4295,7 @@ case OP_FullKey: { BtCursor *pCrsr; VERIFY( if( !p->aCsr[i].keyAsData ) goto bad_instruction; ) + VERIFY( if( p->aCsr[i].pseudoTable ) goto bad_instruction; ) if( VERIFY( i>=0 && inCursor && ) (pCrsr = p->aCsr[i].pCursor)!=0 ){ int amt; char *z; @@ -4201,12 +4328,10 @@ case OP_FullKey: { */ case OP_NullRow: { int i = pOp->p1; - BtCursor *pCrsr; - if( VERIFY( i>=0 && inCursor && ) (pCrsr = p->aCsr[i].pCursor)!=0 ){ - p->aCsr[i].nullRow = 1; - p->aCsr[i].recnoIsValid = 0; - } + assert( i>=0 && inCursor ); + p->aCsr[i].nullRow = 1; + p->aCsr[i].recnoIsValid = 0; break; } @@ -4220,15 +4345,20 @@ case OP_NullRow: { */ case OP_Last: { int i = pOp->p1; + Cursor *pC; BtCursor *pCrsr; - if( VERIFY( i>=0 && inCursor && ) (pCrsr = p->aCsr[i].pCursor)!=0 ){ + assert( i>=0 && inCursor ); + pC = &p->aCsr[i]; + if( (pCrsr = pC->pCursor)!=0 ){ int res; sqliteBtreeLast(pCrsr, &res); p->aCsr[i].nullRow = res; if( res && pOp->p2>0 ){ pc = pOp->p2 - 1; } + }else{ + pC->nullRow = 0; } break; } @@ -4243,16 +4373,21 @@ case OP_Last: { */ case OP_Rewind: { int i = pOp->p1; + Cursor *pC; BtCursor *pCrsr; - if( VERIFY( i>=0 && inCursor && ) (pCrsr = p->aCsr[i].pCursor)!=0 ){ + assert( i>=0 && inCursor ); + pC = &p->aCsr[i]; + if( (pCrsr = pC->pCursor)!=0 ){ int res; sqliteBtreeFirst(pCrsr, &res); - p->aCsr[i].atFirst = res==0; - p->aCsr[i].nullRow = res; + pC->atFirst = res==0; + pC->nullRow = res; if( res && pOp->p2>0 ){ pc = pOp->p2 - 1; } + }else{ + pC->nullRow = 0; } break; } @@ -4279,8 +4414,9 @@ case OP_Next: { BtCursor *pCrsr; CHECK_FOR_INTERRUPT; - if( VERIFY( pOp->p1>=0 && pOp->p1nCursor && ) - (pCrsr = (pC = &p->aCsr[pOp->p1])->pCursor)!=0 ){ + assert( pOp->p1>=0 && pOp->p1nCursor ); + pC = &p->aCsr[pOp->p1]; + if( (pCrsr = pC->pCursor)!=0 ){ int res; if( pC->nullRow ){ res = 1; @@ -4293,8 +4429,10 @@ case OP_Next: { pc = pOp->p2 - 1; sqlite_search_count++; } - pC->recnoIsValid = 0; + }else{ + pC->nullRow = 1; } + pC->recnoIsValid = 0; break; } diff --git a/test/rowid.test b/test/rowid.test index 1c4c49c1e2..15d0c692b4 100644 --- a/test/rowid.test +++ b/test/rowid.test @@ -12,7 +12,7 @@ # focus of this file is testing the magic ROWID column that is # found on all tables. # -# $Id: rowid.test,v 1.9 2003/04/15 14:01:44 drh Exp $ +# $Id: rowid.test,v 1.10 2003/04/15 19:22:24 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -403,7 +403,7 @@ do_test rowid-8.8 { execsql { SELECT rowid, * FROM t4; } -} {1 1 2 133 3 {}} +} {1 1 2 133 3 134} finish_test