1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-08-07 02:42:48 +03:00

Added FOR EACH ROW triggers functionality (CVS 562)

FossilOrigin-Name: 794bf67b6b36fce8854d5daff12f21dbb943240c
This commit is contained in:
danielk1977
2002-05-15 08:30:12 +00:00
parent 9456bcc975
commit c3f9bad209
18 changed files with 2239 additions and 125 deletions

View File

@@ -1,5 +1,5 @@
C Version\s2.4.12\s(CVS\s561) C Added\sFOR\sEACH\sROW\striggers\sfunctionality\s(CVS\s562)
D 2002-05-10T14:41:54 D 2002-05-15T08:30:13
F Makefile.in 50f1b3351df109b5774771350d8c1b8d3640130d F Makefile.in 50f1b3351df109b5774771350d8c1b8d3640130d
F Makefile.template 89e373b2dad0321df00400fa968dc14b61a03296 F Makefile.template 89e373b2dad0321df00400fa968dc14b61a03296
F README a4c0ba11354ef6ba0776b400d057c59da47a4cc0 F README a4c0ba11354ef6ba0776b400d057c59da47a4cc0
@@ -20,40 +20,41 @@ F sqlite.1 83f4a9d37bdf2b7ef079a82d54eaf2e3509ee6ea
F src/TODO af7f3cab0228e34149cf98e073aa83d45878e7e6 F src/TODO af7f3cab0228e34149cf98e073aa83d45878e7e6
F src/btree.c 7dd7ddc66459982dd0cb9800958c1f8d65a32d9f F src/btree.c 7dd7ddc66459982dd0cb9800958c1f8d65a32d9f
F src/btree.h 8abeabfe6e0b1a990b64fa457592a6482f6674f3 F src/btree.h 8abeabfe6e0b1a990b64fa457592a6482f6674f3
F src/build.c 6a5064503250e7b8cdd9285970d01522df8468f7 F src/build.c cbf1b552d381c3f94baad9be2defbc60a158ac64
F src/delete.c 6a6b8192cdff5e4b083da3bc63de099f3790d01f F src/delete.c 392159781f9dff5f07ce2cb7d3a3a184eb38c0ab
F src/encode.c 346b12b46148506c32038524b95c4631ab46d760 F src/encode.c 346b12b46148506c32038524b95c4631ab46d760
F src/expr.c cf8d2ea17e419fc83b23e080195b2952e0be4164 F src/expr.c 6888e37e4eecdc20567aedd442328df752465723
F src/func.c a31dcba85bc2ecb9b752980289cf7e6cd0cafbce F src/func.c a31dcba85bc2ecb9b752980289cf7e6cd0cafbce
F src/hash.c cc259475e358baaf299b00a2c7370f2b03dda892 F src/hash.c cc259475e358baaf299b00a2c7370f2b03dda892
F src/hash.h dca065dda89d4575f3176e75e9a3dc0f4b4fb8b9 F src/hash.h dca065dda89d4575f3176e75e9a3dc0f4b4fb8b9
F src/insert.c 31233f44fc79edbb43523a830e54736a8e222ff4 F src/insert.c 9f89b395e25f2a9eaea841fa736a4036d33d2b24
F src/main.c 0a4643660b2a3dee3427a10fcea336756526c27c F src/main.c 6bc0b3dd014f6af13007472581593e87b2797139
F src/md5.c b2b1a34fce66ceca97f4e0dabc20be8be7933c92 F src/md5.c b2b1a34fce66ceca97f4e0dabc20be8be7933c92
F src/os.c 5ab8b6b4590d0c1ab8e96c67996c170e4462e0fc F src/os.c 5ab8b6b4590d0c1ab8e96c67996c170e4462e0fc
F src/os.h 4a361fccfbc4e7609b3e1557f604f94c1e96ad10 F src/os.h 4a361fccfbc4e7609b3e1557f604f94c1e96ad10
F src/pager.c ba5740104cc27b342cd43eebfdc44d60f64a3ded F src/pager.c ba5740104cc27b342cd43eebfdc44d60f64a3ded
F src/pager.h 6fddfddd3b73aa8abc081b973886320e3c614f0e F src/pager.h 6fddfddd3b73aa8abc081b973886320e3c614f0e
F src/parse.y 83850a81fe9170d32eb683e77d7602736c663e34 F src/parse.y 164789531d0c6a2c28fb4baded14afc1be4bd4aa
F src/printf.c 300a90554345751f26e1fc0c0333b90a66110a1d F src/printf.c 300a90554345751f26e1fc0c0333b90a66110a1d
F src/random.c 19e8e00fe0df32a742f115773f57651be327cabe F src/random.c 19e8e00fe0df32a742f115773f57651be327cabe
F src/select.c 1b623a7d826ec7c245bc542b665d61724da2a62d F src/select.c 1b623a7d826ec7c245bc542b665d61724da2a62d
F src/shell.c 5acbe59e137d60d8efd975c683dbea74ab626530 F src/shell.c 5acbe59e137d60d8efd975c683dbea74ab626530
F src/shell.tcl 27ecbd63dd88396ad16d81ab44f73e6c0ea9d20e F src/shell.tcl 27ecbd63dd88396ad16d81ab44f73e6c0ea9d20e
F src/sqlite.h.in 0038faa6d642de06b91143ee65a131bd831d020b F src/sqlite.h.in 0038faa6d642de06b91143ee65a131bd831d020b
F src/sqliteInt.h b37d2d28e4ca3dcf67772bf937aa12b171d9610b F src/sqliteInt.h a96603825503c5bbd095f1ac34ce1023f89a908e
F src/table.c eed2098c9b577aa17f8abe89313a9c4413f57d63 F src/table.c eed2098c9b577aa17f8abe89313a9c4413f57d63
F src/tclsqlite.c 9300c9606a38bc0c75d6c0bc8a6197ab979353d1 F src/tclsqlite.c 9300c9606a38bc0c75d6c0bc8a6197ab979353d1
F src/test1.c 09d95048b66ce6dcd2bae90f443589043d7d631e F src/test1.c 09d95048b66ce6dcd2bae90f443589043d7d631e
F src/test2.c 669cc22781c6461a273416ec1a7414d25c081730 F src/test2.c 669cc22781c6461a273416ec1a7414d25c081730
F src/test3.c 4e52fff8b01f08bd202f7633feda5639b7ba2b5e F src/test3.c 4e52fff8b01f08bd202f7633feda5639b7ba2b5e
F src/threadtest.c 81f0598e0f031c1bd506af337fdc1b7e8dff263f F src/threadtest.c 81f0598e0f031c1bd506af337fdc1b7e8dff263f
F src/tokenize.c 5624d342601f616157ba266abccc1368a5afee70 F src/tokenize.c f12f78c58b2a79ea4eee880efad63a328e103c62
F src/update.c 7dd714a6a7fa47f849ebb36b6d915974d6c6accb F src/trigger.c b8df3e8f0952979bbbcbd0cb05b7d564924a3282
F src/update.c 2e8becd1cd3a597f74f8879e2c246cca5d20a119
F src/util.c 707c30f8c13cddace7c08556ac450c0b786660b3 F src/util.c 707c30f8c13cddace7c08556ac450c0b786660b3
F src/vdbe.c c957417fa83b5fb717dcd81204c253125b3e7e0c F src/vdbe.c 428d7dba1fb84a3da6170c3cb387d177c315a72a
F src/vdbe.h 67840a462e1daedb958cca0ccc97db140d3d9152 F src/vdbe.h 126a651ba26f05de075dcc6da5466244a31af6b8
F src/where.c 5e3e97adfa5800378f2ed45bb9312dd3a70e239c F src/where.c 3138c1b44193ab5f432919ab25e49f3d97bd6108
F test/all.test e4d3821eeba751829b419cd47814bd20af4286d1 F test/all.test e4d3821eeba751829b419cd47814bd20af4286d1
F test/bigrow.test 8ab252dba108f12ad64e337b0f2ff31a807ac578 F test/bigrow.test 8ab252dba108f12ad64e337b0f2ff31a807ac578
F test/btree.test bf326f546a666617367a7033fa2c07451bd4f8e1 F test/btree.test bf326f546a666617367a7033fa2c07451bd4f8e1
@@ -98,6 +99,8 @@ F test/tclsqlite.test 79deeffd7cd637ca0f06c5dbbf2f44d272079533
F test/temptable.test daa83489eea2e9aaeeece09675c28be84c72cb67 F test/temptable.test daa83489eea2e9aaeeece09675c28be84c72cb67
F test/tester.tcl dc1b56bd628b487e4d75bfd1e7480b5ed8810ac6 F test/tester.tcl dc1b56bd628b487e4d75bfd1e7480b5ed8810ac6
F test/trans.test ae0b9a82d5d34122c3a3108781eb8d078091ccee F test/trans.test ae0b9a82d5d34122c3a3108781eb8d078091ccee
F test/trigger1.test 06dd47935cf38ce5de0b232e7b61aad57685bae1
F test/trigger2.test 662818d5cc3313c14819df1c9084c119057a0bde
F test/unique.test 07776624b82221a80c8b4138ce0dd8b0853bb3ea F test/unique.test 07776624b82221a80c8b4138ce0dd8b0853bb3ea
F test/update.test 3cf1ca0565f678063c2dfa9a7948d2d66ae1a778 F test/update.test 3cf1ca0565f678063c2dfa9a7948d2d66ae1a778
F test/vacuum.test 059871b312eb910bbe49dafde1d01490cc2c6bbe F test/vacuum.test 059871b312eb910bbe49dafde1d01490cc2c6bbe
@@ -124,14 +127,14 @@ F www/dynload.tcl 02eb8273aa78cfa9070dd4501dca937fb22b466c
F www/faq.tcl 45bdb18b75ac3aa1befec42985fb892413aac0bb F www/faq.tcl 45bdb18b75ac3aa1befec42985fb892413aac0bb
F www/formatchng.tcl 2ce21ff30663fad6618198fe747ce675df577590 F www/formatchng.tcl 2ce21ff30663fad6618198fe747ce675df577590
F www/index.tcl d0c52fbf031d0a3ee6d9d77aa669d5a4b24b6130 F www/index.tcl d0c52fbf031d0a3ee6d9d77aa669d5a4b24b6130
F www/lang.tcl d47800eb1da14a2ea501c6088beccc4001fb0486 F www/lang.tcl a22cf9eff51e65ec5aa39b1efb5b7952d800ac06
F www/mingw.tcl f1c7c0a7f53387dd9bb4f8c7e8571b7561510ebc F www/mingw.tcl f1c7c0a7f53387dd9bb4f8c7e8571b7561510ebc
F www/opcode.tcl bdec8ef9f100dbd87bbef8976c54b88e43fd8ccc F www/opcode.tcl bdec8ef9f100dbd87bbef8976c54b88e43fd8ccc
F www/speed.tcl da8afcc1d3ccc5696cfb388a68982bc3d9f7f00f F www/speed.tcl da8afcc1d3ccc5696cfb388a68982bc3d9f7f00f
F www/sqlite.tcl 8b5884354cb615049aed83039f8dfe1552a44279 F www/sqlite.tcl 8b5884354cb615049aed83039f8dfe1552a44279
F www/tclsqlite.tcl 1db15abeb446aad0caf0b95b8b9579720e4ea331 F www/tclsqlite.tcl 1db15abeb446aad0caf0b95b8b9579720e4ea331
F www/vdbe.tcl 2013852c27a02a091d39a766bc87cff329f21218 F www/vdbe.tcl 2013852c27a02a091d39a766bc87cff329f21218
P 232b7ef2c8207eb6d2564a641446267d3dec97af P 06cdaf1c80f7bc25fc555c7c8a35258faed2d2e9
R 3b5832b01c3219f88712622031d79de7 R f9f4dba69771590feeaaa99270d2ac88
U drh U danielk1977
Z 49360959ab78dbfb5d6ce76a90c8f01e Z 1a13ba7db9cc1bf25e7e68999e113789

View File

@@ -1 +1 @@
06cdaf1c80f7bc25fc555c7c8a35258faed2d2e9 794bf67b6b36fce8854d5daff12f21dbb943240c

View File

@@ -25,7 +25,7 @@
** ROLLBACK ** ROLLBACK
** PRAGMA ** PRAGMA
** **
** $Id: build.c,v 1.87 2002/05/08 21:30:15 drh Exp $ ** $Id: build.c,v 1.88 2002/05/15 08:30:13 danielk1977 Exp $
*/ */
#include "sqliteInt.h" #include "sqliteInt.h"
#include <ctype.h> #include <ctype.h>
@@ -250,6 +250,22 @@ void sqliteCommitInternalChanges(sqlite *db){
sqliteUnlinkAndDeleteIndex(db, pIndex); sqliteUnlinkAndDeleteIndex(db, pIndex);
} }
sqliteHashClear(&db->idxDrop); sqliteHashClear(&db->idxDrop);
/* Set the commit flag on all triggers added this transaction */
for(pElem=sqliteHashFirst(&db->trigHash); pElem; pElem=sqliteHashNext(pElem)){
Trigger *pTrigger = sqliteHashData(pElem);
pTrigger->isCommit = 1;
}
/* Delete the structures for triggers removed this transaction */
pElem = sqliteHashFirst(&db->trigDrop);
while (pElem) {
Trigger *pTrigger = sqliteHashData(pElem);
sqliteDeleteTrigger(pTrigger);
pElem = sqliteHashNext(pElem);
}
sqliteHashClear(&db->trigDrop);
db->flags &= ~SQLITE_InternChanges; db->flags &= ~SQLITE_InternChanges;
} }
@@ -304,6 +320,48 @@ void sqliteRollbackInternalChanges(sqlite *db){
assert( pOld==0 || pOld==p ); assert( pOld==0 || pOld==p );
} }
sqliteHashClear(&db->idxDrop); sqliteHashClear(&db->idxDrop);
/* Remove any triggers that haven't been commited yet */
for(pElem = sqliteHashFirst(&db->trigHash); pElem;
pElem = (pElem?sqliteHashNext(pElem):0)) {
Trigger * pTrigger = sqliteHashData(pElem);
if (!pTrigger->isCommit) {
Table * tbl = sqliteFindTable(db, pTrigger->table);
if (tbl) {
if (tbl->pTrigger == pTrigger)
tbl->pTrigger = pTrigger->pNext;
else {
Trigger * cc = tbl->pTrigger;
while (cc) {
if (cc->pNext == pTrigger) {
cc->pNext = cc->pNext->pNext;
break;
}
cc = cc->pNext;
}
assert(cc);
}
}
sqliteHashInsert(&db->trigHash, pTrigger->name,
1 + strlen(pTrigger->name), 0);
sqliteDeleteTrigger(pTrigger);
pElem = sqliteHashFirst(&db->trigHash);
}
}
/* Any triggers that were dropped - put 'em back in place */
for(pElem = sqliteHashFirst(&db->trigDrop); pElem;
pElem = sqliteHashNext(pElem)) {
Trigger * pTrigger = sqliteHashData(pElem);
Table * tab = sqliteFindTable(db, pTrigger->table);
sqliteHashInsert(&db->trigHash, pTrigger->name,
strlen(pTrigger->name) + 1, pTrigger);
pTrigger->pNext = tab->pTrigger;
tab->pTrigger = pTrigger;
}
sqliteHashClear(&db->trigDrop);
db->flags &= ~SQLITE_InternChanges; db->flags &= ~SQLITE_InternChanges;
} }
@@ -595,7 +653,7 @@ void sqliteAddPrimaryKey(Parse *pParse, IdList *pList, int onError){
** and the probability of hitting the same cookie value is only ** and the probability of hitting the same cookie value is only
** 1 chance in 2^32. So we're safe enough. ** 1 chance in 2^32. So we're safe enough.
*/ */
static void changeCookie(sqlite *db){ void changeCookie(sqlite *db){
if( db->next_cookie==db->schema_cookie ){ if( db->next_cookie==db->schema_cookie ){
db->next_cookie = db->schema_cookie + sqliteRandomByte() + 1; db->next_cookie = db->schema_cookie + sqliteRandomByte() + 1;
db->flags |= SQLITE_InternChanges; db->flags |= SQLITE_InternChanges;
@@ -1036,6 +1094,13 @@ void sqliteDropTable(Parse *pParse, Token *pName, int isView){
}; };
Index *pIdx; Index *pIdx;
sqliteBeginWriteOperation(pParse); sqliteBeginWriteOperation(pParse);
/* Drop all triggers associated with the table being dropped */
while (pTable->pTrigger) {
Token tt;
tt.z = pTable->pTrigger->name;
tt.n = strlen(pTable->pTrigger->name);
sqliteDropTrigger(pParse, &tt, 1);
}
if( !pTable->isTemp ){ if( !pTable->isTemp ){
base = sqliteVdbeAddOpList(v, ArraySize(dropTable), dropTable); base = sqliteVdbeAddOpList(v, ArraySize(dropTable), dropTable);
sqliteVdbeChangeP3(v, base+2, pTable->zName, 0); sqliteVdbeChangeP3(v, base+2, pTable->zName, 0);
@@ -1653,6 +1718,7 @@ void sqliteBeginWriteOperation(Parse *pParse){
Vdbe *v; Vdbe *v;
v = sqliteGetVdbe(pParse); v = sqliteGetVdbe(pParse);
if( v==0 ) return; if( v==0 ) return;
if (pParse->trigStack) return; /* if this is in a trigger */
if( (pParse->db->flags & SQLITE_InTrans)==0 ){ if( (pParse->db->flags & SQLITE_InTrans)==0 ){
sqliteVdbeAddOp(v, OP_Transaction, 0, 0); sqliteVdbeAddOp(v, OP_Transaction, 0, 0);
sqliteVdbeAddOp(v, OP_VerifyCookie, pParse->db->schema_cookie, 0); sqliteVdbeAddOp(v, OP_VerifyCookie, pParse->db->schema_cookie, 0);
@@ -1672,6 +1738,7 @@ void sqliteBeginMultiWriteOperation(Parse *pParse){
Vdbe *v; Vdbe *v;
v = sqliteGetVdbe(pParse); v = sqliteGetVdbe(pParse);
if( v==0 ) return; if( v==0 ) return;
if (pParse->trigStack) return; /* if this is in a trigger */
if( (pParse->db->flags & SQLITE_InTrans)==0 ){ if( (pParse->db->flags & SQLITE_InTrans)==0 ){
sqliteVdbeAddOp(v, OP_Transaction, 0, 0); sqliteVdbeAddOp(v, OP_Transaction, 0, 0);
sqliteVdbeAddOp(v, OP_VerifyCookie, pParse->db->schema_cookie, 0); sqliteVdbeAddOp(v, OP_VerifyCookie, pParse->db->schema_cookie, 0);
@@ -1689,6 +1756,7 @@ void sqliteBeginMultiWriteOperation(Parse *pParse){
*/ */
void sqliteEndWriteOperation(Parse *pParse){ void sqliteEndWriteOperation(Parse *pParse){
Vdbe *v; Vdbe *v;
if (pParse->trigStack) return; /* if this is in a trigger */
v = sqliteGetVdbe(pParse); v = sqliteGetVdbe(pParse);
if( v==0 ) return; if( v==0 ) return;
if( pParse->db->flags & SQLITE_InTrans ){ if( pParse->db->flags & SQLITE_InTrans ){
@@ -1915,6 +1983,14 @@ void sqlitePragma(Parse *pParse, Token *pLeft, Token *pRight, int minusFlag){
} }
}else }else
if( sqliteStrICmp(zLeft, "trigger_overhead_test")==0 ){
if( getBoolean(zRight) ){
always_code_trigger_setup = 1;
}else{
always_code_trigger_setup = 0;
}
}else
if( sqliteStrICmp(zLeft, "vdbe_trace")==0 ){ if( sqliteStrICmp(zLeft, "vdbe_trace")==0 ){
if( getBoolean(zRight) ){ if( getBoolean(zRight) ){
db->flags |= SQLITE_VdbeTrace; db->flags |= SQLITE_VdbeTrace;

View File

@@ -12,7 +12,7 @@
** This file contains C code routines that are called by the parser ** This file contains C code routines that are called by the parser
** to handle DELETE FROM statements. ** to handle DELETE FROM statements.
** **
** $Id: delete.c,v 1.30 2002/04/12 10:08:59 drh Exp $ ** $Id: delete.c,v 1.31 2002/05/15 08:30:13 danielk1977 Exp $
*/ */
#include "sqliteInt.h" #include "sqliteInt.h"
@@ -84,6 +84,8 @@ void sqliteDeleteFrom(
sqlite *db; /* Main database structure */ sqlite *db; /* Main database structure */
int openOp; /* Opcode used to open a cursor to the table */ int openOp; /* Opcode used to open a cursor to the table */
int row_triggers_exist = 0;
int oldIdx = -1;
if( pParse->nErr || sqlite_malloc_failed ){ if( pParse->nErr || sqlite_malloc_failed ){
pTabList = 0; pTabList = 0;
@@ -91,6 +93,31 @@ void sqliteDeleteFrom(
} }
db = pParse->db; db = pParse->db;
/* Check for the special case of a VIEW with one or more ON DELETE triggers
* defined
*/
{
Table * pTab;
char * zTab = sqliteTableNameFromToken(pTableName);
if(zTab != 0) {
pTab = sqliteFindTable(pParse->db, zTab);
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);
}
sqliteFree(zTab);
if (row_triggers_exist && pTab->pSelect ) {
/* Just fire VIEW triggers */
sqliteViewTriggers(pParse, pTab, pWhere, OE_Replace, 0);
return;
}
}
}
/* Locate the table which we want to delete. This table has to be /* Locate the table which we want to delete. This table has to be
** put in an IdList structure because some of the subroutines we ** put in an IdList structure because some of the subroutines we
** will be calling are designed to work with multiple tables and expect ** will be calling are designed to work with multiple tables and expect
@@ -102,6 +129,9 @@ void sqliteDeleteFrom(
pTab = pTabList->a[0].pTab; pTab = pTabList->a[0].pTab;
assert( pTab->pSelect==0 ); /* This table is not a view */ assert( pTab->pSelect==0 ); /* This table is not a view */
if (row_triggers_exist)
oldIdx = pParse->nTab++;
/* Resolve the column names in all the expressions. /* Resolve the column names in all the expressions.
*/ */
base = pParse->nTab++; base = pParse->nTab++;
@@ -118,7 +148,10 @@ void sqliteDeleteFrom(
*/ */
v = sqliteGetVdbe(pParse); v = sqliteGetVdbe(pParse);
if( v==0 ) goto delete_from_cleanup; if( v==0 ) goto delete_from_cleanup;
sqliteBeginWriteOperation(pParse); if (row_triggers_exist)
sqliteBeginMultiWriteOperation(pParse);
else
sqliteBeginWriteOperation(pParse);
/* Initialize the counter of the number of rows deleted, if /* Initialize the counter of the number of rows deleted, if
** we are counting rows. ** we are counting rows.
@@ -130,7 +163,7 @@ void sqliteDeleteFrom(
/* Special case: A DELETE without a WHERE clause deletes everything. /* Special case: A DELETE without a WHERE clause deletes everything.
** It is easier just to erase the whole table. ** It is easier just to erase the whole table.
*/ */
if( pWhere==0 ){ if( pWhere==0 && !row_triggers_exist){
if( db->flags & SQLITE_CountRows ){ if( db->flags & SQLITE_CountRows ){
/* If counting rows deleted, just count the total number of /* If counting rows deleted, just count the total number of
** entries in the table. */ ** entries in the table. */
@@ -176,17 +209,66 @@ void sqliteDeleteFrom(
** because deleting an item can change the scan order. ** because deleting an item can change the scan order.
*/ */
sqliteVdbeAddOp(v, OP_ListRewind, 0, 0); sqliteVdbeAddOp(v, OP_ListRewind, 0, 0);
end = sqliteVdbeMakeLabel(v);
if (row_triggers_exist) {
int ii;
addr = sqliteVdbeAddOp(v, OP_ListRead, 0, end);
sqliteVdbeAddOp(v, OP_Dup, 0, 0);
openOp = pTab->isTemp ? OP_OpenAux : OP_Open;
sqliteVdbeAddOp(v, openOp, base, pTab->tnum);
sqliteVdbeAddOp(v, OP_MoveTo, base, 0);
sqliteVdbeAddOp(v, OP_OpenTemp, oldIdx, 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);
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);
}
pParse->nTab = base + 1;
openOp = pTab->isTemp ? OP_OpenWrAux : OP_OpenWrite; openOp = pTab->isTemp ? OP_OpenWrAux : OP_OpenWrite;
sqliteVdbeAddOp(v, openOp, base, pTab->tnum); sqliteVdbeAddOp(v, openOp, base, pTab->tnum);
for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){
sqliteVdbeAddOp(v, openOp, base+i, pIdx->tnum); sqliteVdbeAddOp(v, openOp, pParse->nTab++, pIdx->tnum);
} }
end = sqliteVdbeMakeLabel(v);
addr = sqliteVdbeAddOp(v, OP_ListRead, 0, end); if (!row_triggers_exist)
sqliteGenerateRowDelete(v, pTab, base, 1); addr = sqliteVdbeAddOp(v, OP_ListRead, 0, end);
sqliteGenerateRowDelete(v, pTab, base, pParse->trigStack?0:1);
if (row_triggers_exist) {
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);
sqliteCodeRowTrigger(pParse, TK_DELETE, 0, TK_AFTER, pTab, -1,
oldIdx, (pParse->trigStack)?pParse->trigStack->orconf:OE_Default);
}
sqliteVdbeAddOp(v, OP_Goto, 0, addr); sqliteVdbeAddOp(v, OP_Goto, 0, addr);
sqliteVdbeResolveLabel(v, end); sqliteVdbeResolveLabel(v, end);
sqliteVdbeAddOp(v, OP_ListReset, 0, 0); sqliteVdbeAddOp(v, OP_ListReset, 0, 0);
if (!row_triggers_exist) {
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);
pParse->nTab = base;
}
} }
sqliteEndWriteOperation(pParse); sqliteEndWriteOperation(pParse);

View File

@@ -12,7 +12,7 @@
** This file contains routines used for analyzing expressions and ** This file contains routines used for analyzing expressions and
** for generating VDBE code that evaluates expressions in SQLite. ** for generating VDBE code that evaluates expressions in SQLite.
** **
** $Id: expr.c,v 1.58 2002/04/20 14:24:42 drh Exp $ ** $Id: expr.c,v 1.59 2002/05/15 08:30:13 danielk1977 Exp $
*/ */
#include "sqliteInt.h" #include "sqliteInt.h"
@@ -481,6 +481,33 @@ int sqliteExprResolveIds(
} }
} }
} }
/* If we have not already resolved this *.* expression, then maybe
* it is a new.* or old.* trigger argument reference */
if (cnt == 0 && pParse->trigStack != 0) {
TriggerStack * tt = pParse->trigStack;
int j;
int t = 0;
if (tt->newIdx != -1 && sqliteStrICmp("new", zLeft) == 0) {
pExpr->iTable = tt->newIdx;
cntTab++;
t = 1;
}
if (tt->oldIdx != -1 && sqliteStrICmp("old", zLeft) == 0) {
pExpr->iTable = tt->oldIdx;
cntTab++;
t = 1;
}
if (t)
for(j=0; j<tt->pTab->nCol; j++) {
if( sqliteStrICmp(tt->pTab->aCol[j].zName, zRight)==0 ){
cnt++;
pExpr->iColumn = j;
}
}
}
if( cnt==0 && cntTab==1 && sqliteIsRowid(zRight) ){ if( cnt==0 && cntTab==1 && sqliteIsRowid(zRight) ){
cnt = 1; cnt = 1;
pExpr->iColumn = -1; pExpr->iColumn = -1;

View File

@@ -12,7 +12,7 @@
** This file contains C code routines that are called by the parser ** This file contains C code routines that are called by the parser
** to handle INSERT statements in SQLite. ** to handle INSERT statements in SQLite.
** **
** $Id: insert.c,v 1.52 2002/04/12 10:08:59 drh Exp $ ** $Id: insert.c,v 1.53 2002/05/15 08:30:13 danielk1977 Exp $
*/ */
#include "sqliteInt.h" #include "sqliteInt.h"
@@ -40,7 +40,7 @@ void sqliteInsert(
int onError /* How to handle constraint errors */ int onError /* How to handle constraint errors */
){ ){
Table *pTab; /* The table to insert into */ Table *pTab; /* The table to insert into */
char *zTab; /* Name of the table into which we are inserting */ char *zTab = 0; /* Name of the table into which we are inserting */
int i, j, idx; /* Loop counters */ int i, j, idx; /* Loop counters */
Vdbe *v; /* Generate code into this virtual machine */ Vdbe *v; /* Generate code into this virtual machine */
Index *pIdx; /* For looping over indices of the table */ Index *pIdx; /* For looping over indices of the table */
@@ -53,6 +53,9 @@ void sqliteInsert(
int keyColumn = -1; /* Column that is the INTEGER PRIMARY KEY */ int keyColumn = -1; /* Column that is the INTEGER PRIMARY KEY */
int endOfLoop; /* Label for the end of the insertion loop */ int endOfLoop; /* Label for the end of the insertion loop */
int row_triggers_exist = 0; /* True if there are FOR EACH ROW triggers */
int newIdx = -1;
if( pParse->nErr || sqlite_malloc_failed ) goto insert_cleanup; if( pParse->nErr || sqlite_malloc_failed ) goto insert_cleanup;
db = pParse->db; db = pParse->db;
@@ -60,21 +63,48 @@ void sqliteInsert(
*/ */
zTab = sqliteTableNameFromToken(pTableName); zTab = sqliteTableNameFromToken(pTableName);
if( zTab==0 ) goto insert_cleanup; if( zTab==0 ) goto insert_cleanup;
pTab = sqliteTableNameToTable(pParse, zTab); pTab = sqliteFindTable(pParse->db, zTab);
if( pTab==0 ){
sqliteSetString(&pParse->zErrMsg, "no such table: ", zTab, 0);
pParse->nErr++;
goto insert_cleanup;
}
/* Ensure that:
* (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);
if( pTab->readOnly || (pTab->pSelect && !row_triggers_exist) ){
sqliteSetString(&pParse->zErrMsg,
pTab->pSelect ? "view " : "table ",
zTab,
" may not be modified", 0);
pParse->nErr++;
goto insert_cleanup;
}
sqliteFree(zTab); sqliteFree(zTab);
zTab = 0;
if( pTab==0 ) goto insert_cleanup; if( pTab==0 ) goto insert_cleanup;
assert( pTab->pSelect==0 ); /* This table is not a VIEW */
/* Allocate a VDBE /* Allocate a VDBE
*/ */
v = sqliteGetVdbe(pParse); v = sqliteGetVdbe(pParse);
if( v==0 ) goto insert_cleanup; if( v==0 ) goto insert_cleanup;
if( pSelect ){ if( pSelect || row_triggers_exist ){
sqliteBeginMultiWriteOperation(pParse); sqliteBeginMultiWriteOperation(pParse);
}else{ }else{
sqliteBeginWriteOperation(pParse); sqliteBeginWriteOperation(pParse);
} }
/* if there are row triggers, allocate a temp table for new.* references. */
if (row_triggers_exist)
newIdx = pParse->nTab++;
/* Figure out how many columns of data are supplied. If the data /* Figure out how many columns of data are supplied. If the data
** is coming from a SELECT statement, then this step has to generate ** is coming from a SELECT statement, then this step has to generate
** all the code to implement the SELECT statement and leave the data ** all the code to implement the SELECT statement and leave the data
@@ -173,25 +203,29 @@ void sqliteInsert(
keyColumn = pTab->iPKey; keyColumn = pTab->iPKey;
} }
/* Open cursors into the table that is received the new data and /* Open the temp table for FOR EACH ROW triggers */
** all indices of that table. if (row_triggers_exist)
*/ sqliteVdbeAddOp(v, OP_OpenTemp, newIdx, 0);
base = pParse->nTab;
openOp = pTab->isTemp ? OP_OpenWrAux : OP_OpenWrite;
sqliteVdbeAddOp(v, openOp, base, pTab->tnum);
sqliteVdbeChangeP3(v, -1, pTab->zName, P3_STATIC);
for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){
sqliteVdbeAddOp(v, openOp, idx+base, pIdx->tnum);
sqliteVdbeChangeP3(v, -1, pIdx->zName, P3_STATIC);
}
pParse->nTab += idx;
/* Initialize the count of rows to be inserted /* Initialize the count of rows to be inserted
*/ */
if( db->flags & SQLITE_CountRows ){ if( db->flags & SQLITE_CountRows && !pParse->trigStack){
sqliteVdbeAddOp(v, OP_Integer, 0, 0); /* Initialize the row count */ sqliteVdbeAddOp(v, OP_Integer, 0, 0); /* Initialize the row count */
} }
/* Open tables and indices if there are no row triggers */
if (!row_triggers_exist) {
base = pParse->nTab;
openOp = pTab->isTemp ? OP_OpenWrAux : OP_OpenWrite;
sqliteVdbeAddOp(v, openOp, base, pTab->tnum);
sqliteVdbeChangeP3(v, -1, pTab->zName, P3_STATIC);
for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){
sqliteVdbeAddOp(v, openOp, idx+base, pIdx->tnum);
sqliteVdbeChangeP3(v, -1, pIdx->zName, P3_STATIC);
}
pParse->nTab += idx;
}
/* If the data source is a SELECT statement, then we have to create /* If the data source is a SELECT statement, then we have to create
** a loop because there might be multiple rows of data. If the data ** a loop because there might be multiple rows of data. If the data
** source is an expression list, then exactly one row will be inserted ** source is an expression list, then exactly one row will be inserted
@@ -203,91 +237,159 @@ void sqliteInsert(
iCont = sqliteVdbeCurrentAddr(v); iCont = sqliteVdbeCurrentAddr(v);
} }
if (row_triggers_exist) {
/* build the new.* reference row */
sqliteVdbeAddOp(v, OP_Integer, 13, 0);
for(i=0; i<pTab->nCol; i++){
if( pColumn==0 ){
j = i;
}else{
for(j=0; j<pColumn->nId; j++){
if( pColumn->a[j].idx==i ) break;
}
}
if( pColumn && j>=pColumn->nId ){
sqliteVdbeAddOp(v, OP_String, 0, 0);
sqliteVdbeChangeP3(v, -1, pTab->aCol[i].zDflt, P3_STATIC);
}else if( srcTab>=0 ){
sqliteVdbeAddOp(v, OP_Column, srcTab, j);
}else{
sqliteExprCode(pParse, pList->a[j].pExpr);
}
}
sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0);
sqliteVdbeAddOp(v, OP_PutIntKey, newIdx, 0);
sqliteVdbeAddOp(v, OP_Rewind, newIdx, 0);
/* Fire BEFORE triggers */
if (
sqliteCodeRowTrigger(pParse, TK_INSERT, 0, TK_BEFORE, pTab, newIdx, -1,
onError)
) goto insert_cleanup;
/* Open the tables and indices for the INSERT */
if (!pTab->pSelect) {
base = pParse->nTab;
openOp = pTab->isTemp ? OP_OpenWrAux : OP_OpenWrite;
sqliteVdbeAddOp(v, openOp, base, pTab->tnum);
sqliteVdbeChangeP3(v, -1, pTab->zName, P3_STATIC);
for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){
sqliteVdbeAddOp(v, openOp, 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 /* Push the record number for the new entry onto the stack. The
** record number is a randomly generate integer created by NewRecno ** record number is a randomly generate integer created by NewRecno
** except when the table has an INTEGER PRIMARY KEY column, in which ** except when the table has an INTEGER PRIMARY KEY column, in which
** case the record number is the same as that column. ** case the record number is the same as that column.
*/ */
if( keyColumn>=0 ){ if (!pTab->pSelect) {
if( srcTab>=0 ){ if( keyColumn>=0 ){
sqliteVdbeAddOp(v, OP_Column, srcTab, keyColumn); if( srcTab>=0 ){
}else{ sqliteVdbeAddOp(v, OP_Column, srcTab, keyColumn);
int addr; }else{
sqliteExprCode(pParse, pList->a[keyColumn].pExpr); int addr;
sqliteExprCode(pParse, pList->a[keyColumn].pExpr);
/* If the PRIMARY KEY expression is NULL, then use OP_NewRecno /* If the PRIMARY KEY expression is NULL, then use OP_NewRecno
** to generate a unique primary key value. ** to generate a unique primary key value.
*/ */
addr = sqliteVdbeAddOp(v, OP_Dup, 0, 1); addr = sqliteVdbeAddOp(v, OP_Dup, 0, 1);
sqliteVdbeAddOp(v, OP_NotNull, 0, addr+4); sqliteVdbeAddOp(v, OP_NotNull, 0, addr+4);
sqliteVdbeAddOp(v, OP_Pop, 1, 0); sqliteVdbeAddOp(v, OP_Pop, 1, 0);
sqliteVdbeAddOp(v, OP_NewRecno, base, 0);
}
sqliteVdbeAddOp(v, OP_MustBeInt, 0, 0);
}else{
sqliteVdbeAddOp(v, OP_NewRecno, base, 0); sqliteVdbeAddOp(v, OP_NewRecno, base, 0);
} }
sqliteVdbeAddOp(v, OP_MustBeInt, 0, 0);
}else{
sqliteVdbeAddOp(v, OP_NewRecno, base, 0);
}
/* Push onto the stack, data for all columns of the new entry, beginning /* Push onto the stack, data for all columns of the new entry, beginning
** with the first column. ** with the first column.
*/ */
for(i=0; i<pTab->nCol; i++){ for(i=0; i<pTab->nCol; i++){
if( i==pTab->iPKey ){ if( i==pTab->iPKey ){
/* The value of the INTEGER PRIMARY KEY column is always a NULL. /* The value of the INTEGER PRIMARY KEY column is always a NULL.
** Whenever this column is read, the record number will be substituted ** Whenever this column is read, the record number will be substituted
** in its place. So will fill this column with a NULL to avoid ** in its place. So will fill this column with a NULL to avoid
** taking up data space with information that will never be used. */ ** taking up data space with information that will never be used. */
sqliteVdbeAddOp(v, OP_String, 0, 0); sqliteVdbeAddOp(v, OP_String, 0, 0);
continue; continue;
} }
if( pColumn==0 ){ if( pColumn==0 ){
j = i; j = i;
}else{ }else{
for(j=0; j<pColumn->nId; j++){ for(j=0; j<pColumn->nId; j++){
if( pColumn->a[j].idx==i ) break; if( pColumn->a[j].idx==i ) break;
}
}
if( pColumn && j>=pColumn->nId ){
sqliteVdbeAddOp(v, OP_String, 0, 0);
sqliteVdbeChangeP3(v, -1, pTab->aCol[i].zDflt, P3_STATIC);
}else if( srcTab>=0 ){
sqliteVdbeAddOp(v, OP_Column, srcTab, j);
}else{
sqliteExprCode(pParse, pList->a[j].pExpr);
} }
} }
if( pColumn && j>=pColumn->nId ){
sqliteVdbeAddOp(v, OP_String, 0, 0); /* Generate code to check constraints and generate index keys and
sqliteVdbeChangeP3(v, -1, pTab->aCol[i].zDflt, P3_STATIC); ** do the insertion.
}else if( srcTab>=0 ){ */
sqliteVdbeAddOp(v, OP_Column, srcTab, j); endOfLoop = sqliteVdbeMakeLabel(v);
}else{ sqliteGenerateConstraintChecks(pParse, pTab, base, 0,0,0,onError,endOfLoop);
sqliteExprCode(pParse, pList->a[j].pExpr); sqliteCompleteInsertion(pParse, pTab, base, 0,0,0);
/* Update the count of rows that are inserted
*/
if( (db->flags & SQLITE_CountRows)!=0 && !pParse->trigStack){
sqliteVdbeAddOp(v, OP_AddImm, 1, 0);
} }
} }
/* Generate code to check constraints and generate index keys and if (row_triggers_exist) {
** do the insertion. /* Close all tables opened */
*/ if (!pTab->pSelect) {
endOfLoop = sqliteVdbeMakeLabel(v); sqliteVdbeAddOp(v, OP_Close, base, 0);
sqliteGenerateConstraintChecks(pParse, pTab, base, 0,0,0, onError, endOfLoop); for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){
sqliteCompleteInsertion(pParse, pTab, base, 0,0,0); sqliteVdbeAddOp(v, OP_Close, idx+base, 0);
}
}
/* Update the count of rows that are inserted /* Code AFTER triggers */
*/ if (
if( (db->flags & SQLITE_CountRows)!=0 ){ sqliteCodeRowTrigger(pParse, TK_INSERT, 0, TK_AFTER, pTab, newIdx, -1,
sqliteVdbeAddOp(v, OP_AddImm, 1, 0); onError)
) goto insert_cleanup;
} }
/* The bottom of the loop, if the data source is a SELECT statement /* The bottom of the loop, if the data source is a SELECT statement
*/ */
sqliteVdbeResolveLabel(v, endOfLoop); sqliteVdbeResolveLabel(v, endOfLoop);
if( srcTab>=0 ){ if( srcTab>=0 ){
sqliteVdbeAddOp(v, OP_Next, srcTab, iCont); sqliteVdbeAddOp(v, OP_Next, srcTab, iCont);
sqliteVdbeResolveLabel(v, iBreak); sqliteVdbeResolveLabel(v, iBreak);
sqliteVdbeAddOp(v, OP_Close, srcTab, 0); sqliteVdbeAddOp(v, OP_Close, srcTab, 0);
} }
sqliteVdbeAddOp(v, OP_Close, base, 0);
for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){ if (!row_triggers_exist) {
sqliteVdbeAddOp(v, OP_Close, idx+base, 0); /* Close all tables opened */
sqliteVdbeAddOp(v, OP_Close, base, 0);
for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){
sqliteVdbeAddOp(v, OP_Close, idx+base, 0);
}
} }
sqliteEndWriteOperation(pParse); sqliteEndWriteOperation(pParse);
/* /*
** Return the number of rows inserted. ** Return the number of rows inserted.
*/ */
if( db->flags & SQLITE_CountRows ){ if( db->flags & SQLITE_CountRows && !pParse->trigStack ){
sqliteVdbeAddOp(v, OP_ColumnCount, 1, 0); sqliteVdbeAddOp(v, OP_ColumnCount, 1, 0);
sqliteVdbeAddOp(v, OP_ColumnName, 0, 0); sqliteVdbeAddOp(v, OP_ColumnName, 0, 0);
sqliteVdbeChangeP3(v, -1, "rows inserted", P3_STATIC); sqliteVdbeChangeP3(v, -1, "rows inserted", P3_STATIC);
@@ -297,6 +399,7 @@ void sqliteInsert(
insert_cleanup: insert_cleanup:
if( pList ) sqliteExprListDelete(pList); if( pList ) sqliteExprListDelete(pList);
if( pSelect ) sqliteSelectDelete(pSelect); if( pSelect ) sqliteSelectDelete(pSelect);
if ( zTab ) sqliteFree(zTab);
sqliteIdListDelete(pColumn); sqliteIdListDelete(pColumn);
} }
@@ -573,7 +676,7 @@ void sqliteCompleteInsertion(
sqliteVdbeAddOp(v, OP_IdxPut, base+i+1, 0); sqliteVdbeAddOp(v, OP_IdxPut, base+i+1, 0);
} }
sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0); sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0);
sqliteVdbeAddOp(v, OP_PutIntKey, base, 1); sqliteVdbeAddOp(v, OP_PutIntKey, base, pParse->trigStack?0:1);
if( isUpdate && recnoChng ){ if( isUpdate && recnoChng ){
sqliteVdbeAddOp(v, OP_Pop, 1, 0); sqliteVdbeAddOp(v, OP_Pop, 1, 0);
} }

View File

@@ -14,7 +14,7 @@
** other files are for internal use by SQLite and should not be ** other files are for internal use by SQLite and should not be
** accessed by users of the library. ** accessed by users of the library.
** **
** $Id: main.c,v 1.71 2002/05/10 13:14:07 drh Exp $ ** $Id: main.c,v 1.72 2002/05/15 08:30:13 danielk1977 Exp $
*/ */
#include "sqliteInt.h" #include "sqliteInt.h"
#include "os.h" #include "os.h"
@@ -326,6 +326,8 @@ sqlite *sqlite_open(const char *zFilename, int mode, char **pzErrMsg){
if( db==0 ) goto no_mem_on_open; if( db==0 ) goto no_mem_on_open;
sqliteHashInit(&db->tblHash, SQLITE_HASH_STRING, 0); sqliteHashInit(&db->tblHash, SQLITE_HASH_STRING, 0);
sqliteHashInit(&db->idxHash, SQLITE_HASH_STRING, 0); sqliteHashInit(&db->idxHash, SQLITE_HASH_STRING, 0);
sqliteHashInit(&db->trigHash, SQLITE_HASH_STRING, 0);
sqliteHashInit(&db->trigDrop, SQLITE_HASH_STRING, 0);
sqliteHashInit(&db->tblDrop, SQLITE_HASH_POINTER, 0); sqliteHashInit(&db->tblDrop, SQLITE_HASH_POINTER, 0);
sqliteHashInit(&db->idxDrop, SQLITE_HASH_POINTER, 0); sqliteHashInit(&db->idxDrop, SQLITE_HASH_POINTER, 0);
sqliteHashInit(&db->aFunc, SQLITE_HASH_STRING, 1); sqliteHashInit(&db->aFunc, SQLITE_HASH_STRING, 1);
@@ -383,11 +385,29 @@ no_mem_on_open:
static void clearHashTable(sqlite *db, int preserveTemps){ static void clearHashTable(sqlite *db, int preserveTemps){
HashElem *pElem; HashElem *pElem;
Hash temp1; Hash temp1;
Hash temp2;
assert( sqliteHashFirst(&db->tblDrop)==0 ); /* There can not be uncommitted */ assert( sqliteHashFirst(&db->tblDrop)==0 ); /* There can not be uncommitted */
assert( sqliteHashFirst(&db->idxDrop)==0 ); /* DROP TABLEs or DROP INDEXs */ assert( sqliteHashFirst(&db->idxDrop)==0 ); /* DROP TABLEs or DROP INDEXs */
temp1 = db->tblHash; temp1 = db->tblHash;
sqliteHashInit(&db->tblHash, SQLITE_HASH_STRING, 0); temp2 = db->trigHash;
sqliteHashInit(&db->trigHash, SQLITE_HASH_STRING, 0);
sqliteHashClear(&db->idxHash); sqliteHashClear(&db->idxHash);
for (pElem=sqliteHashFirst(&temp2); pElem; pElem=sqliteHashNext(pElem)){
Trigger * pTrigger = sqliteHashData(pElem);
Table *pTab = sqliteFindTable(db, pTrigger->table);
assert(pTab);
if (pTab->isTemp) {
sqliteHashInsert(&db->trigHash, pTrigger->name, strlen(pTrigger->name),
pTrigger);
} else {
sqliteDeleteTrigger(pTrigger);
}
}
sqliteHashClear(&temp2);
sqliteHashInit(&db->tblHash, SQLITE_HASH_STRING, 0);
for(pElem=sqliteHashFirst(&temp1); pElem; pElem=sqliteHashNext(pElem)){ for(pElem=sqliteHashFirst(&temp1); pElem; pElem=sqliteHashNext(pElem)){
Table *pTab = sqliteHashData(pElem); Table *pTab = sqliteHashData(pElem);
if( preserveTemps && pTab->isTemp ){ if( preserveTemps && pTab->isTemp ){
@@ -413,6 +433,7 @@ static void clearHashTable(sqlite *db, int preserveTemps){
} }
} }
sqliteHashClear(&temp1); sqliteHashClear(&temp1);
db->flags &= ~SQLITE_Initialized; db->flags &= ~SQLITE_Initialized;
} }
@@ -458,6 +479,7 @@ void sqlite_close(sqlite *db){
*/ */
int sqlite_complete(const char *zSql){ int sqlite_complete(const char *zSql){
int isComplete = 0; int isComplete = 0;
int seenCreate = 0;
while( *zSql ){ while( *zSql ){
switch( *zSql ){ switch( *zSql ){
case ';': { case ';': {
@@ -501,6 +523,16 @@ int sqlite_complete(const char *zSql){
break; break;
} }
default: { default: {
if (seenCreate && !sqliteStrNICmp(zSql, "trigger", 7))
while (sqliteStrNICmp(zSql, "end", 3))
if (!*++zSql) return 0;
if (!sqliteStrNICmp(zSql, "create", 6)) {
zSql = zSql + 5;
seenCreate = 1;
} else
seenCreate = 0;
isComplete = 0; isComplete = 0;
break; break;
} }

View File

@@ -14,7 +14,7 @@
** the parser. Lemon will also generate a header file containing ** the parser. Lemon will also generate a header file containing
** numeric codes for all of the tokens. ** numeric codes for all of the tokens.
** **
** @(#) $Id: parse.y,v 1.63 2002/05/08 21:46:15 drh Exp $ ** @(#) $Id: parse.y,v 1.64 2002/05/15 08:30:14 danielk1977 Exp $
*/ */
%token_prefix TK_ %token_prefix TK_
%token_type {Token} %token_type {Token}
@@ -33,6 +33,11 @@
** A structure for holding two integers ** A structure for holding two integers
*/ */
struct twoint { int a,b; }; struct twoint { int a,b; };
/*
** A structure for holding an integer and an IdList
*/
struct int_idlist { int a; IdList * b; };
} }
// These are extra tokens used by the lexer but never seen by the // These are extra tokens used by the lexer but never seen by the
@@ -628,3 +633,63 @@ number(A) ::= INTEGER(X). {A = X;}
number(A) ::= FLOAT(X). {A = X;} number(A) ::= FLOAT(X). {A = X;}
plus_opt ::= PLUS. plus_opt ::= PLUS.
plus_opt ::= . plus_opt ::= .
//////////////////////////// The CREATE TRIGGER command /////////////////////
cmd ::= CREATE(A) TRIGGER ids(B) trigger_time(C) trigger_event(D) ON ids(E)
foreach_clause(F) when_clause(G)
BEGIN trigger_cmd_list(S) END(Z). {
sqliteCreateTrigger(pParse, &B, C, D.a, D.b, &E, F, G, S,
A.z, (int)(Z.z - A.z) + Z.n );
}
%type trigger_time {int}
trigger_time(A) ::= BEFORE. { A = TK_BEFORE; }
trigger_time(A) ::= AFTER. { A = TK_AFTER; }
trigger_time(A) ::= INSTEAD OF. { A = TK_INSTEAD;}
trigger_time(A) ::= . { A = TK_BEFORE; }
%type trigger_event {struct int_idlist}
trigger_event(A) ::= DELETE. { A.a = TK_DELETE; A.b = 0; }
trigger_event(A) ::= INSERT. { A.a = TK_INSERT; A.b = 0; }
trigger_event(A) ::= UPDATE. { A.a = TK_UPDATE; A.b = 0;}
trigger_event(A) ::= UPDATE OF inscollist(X). {A.a = TK_UPDATE; A.b = X; }
%type foreach_clause {int}
foreach_clause(A) ::= . { A = TK_ROW; }
foreach_clause(A) ::= FOR EACH ROW. { A = TK_ROW; }
foreach_clause(A) ::= FOR EACH STATEMENT. { A = TK_STATEMENT; }
%type when_clause {Expr *}
when_clause(A) ::= . { A = 0; }
when_clause(A) ::= WHEN expr(X). { A = X; }
%type trigger_cmd_list {TriggerStep *}
trigger_cmd_list(A) ::= trigger_cmd(X) SEMI trigger_cmd_list(Y). {
X->pNext = Y ; A = X; }
trigger_cmd_list(A) ::= . { A = 0; }
%type trigger_cmd {TriggerStep *}
// UPDATE
trigger_cmd(A) ::= UPDATE orconf(R) ids(X) SET setlist(Y) where_opt(Z).
{ A = sqliteTriggerUpdateStep(&X, Y, Z, R); }
// INSERT
trigger_cmd(A) ::= INSERT orconf(R) INTO ids(X) inscollist_opt(F)
VALUES LP itemlist(Y) RP.
{A = sqliteTriggerInsertStep(&X, F, Y, 0, R);}
trigger_cmd(A) ::= INSERT orconf(R) INTO ids(X) inscollist_opt(F) select(S).
{A = sqliteTriggerInsertStep(&X, F, 0, S, R);}
// DELETE
trigger_cmd(A) ::= DELETE FROM ids(X) where_opt(Y).
{A = sqliteTriggerDeleteStep(&X, Y);}
// SELECT
trigger_cmd(A) ::= select(X). {A = sqliteTriggerSelectStep(X); }
//////////////////////// DROP TRIGGER statement //////////////////////////////
cmd ::= DROP TRIGGER ids(X). {
sqliteDropTrigger(pParse,&X,0);
}

View File

@@ -11,7 +11,7 @@
************************************************************************* *************************************************************************
** Internal interface definitions for SQLite. ** Internal interface definitions for SQLite.
** **
** @(#) $Id: sqliteInt.h,v 1.107 2002/05/10 13:14:07 drh Exp $ ** @(#) $Id: sqliteInt.h,v 1.108 2002/05/15 08:30:14 danielk1977 Exp $
*/ */
#include "sqlite.h" #include "sqlite.h"
#include "hash.h" #include "hash.h"
@@ -144,6 +144,9 @@ typedef struct WhereLevel WhereLevel;
typedef struct Select Select; typedef struct Select Select;
typedef struct AggExpr AggExpr; typedef struct AggExpr AggExpr;
typedef struct FuncDef FuncDef; typedef struct FuncDef FuncDef;
typedef struct Trigger Trigger;
typedef struct TriggerStep TriggerStep;
typedef struct TriggerStack TriggerStack;
/* /*
** Each database is an instance of the following structure ** Each database is an instance of the following structure
@@ -170,6 +173,9 @@ struct sqlite {
int magic; /* Magic number for detect library misuse */ int magic; /* Magic number for detect library misuse */
int nChange; /* Number of rows changed */ int nChange; /* Number of rows changed */
int recursionDepth; /* Number of nested calls to sqlite_exec() */ int recursionDepth; /* Number of nested calls to sqlite_exec() */
Hash trigHash; /* All triggers indexed by name */
Hash trigDrop; /* Uncommited dropped triggers */
}; };
/* /*
@@ -270,6 +276,8 @@ struct Table {
u8 isTransient; /* True if automatically deleted when VDBE finishes */ u8 isTransient; /* True if automatically deleted when VDBE finishes */
u8 hasPrimKey; /* True if there exists a primary key */ u8 hasPrimKey; /* True if there exists a primary key */
u8 keyConf; /* What to do in case of uniqueness conflict on iPKey */ u8 keyConf; /* What to do in case of uniqueness conflict on iPKey */
Trigger *pTrigger; /* List of SQL triggers on this table */
}; };
/* /*
@@ -550,8 +558,64 @@ struct Parse {
** while generating expressions. Normally false */ ** while generating expressions. Normally false */
int schemaVerified; /* True if an OP_VerifySchema has been coded someplace int schemaVerified; /* True if an OP_VerifySchema has been coded someplace
** other than after an OP_Transaction */ ** other than after an OP_Transaction */
TriggerStack * trigStack;
}; };
struct TriggerStack {
Trigger * pTrigger;
Table * pTab; /* Table that triggers are currently being coded as */
int newIdx; /* Index of "new" temp table */
int oldIdx; /* Index of "old" temp table */
int orconf; /* Current orconf policy */
struct TriggerStack * pNext;
};
struct TriggerStep {
int op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT, TK_SELECT */
int orconf;
Select * pSelect; /* Valid for SELECT and sometimes
INSERT steps (when pExprList == 0) */
Token target; /* Valid for DELETE, UPDATE, INSERT steps */
Expr * pWhere; /* Valid for DELETE, UPDATE steps */
ExprList * pExprList; /* Valid for UPDATE statements and sometimes
INSERT steps (when pSelect == 0) */
IdList *pIdList; /* Valid for INSERT statements only */
TriggerStep * pNext; /* Next in the link-list */
};
struct Trigger {
char * name; /* The name of the trigger */
char * table; /* The table or view to which the trigger applies */
int op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT */
int tr_tm; /* One of TK_BEFORE, TK_AFTER, TK_INSTEAD */
Expr * pWhen; /* The WHEN clause of the expresion (may be NULL) */
IdList * pColumns; /* If this is an UPDATE OF <column-list> trigger,
the column names are stored in this list */
int foreach; /* One of TK_ROW or TK_STATEMENT */
TriggerStep * step_list; /* Link list of trigger program steps */
char * strings; /* pointer to the allocation of Token strings */
Trigger * pNext; /* Next trigger associated with the table */
int isCommit;
};
TriggerStep * sqliteTriggerSelectStep(Select *);
TriggerStep * sqliteTriggerInsertStep(Token *, IdList *, ExprList *,
Select *, int);
TriggerStep * sqliteTriggerUpdateStep(Token *, ExprList *, Expr *, int);
TriggerStep * sqliteTriggerDeleteStep(Token *, Expr *);
extern int always_code_trigger_setup;
void sqliteCreateTrigger(Parse * ,Token *, int, int, IdList *, Token *, int, Expr *, TriggerStep *, char const *,int);
void sqliteDropTrigger(Parse *, Token *, int);
int sqliteTriggersExist( Parse * , Trigger * , int , int , int, ExprList * );
int sqliteCodeRowTrigger( Parse * pParse, int op, ExprList *, int tr_tm, Table * tbl, int newTable, int oldTable, int onError);
void sqliteViewTriggers(Parse *, Table *, Expr *, int, ExprList *);
/* /*
** Internal function prototypes ** Internal function prototypes
*/ */
@@ -662,3 +726,5 @@ void sqliteRegisterBuildinFunctions(sqlite*);
int sqliteSafetyOn(sqlite*); int sqliteSafetyOn(sqlite*);
int sqliteSafetyOff(sqlite*); int sqliteSafetyOff(sqlite*);
int sqliteSafetyCheck(sqlite*); int sqliteSafetyCheck(sqlite*);
void changeCookie(sqlite *);

View File

@@ -15,7 +15,7 @@
** individual tokens and sends those tokens one-by-one over to the ** individual tokens and sends those tokens one-by-one over to the
** parser for analysis. ** parser for analysis.
** **
** $Id: tokenize.c,v 1.40 2002/03/24 13:13:29 drh Exp $ ** $Id: tokenize.c,v 1.41 2002/05/15 08:30:14 danielk1977 Exp $
*/ */
#include "sqliteInt.h" #include "sqliteInt.h"
#include "os.h" #include "os.h"
@@ -39,10 +39,12 @@ struct Keyword {
*/ */
static Keyword aKeywordTable[] = { static Keyword aKeywordTable[] = {
{ "ABORT", 0, TK_ABORT, 0 }, { "ABORT", 0, TK_ABORT, 0 },
{ "AFTER", 0, TK_AFTER, 0 },
{ "ALL", 0, TK_ALL, 0 }, { "ALL", 0, TK_ALL, 0 },
{ "AND", 0, TK_AND, 0 }, { "AND", 0, TK_AND, 0 },
{ "AS", 0, TK_AS, 0 }, { "AS", 0, TK_AS, 0 },
{ "ASC", 0, TK_ASC, 0 }, { "ASC", 0, TK_ASC, 0 },
{ "BEFORE", 0, TK_BEFORE, 0 },
{ "BEGIN", 0, TK_BEGIN, 0 }, { "BEGIN", 0, TK_BEGIN, 0 },
{ "BETWEEN", 0, TK_BETWEEN, 0 }, { "BETWEEN", 0, TK_BETWEEN, 0 },
{ "BY", 0, TK_BY, 0 }, { "BY", 0, TK_BY, 0 },
@@ -61,10 +63,12 @@ static Keyword aKeywordTable[] = {
{ "DISTINCT", 0, TK_DISTINCT, 0 }, { "DISTINCT", 0, TK_DISTINCT, 0 },
{ "DROP", 0, TK_DROP, 0 }, { "DROP", 0, TK_DROP, 0 },
{ "END", 0, TK_END, 0 }, { "END", 0, TK_END, 0 },
{ "EACH", 0, TK_EACH, 0 },
{ "ELSE", 0, TK_ELSE, 0 }, { "ELSE", 0, TK_ELSE, 0 },
{ "EXCEPT", 0, TK_EXCEPT, 0 }, { "EXCEPT", 0, TK_EXCEPT, 0 },
{ "EXPLAIN", 0, TK_EXPLAIN, 0 }, { "EXPLAIN", 0, TK_EXPLAIN, 0 },
{ "FAIL", 0, TK_FAIL, 0 }, { "FAIL", 0, TK_FAIL, 0 },
{ "FOR", 0, TK_FOR, 0 },
{ "FROM", 0, TK_FROM, 0 }, { "FROM", 0, TK_FROM, 0 },
{ "GLOB", 0, TK_GLOB, 0 }, { "GLOB", 0, TK_GLOB, 0 },
{ "GROUP", 0, TK_GROUP, 0 }, { "GROUP", 0, TK_GROUP, 0 },
@@ -73,6 +77,7 @@ static Keyword aKeywordTable[] = {
{ "IN", 0, TK_IN, 0 }, { "IN", 0, TK_IN, 0 },
{ "INDEX", 0, TK_INDEX, 0 }, { "INDEX", 0, TK_INDEX, 0 },
{ "INSERT", 0, TK_INSERT, 0 }, { "INSERT", 0, TK_INSERT, 0 },
{ "INSTEAD", 0, TK_INSTEAD, 0 },
{ "INTERSECT", 0, TK_INTERSECT, 0 }, { "INTERSECT", 0, TK_INTERSECT, 0 },
{ "INTO", 0, TK_INTO, 0 }, { "INTO", 0, TK_INTO, 0 },
{ "IS", 0, TK_IS, 0 }, { "IS", 0, TK_IS, 0 },
@@ -83,6 +88,7 @@ static Keyword aKeywordTable[] = {
{ "NOT", 0, TK_NOT, 0 }, { "NOT", 0, TK_NOT, 0 },
{ "NOTNULL", 0, TK_NOTNULL, 0 }, { "NOTNULL", 0, TK_NOTNULL, 0 },
{ "NULL", 0, TK_NULL, 0 }, { "NULL", 0, TK_NULL, 0 },
{ "OF", 0, TK_OF, 0 },
{ "OFFSET", 0, TK_OFFSET, 0 }, { "OFFSET", 0, TK_OFFSET, 0 },
{ "ON", 0, TK_ON, 0 }, { "ON", 0, TK_ON, 0 },
{ "OR", 0, TK_OR, 0 }, { "OR", 0, TK_OR, 0 },
@@ -91,6 +97,7 @@ static Keyword aKeywordTable[] = {
{ "PRIMARY", 0, TK_PRIMARY, 0 }, { "PRIMARY", 0, TK_PRIMARY, 0 },
{ "REPLACE", 0, TK_REPLACE, 0 }, { "REPLACE", 0, TK_REPLACE, 0 },
{ "ROLLBACK", 0, TK_ROLLBACK, 0 }, { "ROLLBACK", 0, TK_ROLLBACK, 0 },
{ "ROW", 0, TK_ROW, 0 },
{ "SELECT", 0, TK_SELECT, 0 }, { "SELECT", 0, TK_SELECT, 0 },
{ "SET", 0, TK_SET, 0 }, { "SET", 0, TK_SET, 0 },
{ "TABLE", 0, TK_TABLE, 0 }, { "TABLE", 0, TK_TABLE, 0 },
@@ -98,6 +105,7 @@ static Keyword aKeywordTable[] = {
{ "TEMPORARY", 0, TK_TEMP, 0 }, { "TEMPORARY", 0, TK_TEMP, 0 },
{ "THEN", 0, TK_THEN, 0 }, { "THEN", 0, TK_THEN, 0 },
{ "TRANSACTION", 0, TK_TRANSACTION, 0 }, { "TRANSACTION", 0, TK_TRANSACTION, 0 },
{ "TRIGGER", 0, TK_TRIGGER, 0 },
{ "UNION", 0, TK_UNION, 0 }, { "UNION", 0, TK_UNION, 0 },
{ "UNIQUE", 0, TK_UNIQUE, 0 }, { "UNIQUE", 0, TK_UNIQUE, 0 },
{ "UPDATE", 0, TK_UPDATE, 0 }, { "UPDATE", 0, TK_UPDATE, 0 },

643
src/trigger.c Normal file
View File

@@ -0,0 +1,643 @@
/*
* All copyright on this work is disclaimed by the author.
*
*/
#include "sqliteInt.h"
/*
* This is called by the parser when it sees a CREATE TRIGGER statement
*/
void
sqliteCreateTrigger(
Parse * pParse, /* The parse context of the CREATE TRIGGER statement */
Token * nm, /* The name of the trigger */
int tr_tm, /* One of TK_BEFORE, TK_AFTER */
int op, /* One of TK_INSERT, TK_UPDATE, TK_DELETE */
IdList * cols, /* column list if this is an UPDATE OF trigger */
Token * tbl, /* The name of the table/view the trigger applies to */
int foreach, /* One of TK_ROW or TK_STATEMENT */
Expr * pWhen, /* WHEN clause */
TriggerStep * steps, /* The triggered program */
char const * cc, int len) /* The string data to make persistent */
{
Trigger * nt;
Table * tab;
int offset;
TriggerStep * ss;
/* Check that:
1. the trigger name does not already exist.
2. the table (or view) does exist.
*/
{
char * tmp_str = sqliteStrNDup(nm->z, nm->n);
if (sqliteHashFind(&(pParse->db->trigHash), tmp_str, nm->n + 1)) {
sqliteSetNString(&pParse->zErrMsg, "trigger ", -1,
nm->z, nm->n, " already exists", -1, 0);
sqliteFree(tmp_str);
pParse->nErr++;
goto trigger_cleanup;
}
sqliteFree(tmp_str);
}
{
char * tmp_str = sqliteStrNDup(tbl->z, tbl->n);
tab = sqliteFindTable(pParse->db, tmp_str);
sqliteFree(tmp_str);
if (!tab) {
sqliteSetNString(&pParse->zErrMsg, "no such table: ", -1,
tbl->z, tbl->n, 0);
pParse->nErr++;
goto trigger_cleanup;
}
}
/* Build the Trigger object */
nt = (Trigger *)sqliteMalloc(sizeof(Trigger));
nt->name = sqliteStrNDup(nm->z, nm->n);
nt->table = sqliteStrNDup(tbl->z, tbl->n);
nt->op = op;
nt->tr_tm = tr_tm;
nt->pWhen = pWhen;
nt->pColumns = cols;
nt->foreach = foreach;
nt->step_list = steps;
nt->isCommit = 0;
nt->strings = sqliteStrNDup(cc, len);
offset = (int)(nt->strings - cc);
sqliteExprMoveStrings(nt->pWhen, offset);
ss = nt->step_list;
while (ss) {
sqliteSelectMoveStrings(ss->pSelect, offset);
if (ss->target.z) ss->target.z += offset;
sqliteExprMoveStrings(ss->pWhere, offset);
sqliteExprListMoveStrings(ss->pExprList, offset);
ss = ss->pNext;
}
/* if we are not initializing, and this trigger is not on a TEMP table,
build the sqlite_master entry */
if (!pParse->initFlag && !tab->isTemp) {
/* Make an entry in the sqlite_master table */
sqliteBeginWriteOperation(pParse);
sqliteVdbeAddOp(pParse->pVdbe, OP_OpenWrite, 0, 2);
sqliteVdbeChangeP3(pParse->pVdbe, -1, MASTER_NAME, P3_STATIC);
sqliteVdbeAddOp(pParse->pVdbe, OP_NewRecno, 0, 0);
sqliteVdbeAddOp(pParse->pVdbe, OP_String, 0, 0);
sqliteVdbeChangeP3(pParse->pVdbe, -1, "trigger", P3_STATIC);
sqliteVdbeAddOp(pParse->pVdbe, OP_String, 0, 0);
sqliteVdbeChangeP3(pParse->pVdbe, -1, nt->name, 0);
sqliteVdbeAddOp(pParse->pVdbe, OP_String, 0, 0);
sqliteVdbeChangeP3(pParse->pVdbe, -1, nt->table, 0);
sqliteVdbeAddOp(pParse->pVdbe, OP_Integer, 0, 0);
sqliteVdbeAddOp(pParse->pVdbe, OP_String, 0, 0);
sqliteVdbeChangeP3(pParse->pVdbe, -1, nt->strings, 0);
sqliteVdbeAddOp(pParse->pVdbe, OP_MakeRecord, 5, 0);
sqliteVdbeAddOp(pParse->pVdbe, OP_PutIntKey, 0, 1);
/* Change the cookie, since the schema is changed */
changeCookie(pParse->db);
sqliteVdbeAddOp(pParse->pVdbe, OP_Integer, pParse->db->next_cookie, 0);
sqliteVdbeAddOp(pParse->pVdbe, OP_SetCookie, 0, 0);
sqliteVdbeAddOp(pParse->pVdbe, OP_Close, 0, 0);
sqliteEndWriteOperation(pParse);
}
if (!pParse->explain) {
/* Stick it in the hash-table */
sqliteHashInsert(&(pParse->db->trigHash), nt->name, nm->n + 1, nt);
/* Attach it to the table object */
nt->pNext = tab->pTrigger;
tab->pTrigger = nt;
return;
} else {
sqliteFree(nt->strings);
sqliteFree(nt->name);
sqliteFree(nt->table);
sqliteFree(nt);
}
trigger_cleanup:
sqliteIdListDelete(cols);
sqliteExprDelete(pWhen);
{
TriggerStep * pp;
TriggerStep * nn;
pp = steps;
while (pp) {
nn = pp->pNext;
sqliteExprDelete(pp->pWhere);
sqliteExprListDelete(pp->pExprList);
sqliteSelectDelete(pp->pSelect);
sqliteIdListDelete(pp->pIdList);
sqliteFree(pp);
pp = nn;
}
}
}
TriggerStep *
sqliteTriggerSelectStep(Select * s)
{
TriggerStep * tt = sqliteMalloc(sizeof(TriggerStep));
tt->op = TK_SELECT;
tt->pSelect = s;
tt->orconf = OE_Default;
return tt;
}
TriggerStep *
sqliteTriggerInsertStep(Token * tbl, IdList * col, ExprList * val, Select * s, int orconf)
{
TriggerStep * tt = sqliteMalloc(sizeof(TriggerStep));
assert(val == 0 || s == 0);
assert(val != 0 || s != 0);
tt->op = TK_INSERT;
tt->pSelect = s;
tt->target = *tbl;
tt->pIdList = col;
tt->pExprList = val;
tt->orconf = orconf;
return tt;
}
TriggerStep *
sqliteTriggerUpdateStep(Token * tbl, ExprList * val, Expr * w, int orconf)
{
TriggerStep * tt = sqliteMalloc(sizeof(TriggerStep));
tt->op = TK_UPDATE;
tt->target = *tbl;
tt->pExprList = val;
tt->pWhere = w;
tt->orconf = orconf;
return tt;
}
TriggerStep *
sqliteTriggerDeleteStep(Token * tbl, Expr * w)
{
TriggerStep * tt = sqliteMalloc(sizeof(TriggerStep));
tt->op = TK_DELETE;
tt->target = *tbl;
tt->pWhere = w;
tt->orconf = OE_Default;
return tt;
}
/* This does a recursive delete of the trigger structure */
void sqliteDeleteTrigger(Trigger * tt)
{
TriggerStep * ts, * tc;
ts = tt->step_list;
while (ts) {
tc = ts;
ts = ts->pNext;
sqliteExprDelete(tc->pWhere);
sqliteExprListDelete(tc->pExprList);
sqliteSelectDelete(tc->pSelect);
sqliteIdListDelete(tc->pIdList);
sqliteFree(tc);
}
sqliteFree(tt->name);
sqliteFree(tt->table);
sqliteExprDelete(tt->pWhen);
sqliteIdListDelete(tt->pColumns);
sqliteFree(tt->strings);
sqliteFree(tt);
}
/*
* "nested" is true if this is begin called as the result of a DROP TABLE
*/
void sqliteDropTrigger(Parse *pParse, Token * trigname, int nested)
{
char * tmp_name;
Trigger * trig;
Table * tbl;
tmp_name = sqliteStrNDup(trigname->z, trigname->n);
/* ensure that the trigger being dropped exists */
trig = sqliteHashFind(&(pParse->db->trigHash), tmp_name, trigname->n + 1);
if (!trig) {
sqliteSetNString(&pParse->zErrMsg, "no such trigger: ", -1,
tmp_name, -1, 0);
sqliteFree(tmp_name);
return;
}
/*
* If this is not an "explain", do the following:
* 1. Remove the trigger from its associated table structure
* 2. Move the trigger from the trigHash hash to trigDrop
*/
if (!pParse->explain) {
/* 1 */
tbl = sqliteFindTable(pParse->db, trig->table);
assert(tbl);
if (tbl->pTrigger == trig)
tbl->pTrigger = trig->pNext;
else {
Trigger * cc = tbl->pTrigger;
while (cc) {
if (cc->pNext == trig) {
cc->pNext = cc->pNext->pNext;
break;
}
cc = cc->pNext;
}
assert(cc);
}
/* 2 */
sqliteHashInsert(&(pParse->db->trigHash), tmp_name,
trigname->n + 1, NULL);
sqliteHashInsert(&(pParse->db->trigDrop), trig->name,
trigname->n + 1, trig);
}
/* Unless this is a trigger on a TEMP TABLE, generate code to destroy the
* database record of the trigger */
if (!tbl->isTemp) {
int base;
static VdbeOp dropTrigger[] = {
{ OP_OpenWrite, 0, 2, MASTER_NAME},
{ OP_Rewind, 0, ADDR(9), 0},
{ OP_String, 0, 0, 0}, /* 2 */
{ OP_MemStore, 1, 1, 0},
{ OP_MemLoad, 1, 0, 0}, /* 4 */
{ OP_Column, 0, 1, 0},
{ OP_Ne, 0, ADDR(8), 0},
{ OP_Delete, 0, 0, 0},
{ OP_Next, 0, ADDR(4), 0}, /* 8 */
{ OP_Integer, 0, 0, 0}, /* 9 */
{ OP_SetCookie, 0, 0, 0},
{ OP_Close, 0, 0, 0},
};
if (!nested)
sqliteBeginWriteOperation(pParse);
base = sqliteVdbeAddOpList(pParse->pVdbe,
ArraySize(dropTrigger), dropTrigger);
sqliteVdbeChangeP3(pParse->pVdbe, base+2, tmp_name, 0);
if (!nested)
changeCookie(pParse->db);
sqliteVdbeChangeP1(pParse->pVdbe, base+9, pParse->db->next_cookie);
if (!nested)
sqliteEndWriteOperation(pParse);
}
sqliteFree(tmp_name);
}
static int checkColumnOverLap(IdList * ii, ExprList * ee)
{
int i, e;
if (!ii) return 1;
if (!ee) return 1;
for (i = 0; i < ii->nId; i++)
for (e = 0; e < ee->nExpr; e++)
if (!sqliteStrICmp(ii->a[i].zName, ee->a[e].zName))
return 1;
return 0;
}
/* A global variable that is TRUE if we should always set up temp tables for
* for triggers, even if there are no triggers to code. This is used to test
* how much overhead the triggers algorithm is causing.
*
* This flag can be set or cleared using the "trigger_overhead_test" pragma.
* The pragma is not documented since it is not really part of the interface
* to SQLite, just the test procedure.
*/
int always_code_trigger_setup = 0;
/*
* Returns true if a trigger matching op, tr_tm and foreach that is NOT already
* on the Parse objects trigger-stack (to prevent recursive trigger firing) is
* found in the list specified as pTrigger.
*/
int sqliteTriggersExist(
Parse * pParse,
Trigger * pTrigger,
int op, /* one of TK_DELETE, TK_INSERT, TK_UPDATE */
int tr_tm, /* one of TK_BEFORE, TK_AFTER */
int foreach, /* one of TK_ROW or TK_STATEMENT */
ExprList * pChanges)
{
Trigger * tt;
if (always_code_trigger_setup) return 1;
tt = pTrigger;
while (tt) {
if (tt->op == op && tt->tr_tm == tr_tm && tt->foreach == foreach &&
checkColumnOverLap(tt->pColumns, pChanges)) {
TriggerStack * ss;
ss = pParse->trigStack;
while (ss && ss->pTrigger != pTrigger) ss = ss->pNext;
if (!ss) return 1;
}
tt = tt->pNext;
}
return 0;
}
static int codeTriggerProgram(
Parse *pParse,
TriggerStep * program,
int onError)
{
TriggerStep * step = program;
int orconf;
while (step) {
int saveNTab = pParse->nTab;
orconf = (onError == OE_Default)?step->orconf:onError;
pParse->trigStack->orconf = orconf;
switch(step->op) {
case TK_SELECT: {
int tmp_tbl = pParse->nTab++;
sqliteVdbeAddOp(pParse->pVdbe, OP_OpenTemp, tmp_tbl, 0);
sqliteVdbeAddOp(pParse->pVdbe, OP_KeyAsData, tmp_tbl, 1);
sqliteSelect(pParse, step->pSelect,
SRT_Union, tmp_tbl, 0, 0, 0);
sqliteVdbeAddOp(pParse->pVdbe, OP_Close, tmp_tbl, 0);
pParse->nTab--;
break;
}
case TK_UPDATE: {
sqliteVdbeAddOp(pParse->pVdbe, OP_PushList, 0, 0);
sqliteUpdate(pParse, &step->target,
sqliteExprListDup(step->pExprList),
sqliteExprDup(step->pWhere), orconf);
sqliteVdbeAddOp(pParse->pVdbe, OP_PopList, 0, 0);
break;
}
case TK_INSERT: {
sqliteInsert(pParse, &step->target,
sqliteExprListDup(step->pExprList),
sqliteSelectDup(step->pSelect),
sqliteIdListDup(step->pIdList), orconf);
break;
}
case TK_DELETE: {
sqliteVdbeAddOp(pParse->pVdbe, OP_PushList, 0, 0);
sqliteDeleteFrom(pParse, &step->target,
sqliteExprDup(step->pWhere)
);
sqliteVdbeAddOp(pParse->pVdbe, OP_PopList, 0, 0);
break;
}
default:
assert(0);
}
pParse->nTab = saveNTab;
step = step->pNext;
}
return 0;
}
int sqliteCodeRowTrigger(
Parse * pParse, /* Parse context */
int op, /* One of TK_UPDATE, TK_INSERT, TK_DELETE */
ExprList * changes, /* Changes list for any UPDATE OF triggers */
int tr_tm, /* One of TK_BEFORE, TK_AFTER */
Table * tbl, /* The table to code triggers from */
int newTable, /* The indice of the "new" row to access */
int oldTable, /* The indice of the "old" row to access */
int onError) /* ON CONFLICT policy */
{
Trigger * pTrigger;
TriggerStack * pTriggerStack;
assert(op == TK_UPDATE || op == TK_INSERT || op == TK_DELETE);
assert(tr_tm == TK_BEFORE || tr_tm == TK_AFTER);
assert(newTable != -1 || oldTable != -1);
pTrigger = tbl->pTrigger;
while (pTrigger) {
int fire_this = 0;
/* determine whether we should code this trigger */
if (pTrigger->op == op && pTrigger->tr_tm == tr_tm &&
pTrigger->foreach == TK_ROW) {
fire_this = 1;
pTriggerStack = pParse->trigStack;
while (pTriggerStack) {
if (pTriggerStack->pTrigger == pTrigger) fire_this = 0;
pTriggerStack = pTriggerStack->pNext;
}
if (op == TK_UPDATE && pTrigger->pColumns &&
!checkColumnOverLap(pTrigger->pColumns, changes))
fire_this = 0;
}
if (fire_this) {
int endTrigger;
IdList dummyTablist;
Expr * whenExpr;
dummyTablist.nId = 0;
dummyTablist.a = 0;
/* Push an entry on to the trigger stack */
pTriggerStack = sqliteMalloc(sizeof(TriggerStack));
pTriggerStack->pTrigger = pTrigger;
pTriggerStack->newIdx = newTable;
pTriggerStack->oldIdx = oldTable;
pTriggerStack->pTab = tbl;
pTriggerStack->pNext = pParse->trigStack;
pParse->trigStack = pTriggerStack;
/* code the WHEN clause */
endTrigger = sqliteVdbeMakeLabel(pParse->pVdbe);
whenExpr = sqliteExprDup(pTrigger->pWhen);
if (sqliteExprResolveIds(pParse, 0, &dummyTablist, 0, whenExpr)) {
pParse->trigStack = pParse->trigStack->pNext;
sqliteFree(pTriggerStack);
sqliteExprDelete(whenExpr);
return 1;
}
sqliteExprIfFalse(pParse, whenExpr, endTrigger);
sqliteExprDelete(whenExpr);
codeTriggerProgram(pParse, pTrigger->step_list, onError);
/* Pop the entry off the trigger stack */
pParse->trigStack = pParse->trigStack->pNext;
sqliteFree(pTriggerStack);
sqliteVdbeResolveLabel(pParse->pVdbe, endTrigger);
}
pTrigger = pTrigger->pNext;
}
return 0;
}
/*
* Handle UPDATE and DELETE triggers on views
*/
void sqliteViewTriggers(Parse *pParse, Table *pTab,
Expr * pWhere, int onError, ExprList * pChanges)
{
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 = sqliteIdListAppend(0, &tblNameToken);
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);
sqliteBeginMultiWriteOperation(pParse);
/* Allocate temp tables */
oldIdx = pParse->nTab++;
sqliteVdbeAddOp(v, OP_OpenTemp, oldIdx, 0);
if (pChanges) {
newIdx = pParse->nTab++;
sqliteVdbeAddOp(v, OP_OpenTemp, 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, jj;
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; ii<pChanges->nExpr; 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; jj<pTab->nCol; jj++){
if( sqliteStrICmp(pTab->aCol[jj].zName, pChanges->a[ii].zName)==0 ){
aXRef[jj] = ii;
break;
}
}
if( jj>=pTab->nCol ){
sqliteSetString(&pParse->zErrMsg, "no such column: ",
pChanges->a[ii].zName, 0);
pParse->nErr++;
goto trigger_cleanup;
}
}
sqliteVdbeAddOp(v, OP_Integer, 13, 0);
for (ii = 0; ii < pTab->nCol; 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, onError);
sqliteCodeRowTrigger(pParse, TK_UPDATE, pChanges, TK_AFTER,
pTab, newIdx, oldIdx, onError);
} else {
sqliteCodeRowTrigger(pParse, TK_DELETE, 0, TK_BEFORE, pTab, -1, oldIdx,
onError);
sqliteCodeRowTrigger(pParse, TK_DELETE, 0, TK_AFTER, pTab, -1, oldIdx,
onError);
}
sqliteVdbeAddOp(v, OP_Next, oldIdx, startOfLoop);
sqliteVdbeResolveLabel(v, endOfLoop);
sqliteEndWriteOperation(pParse);
trigger_cleanup:
sqliteFree(aXRef);
sqliteExprListDelete(pChanges);
sqliteExprDelete(pWhere);
sqliteExprListDelete(theSelect.pEList);
sqliteIdListDelete(theSelect.pSrc);
sqliteExprDelete(theSelect.pWhere);
return;
}

View File

@@ -12,7 +12,7 @@
** This file contains C code routines that are called by the parser ** This file contains C code routines that are called by the parser
** to handle UPDATE statements. ** to handle UPDATE statements.
** **
** $Id: update.c,v 1.36 2002/03/03 18:59:41 drh Exp $ ** $Id: update.c,v 1.37 2002/05/15 08:30:14 danielk1977 Exp $
*/ */
#include "sqliteInt.h" #include "sqliteInt.h"
@@ -47,9 +47,38 @@ void sqliteUpdate(
Expr *pRecnoExpr; /* Expression defining the new record number */ Expr *pRecnoExpr; /* Expression defining the new record number */
int openAll; /* True if all indices need to be opened */ int openAll; /* True if all indices need to be opened */
int row_triggers_exist = 0;
int newIdx = -1; /* index of trigger "new" temp table */
int oldIdx = -1; /* index of trigger "old" temp table */
if( pParse->nErr || sqlite_malloc_failed ) goto update_cleanup; if( pParse->nErr || sqlite_malloc_failed ) goto update_cleanup;
db = pParse->db; db = pParse->db;
/* Check for the special case of a VIEW with one or more ON UPDATE triggers
* defined
*/
{
char * zTab = sqliteTableNameFromToken(pTableName);
if(zTab != 0) {
pTab = sqliteFindTable(pParse->db, zTab);
if (pTab) {
row_triggers_exist =
sqliteTriggersExist(pParse, pTab->pTrigger,
TK_UPDATE, TK_BEFORE, TK_ROW, pChanges) ||
sqliteTriggersExist(pParse, pTab->pTrigger,
TK_UPDATE, TK_AFTER, TK_ROW, pChanges);
}
sqliteFree(zTab);
if (row_triggers_exist && pTab->pSelect ) {
/* Just fire VIEW triggers */
sqliteViewTriggers(pParse, pTab, pWhere, onError, pChanges);
return;
}
}
}
/* Locate the table which we want to update. This table has to be /* Locate the table which we want to update. This table has to be
** put in an IdList structure because some of the subroutines we ** put in an IdList structure because some of the subroutines we
** will be calling are designed to work with multiple tables and expect ** will be calling are designed to work with multiple tables and expect
@@ -63,6 +92,11 @@ void sqliteUpdate(
if( aXRef==0 ) goto update_cleanup; if( aXRef==0 ) goto update_cleanup;
for(i=0; i<pTab->nCol; i++) aXRef[i] = -1; for(i=0; i<pTab->nCol; i++) aXRef[i] = -1;
if (row_triggers_exist) {
newIdx = pParse->nTab++;
oldIdx = pParse->nTab++;
}
/* Resolve the column names in all the expressions in both the /* Resolve the column names in all the expressions in both the
** WHERE clause and in the new values. Also find the column index ** WHERE clause and in the new values. Also find the column index
** for each column to be updated in the pChanges array. ** for each column to be updated in the pChanges array.
@@ -159,17 +193,62 @@ void sqliteUpdate(
/* Initialize the count of updated rows /* Initialize the count of updated rows
*/ */
if( db->flags & SQLITE_CountRows ){ if( db->flags & SQLITE_CountRows && !pParse->trigStack ){
sqliteVdbeAddOp(v, OP_Integer, 0, 0); sqliteVdbeAddOp(v, OP_Integer, 0, 0);
} }
if (row_triggers_exist) {
int ii;
sqliteVdbeAddOp(v, OP_OpenTemp, oldIdx, 0);
sqliteVdbeAddOp(v, OP_OpenTemp, newIdx, 0);
sqliteVdbeAddOp(v, OP_ListRewind, 0, 0);
addr = sqliteVdbeAddOp(v, OP_ListRead, 0, 0);
sqliteVdbeAddOp(v, OP_Dup, 0, 0);
sqliteVdbeAddOp(v, OP_Dup, 0, 0);
sqliteVdbeAddOp(v, OP_Open, 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);
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);
}else{
sqliteExprCode(pParse, pChanges->a[aXRef[ii]].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);
if (sqliteCodeRowTrigger(pParse, TK_UPDATE, pChanges, TK_BEFORE, pTab,
newIdx, oldIdx, onError)) goto update_cleanup;
}
/* Rewind the list of records that need to be updated and /* Rewind the list of records that need to be updated and
** open every index that needs updating. Note that if any ** open every index that needs updating. Note that if any
** index could potentially invoke a REPLACE conflict resolution ** index could potentially invoke a REPLACE conflict resolution
** action, then we need to open all indices because we might need ** action, then we need to open all indices because we might need
** to be deleting some records. ** to be deleting some records.
*/ */
sqliteVdbeAddOp(v, OP_ListRewind, 0, 0);
openOp = pTab->isTemp ? OP_OpenWrAux : OP_OpenWrite; openOp = pTab->isTemp ? OP_OpenWrAux : OP_OpenWrite;
sqliteVdbeAddOp(v, openOp, base, pTab->tnum); sqliteVdbeAddOp(v, openOp, base, pTab->tnum);
if( onError==OE_Replace ){ if( onError==OE_Replace ){
@@ -197,8 +276,12 @@ void sqliteUpdate(
** Also, the old data is needed to delete the old index entires. ** Also, the old data is needed to delete the old index entires.
** So make the cursor point at the old record. ** So make the cursor point at the old record.
*/ */
addr = sqliteVdbeAddOp(v, OP_ListRead, 0, 0); if (!row_triggers_exist) {
sqliteVdbeAddOp(v, OP_Dup, 0, 0); int ii;
sqliteVdbeAddOp(v, OP_ListRewind, 0, 0);
addr = sqliteVdbeAddOp(v, OP_ListRead, 0, 0);
sqliteVdbeAddOp(v, OP_Dup, 0, 0);
}
sqliteVdbeAddOp(v, OP_MoveTo, base, 0); sqliteVdbeAddOp(v, OP_MoveTo, base, 0);
/* If the record number will change, push the record number as it /* If the record number will change, push the record number as it
@@ -241,7 +324,7 @@ void sqliteUpdate(
/* If changing the record number, delete the old record. /* If changing the record number, delete the old record.
*/ */
if( chngRecno ){ if( chngRecno ){
sqliteVdbeAddOp(v, OP_Delete, 0, 0); sqliteVdbeAddOp(v, OP_Delete, base, 0);
} }
/* Create the new index entries and the new record. /* Create the new index entries and the new record.
@@ -250,22 +333,49 @@ void sqliteUpdate(
/* Increment the row counter /* Increment the row counter
*/ */
if( db->flags & SQLITE_CountRows ){ if( db->flags & SQLITE_CountRows && !pParse->trigStack){
sqliteVdbeAddOp(v, OP_AddImm, 1, 0); sqliteVdbeAddOp(v, OP_AddImm, 1, 0);
} }
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);
}
sqliteVdbeAddOp(v, OP_Close, base, 0);
pParse->nTab = base;
if (sqliteCodeRowTrigger(pParse, TK_UPDATE, pChanges, TK_AFTER, pTab,
newIdx, oldIdx, onError)) goto update_cleanup;
}
/* Repeat the above with the next record to be updated, until /* Repeat the above with the next record to be updated, until
** all record selected by the WHERE clause have been updated. ** all record selected by the WHERE clause have been updated.
*/ */
sqliteVdbeAddOp(v, OP_Goto, 0, addr); sqliteVdbeAddOp(v, OP_Goto, 0, addr);
sqliteVdbeChangeP2(v, addr, sqliteVdbeCurrentAddr(v)); sqliteVdbeChangeP2(v, addr, sqliteVdbeCurrentAddr(v));
sqliteVdbeAddOp(v, OP_ListReset, 0, 0); sqliteVdbeAddOp(v, OP_ListReset, 0, 0);
/* Close all tables if there were no FOR EACH ROW 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);
}
}
sqliteVdbeAddOp(v, OP_Close, base, 0);
pParse->nTab = base;
} else {
sqliteVdbeAddOp(v, OP_Close, newIdx, 0);
sqliteVdbeAddOp(v, OP_Close, oldIdx, 0);
}
sqliteEndWriteOperation(pParse); sqliteEndWriteOperation(pParse);
/* /*
** Return the number of rows that were changed. ** Return the number of rows that were changed.
*/ */
if( db->flags & SQLITE_CountRows ){ if( db->flags & SQLITE_CountRows && !pParse->trigStack ){
sqliteVdbeAddOp(v, OP_ColumnCount, 1, 0); sqliteVdbeAddOp(v, OP_ColumnCount, 1, 0);
sqliteVdbeAddOp(v, OP_ColumnName, 0, 0); sqliteVdbeAddOp(v, OP_ColumnName, 0, 0);
sqliteVdbeChangeP3(v, -1, "rows updated", P3_STATIC); sqliteVdbeChangeP3(v, -1, "rows updated", P3_STATIC);

View File

@@ -30,7 +30,7 @@
** But other routines are also provided to help in building up ** But other routines are also provided to help in building up
** a program instruction by instruction. ** a program instruction by instruction.
** **
** $Id: vdbe.c,v 1.141 2002/05/10 13:14:07 drh Exp $ ** $Id: vdbe.c,v 1.142 2002/05/15 08:30:14 danielk1977 Exp $
*/ */
#include "sqliteInt.h" #include "sqliteInt.h"
#include <ctype.h> #include <ctype.h>
@@ -249,6 +249,9 @@ struct Vdbe {
int nCallback; /* Number of callbacks invoked so far */ int nCallback; /* Number of callbacks invoked so far */
int iLimit; /* Limit on the number of callbacks remaining */ int iLimit; /* Limit on the number of callbacks remaining */
int iOffset; /* Offset before beginning to do callbacks */ int iOffset; /* Offset before beginning to do callbacks */
int keylistStackDepth;
Keylist ** keylistStack;
}; };
/* /*
@@ -999,6 +1002,15 @@ static void Cleanup(Vdbe *p){
sqliteFree(p->aSet); sqliteFree(p->aSet);
p->aSet = 0; p->aSet = 0;
p->nSet = 0; p->nSet = 0;
if (p->keylistStackDepth > 0) {
int ii;
for (ii = 0; ii < p->keylistStackDepth; ii++) {
KeylistFree(p->keylistStack[ii]);
}
sqliteFree(p->keylistStack);
p->keylistStackDepth = 0;
p->keylistStack = 0;
}
} }
/* /*
@@ -1062,7 +1074,7 @@ static char *zOpName[] = { 0,
"Le", "Gt", "Ge", "IsNull", "Le", "Gt", "Ge", "IsNull",
"NotNull", "Negative", "And", "Or", "NotNull", "Negative", "And", "Or",
"Not", "Concat", "Noop", "Function", "Not", "Concat", "Noop", "Function",
"Limit", "Limit", "PushList", "PopList",
}; };
/* /*
@@ -4539,6 +4551,39 @@ case OP_SetFound: {
break; break;
} }
/* Opcode: PushList * * *
**
** Save the current Vdbe list such that it can be restored by a PopList
** opcode. The list is empty after this is executed.
*/
case OP_PushList: {
p->keylistStackDepth++;
assert(p->keylistStackDepth > 0);
p->keylistStack = sqliteRealloc(p->keylistStack,
sizeof(Keylist *) * p->keylistStackDepth);
p->keylistStack[p->keylistStackDepth - 1] = p->pList;
p->pList = 0;
break;
}
/* Opcode: PopList * * *
**
** Restore the Vdbe list to the state it was in when PushList was last
** executed.
*/
case OP_PopList: {
assert(p->keylistStackDepth > 0);
p->keylistStackDepth--;
KeylistFree(p->pList);
p->pList = p->keylistStack[p->keylistStackDepth];
p->keylistStack[p->keylistStackDepth] = 0;
if (p->keylistStackDepth == 0) {
sqliteFree(p->keylistStack);
p->keylistStack = 0;
}
break;
}
/* Opcode: SetNotFound P1 P2 * /* Opcode: SetNotFound P1 P2 *
** **
** Pop the stack once and compare the value popped off with the ** Pop the stack once and compare the value popped off with the

View File

@@ -15,7 +15,7 @@
** or VDBE. The VDBE implements an abstract machine that runs a ** or VDBE. The VDBE implements an abstract machine that runs a
** simple program to access and modify the underlying database. ** simple program to access and modify the underlying database.
** **
** $Id: vdbe.h,v 1.50 2002/04/20 14:24:43 drh Exp $ ** $Id: vdbe.h,v 1.51 2002/05/15 08:30:14 danielk1977 Exp $
*/ */
#ifndef _SQLITE_VDBE_H_ #ifndef _SQLITE_VDBE_H_
#define _SQLITE_VDBE_H_ #define _SQLITE_VDBE_H_
@@ -198,7 +198,10 @@ typedef struct VdbeOp VdbeOp;
#define OP_Limit 113 #define OP_Limit 113
#define OP_MAX 113 #define OP_PushList 114
#define OP_PopList 115
#define OP_MAX 115
/* /*
** Prototypes for the VDBE interface. See comments on the implementation ** Prototypes for the VDBE interface. See comments on the implementation

View File

@@ -13,7 +13,7 @@
** the WHERE clause of SQL statements. Also found here are subroutines ** the WHERE clause of SQL statements. Also found here are subroutines
** to generate VDBE code to evaluate expressions. ** to generate VDBE code to evaluate expressions.
** **
** $Id: where.c,v 1.41 2002/04/30 19:20:29 drh Exp $ ** $Id: where.c,v 1.42 2002/05/15 08:30:14 danielk1977 Exp $
*/ */
#include "sqliteInt.h" #include "sqliteInt.h"
@@ -216,6 +216,22 @@ WhereInfo *sqliteWhereBegin(
*/ */
for(i=0; i<nExpr; i++){ for(i=0; i<nExpr; i++){
exprAnalyze(base, &aExpr[i]); exprAnalyze(base, &aExpr[i]);
if (pParse->trigStack && pParse->trigStack->newIdx >= 0) {
aExpr[i].prereqRight =
aExpr[i].prereqRight & ~(1 << pParse->trigStack->newIdx - base);
aExpr[i].prereqLeft =
aExpr[i].prereqLeft & ~(1 << pParse->trigStack->newIdx - base);
aExpr[i].prereqAll =
aExpr[i].prereqAll & ~(1 << pParse->trigStack->newIdx - base);
}
if (pParse->trigStack && pParse->trigStack->oldIdx >= 0) {
aExpr[i].prereqRight =
aExpr[i].prereqRight & ~(1 << pParse->trigStack->oldIdx - base);
aExpr[i].prereqLeft =
aExpr[i].prereqLeft & ~(1 << pParse->trigStack->oldIdx - base);
aExpr[i].prereqAll =
aExpr[i].prereqAll & ~(1 << pParse->trigStack->oldIdx - base);
}
} }
/* Figure out a good nesting order for the tables. aOrder[0] will /* Figure out a good nesting order for the tables. aOrder[0] will

113
test/trigger1.test Normal file
View File

@@ -0,0 +1,113 @@
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
#
# This file tests creating and dropping triggers, and interaction thereof
# with the database COMMIT/ROLLBACK logic.
#
# 1. CREATE and DROP TRIGGER tests
# trig-1.1: Error if table does not exist
# trig-1.2: Error if trigger already exists
# trig-1.3: Created triggers are deleted if the transaction is rolled back
# trig-1.4: DROP TRIGGER removes trigger
# trig-1.5: Dropped triggers are restored if the transaction is rolled back
# trig-1.6: Error if dropped trigger doesn't exist
# trig-1.7: Dropping the table automatically drops all triggers
# trig-1.8: A trigger created on a TEMP table is not inserted into sqlite_master
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
do_test trig_cd-1.1 {
catchsql {
CREATE TRIGGER trig UPDATE ON no_such_table BEGIN
SELECT * from sqlite_master;
END;
}
} {1 {no such table: no_such_table}}
execsql {
CREATE TABLE t1(a);
}
execsql {
CREATE TRIGGER tr1 INSERT ON t1 BEGIN
INSERT INTO t1 values(1);
END;
}
do_test trig_cd-1.2 {
catchsql {
CREATE TRIGGER tr1 DELETE ON t1 BEGIN
SELECT * FROM sqlite_master;
END
}
} {1 {trigger tr1 already exists}}
do_test trig_cd-1.3 {
catchsql {
BEGIN;
CREATE TRIGGER tr2 INSERT ON t1 BEGIN
SELECT * from sqlite_master; END;
ROLLBACK;
CREATE TRIGGER tr2 INSERT ON t1 BEGIN
SELECT * from sqlite_master; END;
}
} {0 {}}
do_test trig_cd-1.4 {
catchsql {
DROP TRIGGER tr1;
CREATE TRIGGER tr1 DELETE ON t1 BEGIN
SELECT * FROM sqlite_master;
END
}
} {0 {}}
do_test trig_cd-1.5 {
execsql {
BEGIN;
DROP TRIGGER tr2;
ROLLBACK;
DROP TRIGGER tr2;
}
} {}
do_test trig_cd-1.6 {
catchsql {
DROP TRIGGER biggles;
}
} {1 {no such trigger: biggles}}
do_test trig_cd-1.7 {
catchsql {
DROP TABLE t1;
DROP TRIGGER tr1;
}
} {1 {no such trigger: tr1}}
execsql {
CREATE TEMP TABLE temp_table(a);
}
do_test trig_cd-1.8 {
execsql {
CREATE TRIGGER temp_trig UPDATE ON temp_table BEGIN
SELECT * from sqlite_master;
END;
SELECT count(*) FROM sqlite_master WHERE name = 'temp_trig';
}
} {0}
catchsql {
DROP TABLE temp_table;
}
catchsql {
DROP TABLE t1;
}
finish_test

597
test/trigger2.test Normal file
View File

@@ -0,0 +1,597 @@
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
#
# Regression testing of FOR EACH ROW table triggers
#
# 1. Trigger execution order tests.
# These tests ensure that BEFORE and AFTER triggers are fired at the correct
# times relative to each other and the triggering statement.
#
# trig-1.1.*: ON UPDATE trigger execution model.
# trig-1.2.*: DELETE trigger execution model.
# trig-1.3.*: INSERT trigger execution model.
#
# 2. Trigger program execution tests.
# These tests ensure that trigger programs execute correctly (ie. that a
# trigger program can correctly execute INSERT, UPDATE, DELETE * SELECT
# statements, and combinations thereof).
#
# 3. Selective trigger execution
# This tests that conditional triggers (ie. UPDATE OF triggers and triggers
# with WHEN clauses) are fired only fired when they are supposed to be.
#
# trig-3.1: UPDATE OF triggers
# trig-3.2: WHEN clause
#
# 4. Cascaded trigger execution
# Tests that trigger-programs may cause other triggers to fire. Also that a
# trigger-program is never executed recursively.
#
# trig-4.1: Trivial cascading trigger
# trig-4.2: Trivial recursive trigger handling
#
# 5. Count changes behaviour.
# Verify that rows altered by triggers are not included in the return value
# of the "count changes" interface.
#
# 6. ON CONFLICT clause handling
# trig-6.1[a-f]: INSERT statements
# trig-6.2[a-f]: UPDATE statements
#
# 7. Triggers on views fire correctly.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
# 1.
set ii 0
foreach tbl_defn [ list \
{CREATE TABLE tbl (a, b);} \
{CREATE TABLE tbl (a INTEGER PRIMARY KEY, b);} \
{CREATE TABLE tbl (a, b PRIMARY KEY);} \
{CREATE TABLE tbl (a, b); CREATE INDEX tbl_idx ON tbl(b);} ] {
incr ii
catchsql { DROP INDEX tbl_idx; }
catchsql {
DROP TABLE rlog;
DROP TABLE clog;
DROP TABLE tbl;
DROP TABLE other_tbl;
}
execsql $tbl_defn
execsql {
INSERT INTO tbl VALUES(1, 2);
INSERT INTO tbl VALUES(3, 4);
CREATE TABLE rlog (idx, old_a, old_b, db_sum_a, db_sum_b, new_a, new_b);
CREATE TABLE clog (idx, old_a, old_b, db_sum_a, db_sum_b, new_a, new_b);
CREATE TRIGGER before_update_row BEFORE UPDATE ON tbl FOR EACH ROW
BEGIN
INSERT INTO rlog VALUES ( (SELECT max(idx) + 1 FROM rlog),
old.a, old.b,
(SELECT sum(a) FROM tbl), (SELECT sum(b) FROM tbl),
new.a, new.b);
END;
CREATE TRIGGER after_update_row AFTER UPDATE ON tbl FOR EACH ROW
BEGIN
INSERT INTO rlog VALUES ( (SELECT max(idx) + 1 FROM rlog),
old.a, old.b,
(SELECT sum(a) FROM tbl), (SELECT sum(b) FROM tbl),
new.a, new.b);
END;
CREATE TRIGGER conditional_update_row AFTER UPDATE ON tbl FOR EACH ROW
WHEN old.a = 1
BEGIN
INSERT INTO clog VALUES ( (SELECT max(idx) + 1 FROM clog),
old.a, old.b,
(SELECT sum(a) FROM tbl), (SELECT sum(b) FROM tbl),
new.a, new.b);
END;
}
do_test trig-1.1.$ii {
execsql {
UPDATE tbl SET a = a * 10, b = b * 10;
SELECT * FROM rlog ORDER BY idx;
SELECT * FROM clog ORDER BY idx;
}
} [list 1 1 2 4 6 10 20 \
2 1 2 13 24 10 20 \
3 3 4 13 24 30 40 \
4 3 4 40 60 30 40 \
1 1 2 13 24 10 20 ]
execsql {
DELETE FROM rlog;
DELETE FROM tbl;
INSERT INTO tbl VALUES (100, 100);
INSERT INTO tbl VALUES (300, 200);
CREATE TRIGGER delete_before_row BEFORE DELETE ON tbl FOR EACH ROW
BEGIN
INSERT INTO rlog VALUES ( (SELECT max(idx) + 1 FROM rlog),
old.a, old.b,
(SELECT sum(a) FROM tbl), (SELECT sum(b) FROM tbl),
0, 0);
END;
CREATE TRIGGER delete_after_row AFTER DELETE ON tbl FOR EACH ROW
BEGIN
INSERT INTO rlog VALUES ( (SELECT max(idx) + 1 FROM rlog),
old.a, old.b,
(SELECT sum(a) FROM tbl), (SELECT sum(b) FROM tbl),
0, 0);
END;
}
do_test trig-1.2.$ii {
execsql {
DELETE FROM tbl;
SELECT * FROM rlog;
}
} [list 1 100 100 400 300 0 0 \
2 100 100 300 200 0 0 \
3 300 200 300 200 0 0 \
4 300 200 0 0 0 0 ]
execsql {
DELETE FROM rlog;
CREATE TRIGGER insert_before_row BEFORE INSERT ON tbl FOR EACH ROW
BEGIN
INSERT INTO rlog VALUES ( (SELECT max(idx) + 1 FROM rlog),
0, 0,
(SELECT sum(a) FROM tbl), (SELECT sum(b) FROM tbl),
new.a, new.b);
END;
CREATE TRIGGER insert_after_row AFTER INSERT ON tbl FOR EACH ROW
BEGIN
INSERT INTO rlog VALUES ( (SELECT max(idx) + 1 FROM rlog),
0, 0,
(SELECT sum(a) FROM tbl), (SELECT sum(b) FROM tbl),
new.a, new.b);
END;
}
do_test trig-1.3.$ii {
execsql {
CREATE TABLE other_tbl(a, b);
INSERT INTO other_tbl VALUES(1, 2);
INSERT INTO other_tbl VALUES(3, 4);
-- INSERT INTO tbl SELECT * FROM other_tbl;
INSERT INTO tbl VALUES(5, 6);
DROP TABLE other_tbl;
SELECT * FROM rlog;
}
} [list 1 0 0 0 0 5 6 \
2 0 0 5 6 5 6 ]
}
catchsql {
DROP TABLE rlog;
DROP TABLE clog;
DROP TABLE tbl;
DROP TABLE other_tbl;
}
# 2.
set ii 0
foreach tr_program [ list \
{UPDATE tbl SET b = old.b;} \
{INSERT INTO log VALUES(new.c, 2, 3);} \
{DELETE FROM log WHERE a = 1;} \
{INSERT INTO tbl VALUES(500, new.b * 10, 700);
UPDATE tbl SET c = old.c;
DELETE FROM log;} \
{INSERT INTO log select * from tbl;}
] \
{
foreach test_varset [ list \
{
set statement {UPDATE tbl SET c = 10 WHERE a = 1;}
set prep {INSERT INTO tbl VALUES(1, 2, 3);}
set newC 10
set newB 2
set newA 1
set oldA 1
set oldB 2
set oldC 3
} \
{
set statement {DELETE FROM tbl WHERE a = 1;}
set prep {INSERT INTO tbl VALUES(1, 2, 3);}
set oldA 1
set oldB 2
set oldC 3
} \
{
set statement {INSERT INTO tbl VALUES(1, 2, 3);}
set newA 1
set newB 2
set newC 3
}
] \
{
set statement {}
set prep {}
set newA {''}
set newB {''}
set newC {''}
set oldA {''}
set oldB {''}
set oldC {''}
incr ii
eval $test_varset
set statement_type [string range $statement 0 5]
set tr_program_fixed $tr_program
if {$statement_type == "DELETE"} {
regsub -all new\.a $tr_program_fixed {''} tr_program_fixed
regsub -all new\.b $tr_program_fixed {''} tr_program_fixed
regsub -all new\.c $tr_program_fixed {''} tr_program_fixed
}
if {$statement_type == "INSERT"} {
regsub -all old\.a $tr_program_fixed {''} tr_program_fixed
regsub -all old\.b $tr_program_fixed {''} tr_program_fixed
regsub -all old\.c $tr_program_fixed {''} tr_program_fixed
}
set tr_program_cooked $tr_program
regsub -all new\.a $tr_program_cooked $newA tr_program_cooked
regsub -all new\.b $tr_program_cooked $newB tr_program_cooked
regsub -all new\.c $tr_program_cooked $newC tr_program_cooked
regsub -all old\.a $tr_program_cooked $oldA tr_program_cooked
regsub -all old\.b $tr_program_cooked $oldB tr_program_cooked
regsub -all old\.c $tr_program_cooked $oldC tr_program_cooked
catchsql {
DROP TABLE tbl;
DROP TABLE log;
}
execsql {
CREATE TABLE tbl(a PRIMARY KEY, b, c);
CREATE TABLE log(a, b, c);
}
set query {SELECT * FROM tbl; SELECT * FROM log;}
set prep "$prep; INSERT INTO log VALUES(1, 2, 3); INSERT INTO log VALUES(10, 20, 30);"
# Check execution of BEFORE programs:
set before_data [ execsql "$prep $tr_program_cooked $statement $query" ]
execsql "DELETE FROM tbl; DELETE FROM log; $prep";
execsql "CREATE TRIGGER the_trigger BEFORE [string range $statement 0 6] ON tbl BEGIN $tr_program_fixed END;"
do_test trig-2-$ii-before "execsql {$statement $query}" $before_data
execsql "DROP TRIGGER the_trigger;"
execsql "DELETE FROM tbl; DELETE FROM log;"
# Check execution of AFTER programs
set after_data [ execsql "$prep $statement $tr_program_cooked $query" ]
execsql "DELETE FROM tbl; DELETE FROM log; $prep";
execsql "CREATE TRIGGER the_trigger AFTER [string range $statement 0 6] ON tbl BEGIN $tr_program_fixed END;"
do_test trig-2-$ii-after "execsql {$statement $query}" $after_data
execsql "DROP TRIGGER the_trigger;"
}
}
catchsql {
DROP TABLE tbl;
DROP TABLE log;
}
# 3.
# trig-3.1: UPDATE OF triggers
execsql {
CREATE TABLE tbl (a, b, c, d);
CREATE TABLE log (a);
INSERT INTO log VALUES (0);
INSERT INTO tbl VALUES (0, 0, 0, 0);
INSERT INTO tbl VALUES (1, 0, 0, 0);
CREATE TRIGGER tbl_after_update_cd BEFORE UPDATE OF c, d ON tbl
BEGIN
UPDATE log SET a = a + 1;
END;
}
do_test trig-3.1 {
execsql {
UPDATE tbl SET b = 1, c = 10; -- 2
UPDATE tbl SET b = 10; -- 0
UPDATE tbl SET d = 4 WHERE a = 0; --1
UPDATE tbl SET a = 4, b = 10; --0
SELECT * FROM log;
}
} {3}
execsql {
DROP TABLE tbl;
DROP TABLE log;
}
# trig-3.2: WHEN clause
set when_triggers [ list \
{t1 BEFORE INSERT ON tbl WHEN new.a > 20} \
{t2 BEFORE INSERT ON tbl WHEN (SELECT count(*) FROM tbl) = 0} ]
execsql {
CREATE TABLE tbl (a, b, c, d);
CREATE TABLE log (a);
INSERT INTO log VALUES (0);
}
foreach trig $when_triggers {
execsql "CREATE TRIGGER $trig BEGIN UPDATE log set a = a + 1; END;"
}
do_test trig-3.2 {
execsql {
INSERT INTO tbl VALUES(0, 0, 0, 0); -- 1
SELECT * FROM log;
UPDATE log SET a = 0;
INSERT INTO tbl VALUES(0, 0, 0, 0); -- 0
SELECT * FROM log;
UPDATE log SET a = 0;
INSERT INTO tbl VALUES(200, 0, 0, 0); -- 1
SELECT * FROM log;
UPDATE log SET a = 0;
}
} {1 0 1}
execsql {
DROP TABLE tbl;
DROP TABLE log;
}
# Simple cascaded trigger
execsql {
CREATE TABLE tblA(a, b);
CREATE TABLE tblB(a, b);
CREATE TABLE tblC(a, b);
CREATE TRIGGER tr1 BEFORE INSERT ON tblA BEGIN
INSERT INTO tblB values(new.a, new.b);
END;
CREATE TRIGGER tr2 BEFORE INSERT ON tblB BEGIN
INSERT INTO tblC values(new.a, new.b);
END;
}
do_test trig-4.1 {
execsql {
INSERT INTO tblA values(1, 2);
SELECT * FROM tblA;
SELECT * FROM tblB;
SELECT * FROM tblC;
}
} {1 2 1 2 1 2}
execsql {
DROP TABLE tblA;
DROP TABLE tblB;
DROP TABLE tblC;
}
# Simple recursive trigger
execsql {
CREATE TABLE tbl(a, b, c);
CREATE TRIGGER tbl_trig BEFORE INSERT ON tbl
BEGIN
INSERT INTO tbl VALUES (new.a, new.b, new.c);
END;
}
do_test trig-4.2 {
execsql {
INSERT INTO tbl VALUES (1, 2, 3);
select * from tbl;
}
} {1 2 3 1 2 3}
execsql {
DROP TABLE tbl;
}
# 5.
execsql {
CREATE TABLE tbl(a, b, c);
CREATE TRIGGER tbl_trig BEFORE INSERT ON tbl
BEGIN
INSERT INTO tbl VALUES (1, 2, 3);
INSERT INTO tbl VALUES (2, 2, 3);
UPDATE tbl set b = 10 WHERE a = 1;
DELETE FROM tbl WHERE a = 1;
DELETE FROM tbl;
END;
}
do_test trig-5 {
execsql {
INSERT INTO tbl VALUES(100, 200, 300);
}
db changes
} {1}
execsql {
DROP TABLE tbl;
}
# Handling of ON CONFLICT by INSERT statements inside triggers
execsql {
CREATE TABLE tbl (a primary key, b, c);
CREATE TRIGGER ai_tbl AFTER INSERT ON tbl BEGIN
INSERT OR IGNORE INTO tbl values (new.a, 0, 0);
END;
}
do_test trig-6.1a {
execsql {
BEGIN;
INSERT INTO tbl values (1, 2, 3);
SELECT * from tbl;
}
} {1 2 3}
do_test trig-6.1b {
catchsql {
INSERT OR ABORT INTO tbl values (2, 2, 3);
}
} {1 {constraint failed}}
do_test trig-6.1c {
execsql {
SELECT * from tbl;
}
} {1 2 3}
do_test trig-6.1d {
catchsql {
INSERT OR FAIL INTO tbl values (2, 2, 3);
}
} {1 {constraint failed}}
do_test trig-6.1e {
execsql {
SELECT * from tbl;
}
} {1 2 3 2 2 3}
do_test trig-6.1f {
execsql {
INSERT OR REPLACE INTO tbl values (2, 2, 3);
SELECT * from tbl;
}
} {1 2 3 2 0 0}
do_test trig-6.1g {
catchsql {
INSERT OR ROLLBACK INTO tbl values (3, 2, 3);
}
} {1 {constraint failed}}
do_test trig-6.1h {
execsql {
SELECT * from tbl;
}
} {}
# Handling of ON CONFLICT by UPDATE statements inside triggers
execsql {
INSERT INTO tbl values (4, 2, 3);
INSERT INTO tbl values (6, 3, 4);
CREATE TRIGGER au_tbl AFTER UPDATE ON tbl BEGIN
UPDATE OR IGNORE tbl SET a = new.a, c = 10;
END;
}
do_test trig-6.2a {
execsql {
BEGIN;
UPDATE tbl SET a = 1 WHERE a = 4;
SELECT * from tbl;
}
} {1 2 10 6 3 4}
do_test trig-6.2b {
catchsql {
UPDATE OR ABORT tbl SET a = 4 WHERE a = 1;
}
} {1 {constraint failed}}
do_test trig-6.2c {
execsql {
SELECT * from tbl;
}
} {1 2 10 6 3 4}
do_test trig-6.2d {
catchsql {
UPDATE OR FAIL tbl SET a = 4 WHERE a = 1;
}
} {1 {constraint failed}}
do_test trig-6.2e {
execsql {
SELECT * from tbl;
}
} {4 2 10 6 3 4}
do_test trig-6.2f {
execsql {
UPDATE OR REPLACE tbl SET a = 1 WHERE a = 4;
SELECT * from tbl;
}
} {1 3 10}
execsql {
INSERT INTO tbl VALUES (2, 3, 4);
}
do_test trig-6.2g {
catchsql {
UPDATE OR ROLLBACK tbl SET a = 4 WHERE a = 1;
}
} {1 {constraint failed}}
do_test trig-6.2h {
execsql {
SELECT * from tbl;
}
} {4 2 3 6 3 4}
execsql {
DROP TABLE tbl;
}
# 7. Triggers on views
execsql {
CREATE TABLE ab(a, b);
CREATE TABLE cd(c, d);
INSERT INTO ab VALUES (1, 2);
INSERT INTO ab VALUES (0, 0);
INSERT INTO cd VALUES (3, 4);
CREATE TABLE tlog(ii INTEGER PRIMARY KEY,
olda, oldb, oldc, oldd, newa, newb, newc, newd);
CREATE VIEW abcd AS SELECT a, b, c, d FROM ab, cd;
CREATE TRIGGER before_update BEFORE UPDATE ON abcd BEGIN
INSERT INTO tlog VALUES(NULL,
old.a, old.b, old.c, old.d, new.a, new.b, new.c, new.d);
END;
CREATE TRIGGER after_update AFTER UPDATE ON abcd BEGIN
INSERT INTO tlog VALUES(NULL,
old.a, old.b, old.c, old.d, new.a, new.b, new.c, new.d);
END;
CREATE TRIGGER before_delete BEFORE DELETE ON abcd BEGIN
INSERT INTO tlog VALUES(NULL,
old.a, old.b, old.c, old.d, 0, 0, 0, 0);
END;
CREATE TRIGGER after_delete AFTER DELETE ON abcd BEGIN
INSERT INTO tlog VALUES(NULL,
old.a, old.b, old.c, old.d, 0, 0, 0, 0);
END;
CREATE TRIGGER before_insert BEFORE INSERT ON abcd BEGIN
INSERT INTO tlog VALUES(NULL,
0, 0, 0, 0, new.a, new.b, new.c, new.d);
END;
CREATE TRIGGER after_insert AFTER INSERT ON abcd BEGIN
INSERT INTO tlog VALUES(NULL,
0, 0, 0, 0, new.a, new.b, new.c, new.d);
END;
}
do_test trig-7 {
execsql {
UPDATE abcd SET a = 100, b = 5*5 WHERE a = 1;
DELETE FROM abcd WHERE a = 1;
INSERT INTO abcd VALUES(10, 20, 30, 40);
SELECT * FROM tlog;
}
} [ list 1 1 2 3 4 100 25 3 4 \
2 1 2 3 4 100 25 3 4 \
3 1 2 3 4 0 0 0 0 4 1 2 3 4 0 0 0 0 \
5 0 0 0 0 10 20 30 40 6 0 0 0 0 10 20 30 40 ]
finish_test

View File

@@ -1,7 +1,7 @@
# #
# Run this Tcl script to generate the sqlite.html file. # Run this Tcl script to generate the sqlite.html file.
# #
set rcsid {$Id: lang.tcl,v 1.33 2002/05/06 11:47:33 drh Exp $} set rcsid {$Id: lang.tcl,v 1.34 2002/05/15 08:30:15 danielk1977 Exp $}
puts {<html> puts {<html>
<head> <head>
@@ -54,6 +54,8 @@ foreach {section} [lsort -index 0 -dictionary {
{{ON CONFLICT clause} conflict} {{ON CONFLICT clause} conflict}
{{CREATE VIEW} createview} {{CREATE VIEW} createview}
{{DROP VIEW} dropview} {{DROP VIEW} dropview}
{{CREATE TRIGGER} createtrigger}
{{DROP TRIGGER} droptrigger}
}] { }] {
puts "<li><a href=\"#[lindex $section 1]\">[lindex $section 0]</a></li>" puts "<li><a href=\"#[lindex $section 1]\">[lindex $section 0]</a></li>"
} }
@@ -1089,6 +1091,129 @@ the database backend and VACUUM has become a no-op.
</p> </p>
} }
Section {CREATE TRIGGER} createtrigger
Syntax {sql-statement} {
CREATE TRIGGER <trigger-name> [ BEFORE | AFTER ]
<database-event>
<trigger-action>
}
Syntax {database-event} {
DELETE |
INSERT |
UPDATE |
UPDATE OF <column-list>
ON <table-name>
}
Syntax {trigger-action} {
[ FOR EACH ROW ] [ WHEN <expression> ]
BEGIN
<trigger-step> ; [ <trigger-step> ; ]*
END
}
Syntax {trigger-step} {
<update-statement> | <insert-statement> |
<delete-statement> | <select-statement>
}
puts {
<p>The CREATE TRIGGER statement is used to add triggers to the
database schema. Triggers are database operations (the <i>trigger-action</i>)
that are automatically performed when a specified database event (the
<i>database-event</i>) occurs. </p>
<p>A trigger may be specified to fire whenever a DELETE, INSERT or UPDATE of a
particular database table occurs, or whenever an UPDATE of one or more
specified columns of a table are updated.</p>
<p>At this time SQLite supports only FOR EACH ROW triggers, not FOR EACH
STATEMENT triggers. Hence explicitly specifying FOR EACH ROW is optional. FOR
EACH ROW implies that the SQL statements specified as <i>trigger-steps</i>
may be executed (depending on the WHEN clause) for each database row being
inserted, updated or deleted by the statement causing the trigger to fire.</p>
<p>Both the WHEN clause and the <i>trigger-steps</i> may access elements of
the row being inserted, deleted or updated using references of the form
"NEW.<i>column-name</i>" and "OLD.<i>column-name</i>", where
<i>column-name</i> is the name of a column from the table that the trigger
is associated with. OLD and NEW references may only be used in triggers on
<i>trigger-event</i>s for which they are relevant, as follows:</p>
<table border=0 cellpadding=10>
<tr>
<td valign="top" align="right" width=120><i>INSERT</i></td>
<td valign="top">NEW references are valid</td>
</tr>
<tr>
<td valign="top" align="right" width=120><i>UPDATE</i></td>
<td valign="top">NEW and OLD references are valid</td>
</tr>
<tr>
<td valign="top" align="right" width=120><i>DELETE</i></td>
<td valign="top">OLD references are valid</td>
</tr>
</table>
</p>
<p>If a WHEN clause is supplied, the SQL statements specified as <i>trigger-steps</i> are only executed for rows for which the WHEN clause is true. If no WHEN clause is supplied, the SQL statements are executed for all rows.</p>
<p>The specified <i>trigger-time</i> determines when the <i>trigger-steps</i>
will be executed relative to the insertion, modification or removal of the
associated row.</p>
<p>An ON CONFLICT clause may be specified as part of an UPDATE or INSERT
<i>trigger-step</i>. However if an ON CONFLICT clause is specified as part of
the statement causing the trigger to fire, then this conflict handling
policy is used instead.</p>
<p>Triggers are automatically dropped when the table that they are
associated with is dropped.</p>
<p>Triggers may be created on views, as well as ordinary tables. If one or
more INSERT, DELETE or UPDATE triggers are defined on a view, then it is not
an error to execute an INSERT, DELETE or UPDATE statement on the view,
respectively. Thereafter, executing an INSERT, DELETE or UPDATE on the view
causes the associated triggers to fire. The real tables underlying the view
are not modified (except possibly explicitly, by a trigger program).</p>
<p><b>Example:</b></p>
<p>Assuming that customer records are stored in the "customers" table, and
that order records are stored in the "orders" table, the following trigger
ensures that all associated orders are redirected when a customer changes
his or her address:</p>
}
Example {
CREATE TRIGGER update_customer_address UPDATE OF address ON customers
BEGIN
UPDATE orders SET address = new.address WHERE customer_name = old.name;
END;
}
puts {
<p>With this trigger installed, executing the statement:</p>
}
Example {
UPDATE customers SET address = '1 Main St.' WHERE name = 'Jack Jones';
}
puts {
<p>causes the following to be automatically executed:</p>
}
Example {
UPDATE orders SET address = '1 Main St.' WHERE customer_name = 'Jack Jones';
}
Section {DROP TRIGGER} droptrigger
Syntax {sql-statement} {
DROP TRIGGER <trigger-name>
}
puts {
<p>Used to drop a trigger from the database schema. Note that triggers
are automatically dropped when the associated table is dropped.</p>
}
puts { puts {
<p><hr /></p> <p><hr /></p>