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:
41
manifest
41
manifest
@@ -1,5 +1,5 @@
|
||||
C Version\s2.4.12\s(CVS\s561)
|
||||
D 2002-05-10T14:41:54
|
||||
C Added\sFOR\sEACH\sROW\striggers\sfunctionality\s(CVS\s562)
|
||||
D 2002-05-15T08:30:13
|
||||
F Makefile.in 50f1b3351df109b5774771350d8c1b8d3640130d
|
||||
F Makefile.template 89e373b2dad0321df00400fa968dc14b61a03296
|
||||
F README a4c0ba11354ef6ba0776b400d057c59da47a4cc0
|
||||
@@ -20,40 +20,41 @@ F sqlite.1 83f4a9d37bdf2b7ef079a82d54eaf2e3509ee6ea
|
||||
F src/TODO af7f3cab0228e34149cf98e073aa83d45878e7e6
|
||||
F src/btree.c 7dd7ddc66459982dd0cb9800958c1f8d65a32d9f
|
||||
F src/btree.h 8abeabfe6e0b1a990b64fa457592a6482f6674f3
|
||||
F src/build.c 6a5064503250e7b8cdd9285970d01522df8468f7
|
||||
F src/delete.c 6a6b8192cdff5e4b083da3bc63de099f3790d01f
|
||||
F src/build.c cbf1b552d381c3f94baad9be2defbc60a158ac64
|
||||
F src/delete.c 392159781f9dff5f07ce2cb7d3a3a184eb38c0ab
|
||||
F src/encode.c 346b12b46148506c32038524b95c4631ab46d760
|
||||
F src/expr.c cf8d2ea17e419fc83b23e080195b2952e0be4164
|
||||
F src/expr.c 6888e37e4eecdc20567aedd442328df752465723
|
||||
F src/func.c a31dcba85bc2ecb9b752980289cf7e6cd0cafbce
|
||||
F src/hash.c cc259475e358baaf299b00a2c7370f2b03dda892
|
||||
F src/hash.h dca065dda89d4575f3176e75e9a3dc0f4b4fb8b9
|
||||
F src/insert.c 31233f44fc79edbb43523a830e54736a8e222ff4
|
||||
F src/main.c 0a4643660b2a3dee3427a10fcea336756526c27c
|
||||
F src/insert.c 9f89b395e25f2a9eaea841fa736a4036d33d2b24
|
||||
F src/main.c 6bc0b3dd014f6af13007472581593e87b2797139
|
||||
F src/md5.c b2b1a34fce66ceca97f4e0dabc20be8be7933c92
|
||||
F src/os.c 5ab8b6b4590d0c1ab8e96c67996c170e4462e0fc
|
||||
F src/os.h 4a361fccfbc4e7609b3e1557f604f94c1e96ad10
|
||||
F src/pager.c ba5740104cc27b342cd43eebfdc44d60f64a3ded
|
||||
F src/pager.h 6fddfddd3b73aa8abc081b973886320e3c614f0e
|
||||
F src/parse.y 83850a81fe9170d32eb683e77d7602736c663e34
|
||||
F src/parse.y 164789531d0c6a2c28fb4baded14afc1be4bd4aa
|
||||
F src/printf.c 300a90554345751f26e1fc0c0333b90a66110a1d
|
||||
F src/random.c 19e8e00fe0df32a742f115773f57651be327cabe
|
||||
F src/select.c 1b623a7d826ec7c245bc542b665d61724da2a62d
|
||||
F src/shell.c 5acbe59e137d60d8efd975c683dbea74ab626530
|
||||
F src/shell.tcl 27ecbd63dd88396ad16d81ab44f73e6c0ea9d20e
|
||||
F src/sqlite.h.in 0038faa6d642de06b91143ee65a131bd831d020b
|
||||
F src/sqliteInt.h b37d2d28e4ca3dcf67772bf937aa12b171d9610b
|
||||
F src/sqliteInt.h a96603825503c5bbd095f1ac34ce1023f89a908e
|
||||
F src/table.c eed2098c9b577aa17f8abe89313a9c4413f57d63
|
||||
F src/tclsqlite.c 9300c9606a38bc0c75d6c0bc8a6197ab979353d1
|
||||
F src/test1.c 09d95048b66ce6dcd2bae90f443589043d7d631e
|
||||
F src/test2.c 669cc22781c6461a273416ec1a7414d25c081730
|
||||
F src/test3.c 4e52fff8b01f08bd202f7633feda5639b7ba2b5e
|
||||
F src/threadtest.c 81f0598e0f031c1bd506af337fdc1b7e8dff263f
|
||||
F src/tokenize.c 5624d342601f616157ba266abccc1368a5afee70
|
||||
F src/update.c 7dd714a6a7fa47f849ebb36b6d915974d6c6accb
|
||||
F src/tokenize.c f12f78c58b2a79ea4eee880efad63a328e103c62
|
||||
F src/trigger.c b8df3e8f0952979bbbcbd0cb05b7d564924a3282
|
||||
F src/update.c 2e8becd1cd3a597f74f8879e2c246cca5d20a119
|
||||
F src/util.c 707c30f8c13cddace7c08556ac450c0b786660b3
|
||||
F src/vdbe.c c957417fa83b5fb717dcd81204c253125b3e7e0c
|
||||
F src/vdbe.h 67840a462e1daedb958cca0ccc97db140d3d9152
|
||||
F src/where.c 5e3e97adfa5800378f2ed45bb9312dd3a70e239c
|
||||
F src/vdbe.c 428d7dba1fb84a3da6170c3cb387d177c315a72a
|
||||
F src/vdbe.h 126a651ba26f05de075dcc6da5466244a31af6b8
|
||||
F src/where.c 3138c1b44193ab5f432919ab25e49f3d97bd6108
|
||||
F test/all.test e4d3821eeba751829b419cd47814bd20af4286d1
|
||||
F test/bigrow.test 8ab252dba108f12ad64e337b0f2ff31a807ac578
|
||||
F test/btree.test bf326f546a666617367a7033fa2c07451bd4f8e1
|
||||
@@ -98,6 +99,8 @@ F test/tclsqlite.test 79deeffd7cd637ca0f06c5dbbf2f44d272079533
|
||||
F test/temptable.test daa83489eea2e9aaeeece09675c28be84c72cb67
|
||||
F test/tester.tcl dc1b56bd628b487e4d75bfd1e7480b5ed8810ac6
|
||||
F test/trans.test ae0b9a82d5d34122c3a3108781eb8d078091ccee
|
||||
F test/trigger1.test 06dd47935cf38ce5de0b232e7b61aad57685bae1
|
||||
F test/trigger2.test 662818d5cc3313c14819df1c9084c119057a0bde
|
||||
F test/unique.test 07776624b82221a80c8b4138ce0dd8b0853bb3ea
|
||||
F test/update.test 3cf1ca0565f678063c2dfa9a7948d2d66ae1a778
|
||||
F test/vacuum.test 059871b312eb910bbe49dafde1d01490cc2c6bbe
|
||||
@@ -124,14 +127,14 @@ F www/dynload.tcl 02eb8273aa78cfa9070dd4501dca937fb22b466c
|
||||
F www/faq.tcl 45bdb18b75ac3aa1befec42985fb892413aac0bb
|
||||
F www/formatchng.tcl 2ce21ff30663fad6618198fe747ce675df577590
|
||||
F www/index.tcl d0c52fbf031d0a3ee6d9d77aa669d5a4b24b6130
|
||||
F www/lang.tcl d47800eb1da14a2ea501c6088beccc4001fb0486
|
||||
F www/lang.tcl a22cf9eff51e65ec5aa39b1efb5b7952d800ac06
|
||||
F www/mingw.tcl f1c7c0a7f53387dd9bb4f8c7e8571b7561510ebc
|
||||
F www/opcode.tcl bdec8ef9f100dbd87bbef8976c54b88e43fd8ccc
|
||||
F www/speed.tcl da8afcc1d3ccc5696cfb388a68982bc3d9f7f00f
|
||||
F www/sqlite.tcl 8b5884354cb615049aed83039f8dfe1552a44279
|
||||
F www/tclsqlite.tcl 1db15abeb446aad0caf0b95b8b9579720e4ea331
|
||||
F www/vdbe.tcl 2013852c27a02a091d39a766bc87cff329f21218
|
||||
P 232b7ef2c8207eb6d2564a641446267d3dec97af
|
||||
R 3b5832b01c3219f88712622031d79de7
|
||||
U drh
|
||||
Z 49360959ab78dbfb5d6ce76a90c8f01e
|
||||
P 06cdaf1c80f7bc25fc555c7c8a35258faed2d2e9
|
||||
R f9f4dba69771590feeaaa99270d2ac88
|
||||
U danielk1977
|
||||
Z 1a13ba7db9cc1bf25e7e68999e113789
|
||||
|
@@ -1 +1 @@
|
||||
06cdaf1c80f7bc25fc555c7c8a35258faed2d2e9
|
||||
794bf67b6b36fce8854d5daff12f21dbb943240c
|
80
src/build.c
80
src/build.c
@@ -25,7 +25,7 @@
|
||||
** ROLLBACK
|
||||
** 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 <ctype.h>
|
||||
@@ -250,6 +250,22 @@ void sqliteCommitInternalChanges(sqlite *db){
|
||||
sqliteUnlinkAndDeleteIndex(db, pIndex);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -304,6 +320,48 @@ void sqliteRollbackInternalChanges(sqlite *db){
|
||||
assert( pOld==0 || pOld==p );
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -595,7 +653,7 @@ void sqliteAddPrimaryKey(Parse *pParse, IdList *pList, int onError){
|
||||
** and the probability of hitting the same cookie value is only
|
||||
** 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 ){
|
||||
db->next_cookie = db->schema_cookie + sqliteRandomByte() + 1;
|
||||
db->flags |= SQLITE_InternChanges;
|
||||
@@ -1036,6 +1094,13 @@ void sqliteDropTable(Parse *pParse, Token *pName, int isView){
|
||||
};
|
||||
Index *pIdx;
|
||||
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 ){
|
||||
base = sqliteVdbeAddOpList(v, ArraySize(dropTable), dropTable);
|
||||
sqliteVdbeChangeP3(v, base+2, pTable->zName, 0);
|
||||
@@ -1653,6 +1718,7 @@ void sqliteBeginWriteOperation(Parse *pParse){
|
||||
Vdbe *v;
|
||||
v = sqliteGetVdbe(pParse);
|
||||
if( v==0 ) return;
|
||||
if (pParse->trigStack) return; /* if this is in a trigger */
|
||||
if( (pParse->db->flags & SQLITE_InTrans)==0 ){
|
||||
sqliteVdbeAddOp(v, OP_Transaction, 0, 0);
|
||||
sqliteVdbeAddOp(v, OP_VerifyCookie, pParse->db->schema_cookie, 0);
|
||||
@@ -1672,6 +1738,7 @@ void sqliteBeginMultiWriteOperation(Parse *pParse){
|
||||
Vdbe *v;
|
||||
v = sqliteGetVdbe(pParse);
|
||||
if( v==0 ) return;
|
||||
if (pParse->trigStack) return; /* if this is in a trigger */
|
||||
if( (pParse->db->flags & SQLITE_InTrans)==0 ){
|
||||
sqliteVdbeAddOp(v, OP_Transaction, 0, 0);
|
||||
sqliteVdbeAddOp(v, OP_VerifyCookie, pParse->db->schema_cookie, 0);
|
||||
@@ -1689,6 +1756,7 @@ void sqliteBeginMultiWriteOperation(Parse *pParse){
|
||||
*/
|
||||
void sqliteEndWriteOperation(Parse *pParse){
|
||||
Vdbe *v;
|
||||
if (pParse->trigStack) return; /* if this is in a trigger */
|
||||
v = sqliteGetVdbe(pParse);
|
||||
if( v==0 ) return;
|
||||
if( pParse->db->flags & SQLITE_InTrans ){
|
||||
@@ -1915,6 +1983,14 @@ void sqlitePragma(Parse *pParse, Token *pLeft, Token *pRight, int minusFlag){
|
||||
}
|
||||
}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( getBoolean(zRight) ){
|
||||
db->flags |= SQLITE_VdbeTrace;
|
||||
|
96
src/delete.c
96
src/delete.c
@@ -12,7 +12,7 @@
|
||||
** This file contains C code routines that are called by the parser
|
||||
** to handle DELETE FROM statements.
|
||||
**
|
||||
** $Id: delete.c,v 1.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"
|
||||
|
||||
@@ -84,6 +84,8 @@ void sqliteDeleteFrom(
|
||||
sqlite *db; /* Main database structure */
|
||||
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 ){
|
||||
pTabList = 0;
|
||||
@@ -91,6 +93,31 @@ void sqliteDeleteFrom(
|
||||
}
|
||||
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
|
||||
** put in an IdList structure because some of the subroutines we
|
||||
** will be calling are designed to work with multiple tables and expect
|
||||
@@ -102,6 +129,9 @@ void sqliteDeleteFrom(
|
||||
pTab = pTabList->a[0].pTab;
|
||||
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.
|
||||
*/
|
||||
base = pParse->nTab++;
|
||||
@@ -118,7 +148,10 @@ void sqliteDeleteFrom(
|
||||
*/
|
||||
v = sqliteGetVdbe(pParse);
|
||||
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
|
||||
** we are counting rows.
|
||||
@@ -130,7 +163,7 @@ void sqliteDeleteFrom(
|
||||
/* Special case: A DELETE without a WHERE clause deletes everything.
|
||||
** It is easier just to erase the whole table.
|
||||
*/
|
||||
if( pWhere==0 ){
|
||||
if( pWhere==0 && !row_triggers_exist){
|
||||
if( db->flags & SQLITE_CountRows ){
|
||||
/* If counting rows deleted, just count the total number of
|
||||
** entries in the table. */
|
||||
@@ -176,17 +209,66 @@ void sqliteDeleteFrom(
|
||||
** because deleting an item can change the scan order.
|
||||
*/
|
||||
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;
|
||||
sqliteVdbeAddOp(v, openOp, base, pTab->tnum);
|
||||
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);
|
||||
sqliteGenerateRowDelete(v, pTab, base, 1);
|
||||
|
||||
if (!row_triggers_exist)
|
||||
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);
|
||||
sqliteVdbeResolveLabel(v, end);
|
||||
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);
|
||||
|
||||
|
29
src/expr.c
29
src/expr.c
@@ -12,7 +12,7 @@
|
||||
** This file contains routines used for analyzing expressions and
|
||||
** for generating VDBE code that evaluates expressions in SQLite.
|
||||
**
|
||||
** $Id: expr.c,v 1.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"
|
||||
|
||||
@@ -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) ){
|
||||
cnt = 1;
|
||||
pExpr->iColumn = -1;
|
||||
|
255
src/insert.c
255
src/insert.c
@@ -12,7 +12,7 @@
|
||||
** This file contains C code routines that are called by the parser
|
||||
** to handle INSERT statements in SQLite.
|
||||
**
|
||||
** $Id: insert.c,v 1.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"
|
||||
|
||||
@@ -40,7 +40,7 @@ void sqliteInsert(
|
||||
int onError /* How to handle constraint errors */
|
||||
){
|
||||
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 */
|
||||
Vdbe *v; /* Generate code into this virtual machine */
|
||||
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 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;
|
||||
db = pParse->db;
|
||||
|
||||
@@ -60,21 +63,48 @@ void sqliteInsert(
|
||||
*/
|
||||
zTab = sqliteTableNameFromToken(pTableName);
|
||||
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);
|
||||
zTab = 0;
|
||||
|
||||
if( pTab==0 ) goto insert_cleanup;
|
||||
assert( pTab->pSelect==0 ); /* This table is not a VIEW */
|
||||
|
||||
/* Allocate a VDBE
|
||||
*/
|
||||
v = sqliteGetVdbe(pParse);
|
||||
if( v==0 ) goto insert_cleanup;
|
||||
if( pSelect ){
|
||||
if( pSelect || row_triggers_exist ){
|
||||
sqliteBeginMultiWriteOperation(pParse);
|
||||
}else{
|
||||
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
|
||||
** is coming from a SELECT statement, then this step has to generate
|
||||
** all the code to implement the SELECT statement and leave the data
|
||||
@@ -173,25 +203,29 @@ void sqliteInsert(
|
||||
keyColumn = pTab->iPKey;
|
||||
}
|
||||
|
||||
/* Open cursors into the table that is received the new data and
|
||||
** all indices of that table.
|
||||
*/
|
||||
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;
|
||||
|
||||
/* Open the temp table for FOR EACH ROW triggers */
|
||||
if (row_triggers_exist)
|
||||
sqliteVdbeAddOp(v, OP_OpenTemp, newIdx, 0);
|
||||
|
||||
/* 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 */
|
||||
}
|
||||
|
||||
/* 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
|
||||
** 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
|
||||
@@ -203,91 +237,159 @@ void sqliteInsert(
|
||||
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
|
||||
** record number is a randomly generate integer created by NewRecno
|
||||
** except when the table has an INTEGER PRIMARY KEY column, in which
|
||||
** case the record number is the same as that column.
|
||||
*/
|
||||
if( keyColumn>=0 ){
|
||||
if( srcTab>=0 ){
|
||||
sqliteVdbeAddOp(v, OP_Column, srcTab, keyColumn);
|
||||
}else{
|
||||
int addr;
|
||||
sqliteExprCode(pParse, pList->a[keyColumn].pExpr);
|
||||
if (!pTab->pSelect) {
|
||||
if( keyColumn>=0 ){
|
||||
if( srcTab>=0 ){
|
||||
sqliteVdbeAddOp(v, OP_Column, srcTab, keyColumn);
|
||||
}else{
|
||||
int addr;
|
||||
sqliteExprCode(pParse, pList->a[keyColumn].pExpr);
|
||||
|
||||
/* If the PRIMARY KEY expression is NULL, then use OP_NewRecno
|
||||
** to generate a unique primary key value.
|
||||
*/
|
||||
addr = sqliteVdbeAddOp(v, OP_Dup, 0, 1);
|
||||
sqliteVdbeAddOp(v, OP_NotNull, 0, addr+4);
|
||||
sqliteVdbeAddOp(v, OP_Pop, 1, 0);
|
||||
/* If the PRIMARY KEY expression is NULL, then use OP_NewRecno
|
||||
** to generate a unique primary key value.
|
||||
*/
|
||||
addr = sqliteVdbeAddOp(v, OP_Dup, 0, 1);
|
||||
sqliteVdbeAddOp(v, OP_NotNull, 0, addr+4);
|
||||
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_MustBeInt, 0, 0);
|
||||
}else{
|
||||
sqliteVdbeAddOp(v, OP_NewRecno, base, 0);
|
||||
}
|
||||
|
||||
/* Push onto the stack, data for all columns of the new entry, beginning
|
||||
** with the first column.
|
||||
*/
|
||||
for(i=0; i<pTab->nCol; i++){
|
||||
if( i==pTab->iPKey ){
|
||||
/* The value of the INTEGER PRIMARY KEY column is always a NULL.
|
||||
** Whenever this column is read, the record number will be substituted
|
||||
** in its place. So will fill this column with a NULL to avoid
|
||||
** taking up data space with information that will never be used. */
|
||||
sqliteVdbeAddOp(v, OP_String, 0, 0);
|
||||
continue;
|
||||
}
|
||||
if( pColumn==0 ){
|
||||
j = i;
|
||||
}else{
|
||||
for(j=0; j<pColumn->nId; j++){
|
||||
if( pColumn->a[j].idx==i ) break;
|
||||
/* Push onto the stack, data for all columns of the new entry, beginning
|
||||
** with the first column.
|
||||
*/
|
||||
for(i=0; i<pTab->nCol; i++){
|
||||
if( i==pTab->iPKey ){
|
||||
/* The value of the INTEGER PRIMARY KEY column is always a NULL.
|
||||
** Whenever this column is read, the record number will be substituted
|
||||
** in its place. So will fill this column with a NULL to avoid
|
||||
** taking up data space with information that will never be used. */
|
||||
sqliteVdbeAddOp(v, OP_String, 0, 0);
|
||||
continue;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
/* Generate code to check constraints and generate index keys and
|
||||
** do the insertion.
|
||||
*/
|
||||
endOfLoop = sqliteVdbeMakeLabel(v);
|
||||
sqliteGenerateConstraintChecks(pParse, pTab, base, 0,0,0,onError,endOfLoop);
|
||||
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
|
||||
** do the insertion.
|
||||
*/
|
||||
endOfLoop = sqliteVdbeMakeLabel(v);
|
||||
sqliteGenerateConstraintChecks(pParse, pTab, base, 0,0,0, onError, endOfLoop);
|
||||
sqliteCompleteInsertion(pParse, pTab, base, 0,0,0);
|
||||
if (row_triggers_exist) {
|
||||
/* Close all tables opened */
|
||||
if (!pTab->pSelect) {
|
||||
sqliteVdbeAddOp(v, OP_Close, base, 0);
|
||||
for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){
|
||||
sqliteVdbeAddOp(v, OP_Close, idx+base, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the count of rows that are inserted
|
||||
*/
|
||||
if( (db->flags & SQLITE_CountRows)!=0 ){
|
||||
sqliteVdbeAddOp(v, OP_AddImm, 1, 0);
|
||||
/* Code AFTER triggers */
|
||||
if (
|
||||
sqliteCodeRowTrigger(pParse, TK_INSERT, 0, TK_AFTER, pTab, newIdx, -1,
|
||||
onError)
|
||||
) goto insert_cleanup;
|
||||
}
|
||||
|
||||
/* The bottom of the loop, if the data source is a SELECT statement
|
||||
*/
|
||||
*/
|
||||
sqliteVdbeResolveLabel(v, endOfLoop);
|
||||
if( srcTab>=0 ){
|
||||
sqliteVdbeAddOp(v, OP_Next, srcTab, iCont);
|
||||
sqliteVdbeResolveLabel(v, iBreak);
|
||||
sqliteVdbeAddOp(v, OP_Close, srcTab, 0);
|
||||
}
|
||||
sqliteVdbeAddOp(v, OP_Close, base, 0);
|
||||
for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){
|
||||
sqliteVdbeAddOp(v, OP_Close, idx+base, 0);
|
||||
|
||||
if (!row_triggers_exist) {
|
||||
/* 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);
|
||||
|
||||
/*
|
||||
** 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_ColumnName, 0, 0);
|
||||
sqliteVdbeChangeP3(v, -1, "rows inserted", P3_STATIC);
|
||||
@@ -297,6 +399,7 @@ void sqliteInsert(
|
||||
insert_cleanup:
|
||||
if( pList ) sqliteExprListDelete(pList);
|
||||
if( pSelect ) sqliteSelectDelete(pSelect);
|
||||
if ( zTab ) sqliteFree(zTab);
|
||||
sqliteIdListDelete(pColumn);
|
||||
}
|
||||
|
||||
@@ -573,7 +676,7 @@ void sqliteCompleteInsertion(
|
||||
sqliteVdbeAddOp(v, OP_IdxPut, base+i+1, 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 ){
|
||||
sqliteVdbeAddOp(v, OP_Pop, 1, 0);
|
||||
}
|
||||
|
36
src/main.c
36
src/main.c
@@ -14,7 +14,7 @@
|
||||
** other files are for internal use by SQLite and should not be
|
||||
** 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 "os.h"
|
||||
@@ -326,6 +326,8 @@ sqlite *sqlite_open(const char *zFilename, int mode, char **pzErrMsg){
|
||||
if( db==0 ) goto no_mem_on_open;
|
||||
sqliteHashInit(&db->tblHash, 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->idxDrop, SQLITE_HASH_POINTER, 0);
|
||||
sqliteHashInit(&db->aFunc, SQLITE_HASH_STRING, 1);
|
||||
@@ -383,11 +385,29 @@ no_mem_on_open:
|
||||
static void clearHashTable(sqlite *db, int preserveTemps){
|
||||
HashElem *pElem;
|
||||
Hash temp1;
|
||||
Hash temp2;
|
||||
assert( sqliteHashFirst(&db->tblDrop)==0 ); /* There can not be uncommitted */
|
||||
assert( sqliteHashFirst(&db->idxDrop)==0 ); /* DROP TABLEs or DROP INDEXs */
|
||||
temp1 = db->tblHash;
|
||||
sqliteHashInit(&db->tblHash, SQLITE_HASH_STRING, 0);
|
||||
temp2 = db->trigHash;
|
||||
sqliteHashInit(&db->trigHash, SQLITE_HASH_STRING, 0);
|
||||
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)){
|
||||
Table *pTab = sqliteHashData(pElem);
|
||||
if( preserveTemps && pTab->isTemp ){
|
||||
@@ -413,6 +433,7 @@ static void clearHashTable(sqlite *db, int preserveTemps){
|
||||
}
|
||||
}
|
||||
sqliteHashClear(&temp1);
|
||||
|
||||
db->flags &= ~SQLITE_Initialized;
|
||||
}
|
||||
|
||||
@@ -458,6 +479,7 @@ void sqlite_close(sqlite *db){
|
||||
*/
|
||||
int sqlite_complete(const char *zSql){
|
||||
int isComplete = 0;
|
||||
int seenCreate = 0;
|
||||
while( *zSql ){
|
||||
switch( *zSql ){
|
||||
case ';': {
|
||||
@@ -501,6 +523,16 @@ int sqlite_complete(const char *zSql){
|
||||
break;
|
||||
}
|
||||
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;
|
||||
break;
|
||||
}
|
||||
|
67
src/parse.y
67
src/parse.y
@@ -14,7 +14,7 @@
|
||||
** the parser. Lemon will also generate a header file containing
|
||||
** 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_type {Token}
|
||||
@@ -33,6 +33,11 @@
|
||||
** A structure for holding two integers
|
||||
*/
|
||||
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
|
||||
@@ -628,3 +633,63 @@ number(A) ::= INTEGER(X). {A = X;}
|
||||
number(A) ::= FLOAT(X). {A = X;}
|
||||
plus_opt ::= PLUS.
|
||||
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);
|
||||
}
|
||||
|
||||
|
@@ -11,7 +11,7 @@
|
||||
*************************************************************************
|
||||
** 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 "hash.h"
|
||||
@@ -144,6 +144,9 @@ typedef struct WhereLevel WhereLevel;
|
||||
typedef struct Select Select;
|
||||
typedef struct AggExpr AggExpr;
|
||||
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
|
||||
@@ -170,6 +173,9 @@ struct sqlite {
|
||||
int magic; /* Magic number for detect library misuse */
|
||||
int nChange; /* Number of rows changed */
|
||||
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 hasPrimKey; /* True if there exists a primary key */
|
||||
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 */
|
||||
int schemaVerified; /* True if an OP_VerifySchema has been coded someplace
|
||||
** 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
|
||||
*/
|
||||
@@ -662,3 +726,5 @@ void sqliteRegisterBuildinFunctions(sqlite*);
|
||||
int sqliteSafetyOn(sqlite*);
|
||||
int sqliteSafetyOff(sqlite*);
|
||||
int sqliteSafetyCheck(sqlite*);
|
||||
|
||||
void changeCookie(sqlite *);
|
||||
|
@@ -15,7 +15,7 @@
|
||||
** individual tokens and sends those tokens one-by-one over to the
|
||||
** 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 "os.h"
|
||||
@@ -39,10 +39,12 @@ struct Keyword {
|
||||
*/
|
||||
static Keyword aKeywordTable[] = {
|
||||
{ "ABORT", 0, TK_ABORT, 0 },
|
||||
{ "AFTER", 0, TK_AFTER, 0 },
|
||||
{ "ALL", 0, TK_ALL, 0 },
|
||||
{ "AND", 0, TK_AND, 0 },
|
||||
{ "AS", 0, TK_AS, 0 },
|
||||
{ "ASC", 0, TK_ASC, 0 },
|
||||
{ "BEFORE", 0, TK_BEFORE, 0 },
|
||||
{ "BEGIN", 0, TK_BEGIN, 0 },
|
||||
{ "BETWEEN", 0, TK_BETWEEN, 0 },
|
||||
{ "BY", 0, TK_BY, 0 },
|
||||
@@ -61,10 +63,12 @@ static Keyword aKeywordTable[] = {
|
||||
{ "DISTINCT", 0, TK_DISTINCT, 0 },
|
||||
{ "DROP", 0, TK_DROP, 0 },
|
||||
{ "END", 0, TK_END, 0 },
|
||||
{ "EACH", 0, TK_EACH, 0 },
|
||||
{ "ELSE", 0, TK_ELSE, 0 },
|
||||
{ "EXCEPT", 0, TK_EXCEPT, 0 },
|
||||
{ "EXPLAIN", 0, TK_EXPLAIN, 0 },
|
||||
{ "FAIL", 0, TK_FAIL, 0 },
|
||||
{ "FOR", 0, TK_FOR, 0 },
|
||||
{ "FROM", 0, TK_FROM, 0 },
|
||||
{ "GLOB", 0, TK_GLOB, 0 },
|
||||
{ "GROUP", 0, TK_GROUP, 0 },
|
||||
@@ -73,6 +77,7 @@ static Keyword aKeywordTable[] = {
|
||||
{ "IN", 0, TK_IN, 0 },
|
||||
{ "INDEX", 0, TK_INDEX, 0 },
|
||||
{ "INSERT", 0, TK_INSERT, 0 },
|
||||
{ "INSTEAD", 0, TK_INSTEAD, 0 },
|
||||
{ "INTERSECT", 0, TK_INTERSECT, 0 },
|
||||
{ "INTO", 0, TK_INTO, 0 },
|
||||
{ "IS", 0, TK_IS, 0 },
|
||||
@@ -83,6 +88,7 @@ static Keyword aKeywordTable[] = {
|
||||
{ "NOT", 0, TK_NOT, 0 },
|
||||
{ "NOTNULL", 0, TK_NOTNULL, 0 },
|
||||
{ "NULL", 0, TK_NULL, 0 },
|
||||
{ "OF", 0, TK_OF, 0 },
|
||||
{ "OFFSET", 0, TK_OFFSET, 0 },
|
||||
{ "ON", 0, TK_ON, 0 },
|
||||
{ "OR", 0, TK_OR, 0 },
|
||||
@@ -91,6 +97,7 @@ static Keyword aKeywordTable[] = {
|
||||
{ "PRIMARY", 0, TK_PRIMARY, 0 },
|
||||
{ "REPLACE", 0, TK_REPLACE, 0 },
|
||||
{ "ROLLBACK", 0, TK_ROLLBACK, 0 },
|
||||
{ "ROW", 0, TK_ROW, 0 },
|
||||
{ "SELECT", 0, TK_SELECT, 0 },
|
||||
{ "SET", 0, TK_SET, 0 },
|
||||
{ "TABLE", 0, TK_TABLE, 0 },
|
||||
@@ -98,6 +105,7 @@ static Keyword aKeywordTable[] = {
|
||||
{ "TEMPORARY", 0, TK_TEMP, 0 },
|
||||
{ "THEN", 0, TK_THEN, 0 },
|
||||
{ "TRANSACTION", 0, TK_TRANSACTION, 0 },
|
||||
{ "TRIGGER", 0, TK_TRIGGER, 0 },
|
||||
{ "UNION", 0, TK_UNION, 0 },
|
||||
{ "UNIQUE", 0, TK_UNIQUE, 0 },
|
||||
{ "UPDATE", 0, TK_UPDATE, 0 },
|
||||
|
643
src/trigger.c
Normal file
643
src/trigger.c
Normal 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;
|
||||
}
|
||||
|
||||
|
126
src/update.c
126
src/update.c
@@ -12,7 +12,7 @@
|
||||
** This file contains C code routines that are called by the parser
|
||||
** to handle UPDATE statements.
|
||||
**
|
||||
** $Id: update.c,v 1.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"
|
||||
|
||||
@@ -47,9 +47,38 @@ void sqliteUpdate(
|
||||
Expr *pRecnoExpr; /* Expression defining the new record number */
|
||||
int openAll; /* True if all indices need to be opened */
|
||||
|
||||
int row_triggers_exist = 0;
|
||||
|
||||
int 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;
|
||||
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
|
||||
** put in an IdList structure because some of the subroutines we
|
||||
** will be calling are designed to work with multiple tables and expect
|
||||
@@ -63,6 +92,11 @@ void sqliteUpdate(
|
||||
if( aXRef==0 ) goto update_cleanup;
|
||||
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
|
||||
** WHERE clause and in the new values. Also find the column index
|
||||
** for each column to be updated in the pChanges array.
|
||||
@@ -159,17 +193,62 @@ void sqliteUpdate(
|
||||
|
||||
/* Initialize the count of updated rows
|
||||
*/
|
||||
if( db->flags & SQLITE_CountRows ){
|
||||
if( db->flags & SQLITE_CountRows && !pParse->trigStack ){
|
||||
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
|
||||
** open every index that needs updating. Note that if any
|
||||
** index could potentially invoke a REPLACE conflict resolution
|
||||
** action, then we need to open all indices because we might need
|
||||
** to be deleting some records.
|
||||
*/
|
||||
sqliteVdbeAddOp(v, OP_ListRewind, 0, 0);
|
||||
openOp = pTab->isTemp ? OP_OpenWrAux : OP_OpenWrite;
|
||||
sqliteVdbeAddOp(v, openOp, base, pTab->tnum);
|
||||
if( onError==OE_Replace ){
|
||||
@@ -197,8 +276,12 @@ void sqliteUpdate(
|
||||
** Also, the old data is needed to delete the old index entires.
|
||||
** So make the cursor point at the old record.
|
||||
*/
|
||||
addr = sqliteVdbeAddOp(v, OP_ListRead, 0, 0);
|
||||
sqliteVdbeAddOp(v, OP_Dup, 0, 0);
|
||||
if (!row_triggers_exist) {
|
||||
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);
|
||||
|
||||
/* 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( chngRecno ){
|
||||
sqliteVdbeAddOp(v, OP_Delete, 0, 0);
|
||||
sqliteVdbeAddOp(v, OP_Delete, base, 0);
|
||||
}
|
||||
|
||||
/* Create the new index entries and the new record.
|
||||
@@ -250,22 +333,49 @@ void sqliteUpdate(
|
||||
|
||||
/* Increment the row counter
|
||||
*/
|
||||
if( db->flags & SQLITE_CountRows ){
|
||||
if( db->flags & SQLITE_CountRows && !pParse->trigStack){
|
||||
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
|
||||
** all record selected by the WHERE clause have been updated.
|
||||
*/
|
||||
sqliteVdbeAddOp(v, OP_Goto, 0, addr);
|
||||
sqliteVdbeChangeP2(v, addr, sqliteVdbeCurrentAddr(v));
|
||||
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);
|
||||
|
||||
/*
|
||||
** 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_ColumnName, 0, 0);
|
||||
sqliteVdbeChangeP3(v, -1, "rows updated", P3_STATIC);
|
||||
|
49
src/vdbe.c
49
src/vdbe.c
@@ -30,7 +30,7 @@
|
||||
** But other routines are also provided to help in building up
|
||||
** 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 <ctype.h>
|
||||
@@ -249,6 +249,9 @@ struct Vdbe {
|
||||
int nCallback; /* Number of callbacks invoked so far */
|
||||
int iLimit; /* Limit on the number of callbacks remaining */
|
||||
int iOffset; /* Offset before beginning to do callbacks */
|
||||
|
||||
int keylistStackDepth;
|
||||
Keylist ** keylistStack;
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -999,6 +1002,15 @@ static void Cleanup(Vdbe *p){
|
||||
sqliteFree(p->aSet);
|
||||
p->aSet = 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",
|
||||
"NotNull", "Negative", "And", "Or",
|
||||
"Not", "Concat", "Noop", "Function",
|
||||
"Limit",
|
||||
"Limit", "PushList", "PopList",
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -4539,6 +4551,39 @@ case OP_SetFound: {
|
||||
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 *
|
||||
**
|
||||
** Pop the stack once and compare the value popped off with the
|
||||
|
@@ -15,7 +15,7 @@
|
||||
** or VDBE. The VDBE implements an abstract machine that runs a
|
||||
** 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_
|
||||
#define _SQLITE_VDBE_H_
|
||||
@@ -198,7 +198,10 @@ typedef struct VdbeOp VdbeOp;
|
||||
|
||||
#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
|
||||
|
18
src/where.c
18
src/where.c
@@ -13,7 +13,7 @@
|
||||
** the WHERE clause of SQL statements. Also found here are subroutines
|
||||
** 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"
|
||||
|
||||
@@ -216,6 +216,22 @@ WhereInfo *sqliteWhereBegin(
|
||||
*/
|
||||
for(i=0; i<nExpr; 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
|
||||
|
113
test/trigger1.test
Normal file
113
test/trigger1.test
Normal 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
597
test/trigger2.test
Normal 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
|
||||
|
127
www/lang.tcl
127
www/lang.tcl
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# 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>
|
||||
<head>
|
||||
@@ -54,6 +54,8 @@ foreach {section} [lsort -index 0 -dictionary {
|
||||
{{ON CONFLICT clause} conflict}
|
||||
{{CREATE VIEW} createview}
|
||||
{{DROP VIEW} dropview}
|
||||
{{CREATE TRIGGER} createtrigger}
|
||||
{{DROP TRIGGER} droptrigger}
|
||||
}] {
|
||||
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>
|
||||
}
|
||||
|
||||
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 {
|
||||
<p><hr /></p>
|
||||
|
Reference in New Issue
Block a user