And here's the patch. I should have been using Fossil all this time.
I'll switch to Fossil soon. For now the patch is to
sqlite\-src\-3070602.
A few more notes on the patch besides the ones I posted earlier:
- DB triggers are omitted by defining SQLITE_OMIT_TRIGGER
- DB triggers are implemented under the hood as INSERT INTO
sqlite_intview_... DEFAULT VALUES. I call these "crutch views". This
means that there's a NEW.* row for DB triggers, but this must not be
used.
- There are some XXX comments in my patch; I know how to resolve these.
The patch:
diff -ru sqlite-src-3070602-orig/src/btree.c sqlite-src-3070602/src/btree.c
--- sqlite-src-3070602-orig/src/btree.c 2011-04-17 12:53:51.000000000 -0500
+++ sqlite-src-3070602/src/btree.c 2011-05-14 17:54:12.588609187 -0500
@@ -2531,6 +2531,8 @@
sqlite3BtreeEnter(p);
btreeIntegrity(p);
+ assert( p->beginTrigFired==0 || p->inTrans==TRANS_WRITE );
+
/* If the btree is already in a write-transaction, or it
** is already in a read-transaction and a read-transaction
** is requested, this is a no-op.
@@ -2539,6 +2541,8 @@
goto trans_begun;
}
+ p->beginTrigFired = 0;
+
/* Write transactions are not possible on a read-only database */
if( pBt->readOnly && wrflag ){
rc = SQLITE_READONLY;
@@ -3142,6 +3146,7 @@
/* Set the current transaction state to TRANS_NONE and unlock the
** pager if this call closed the only read or write transaction. */
p->inTrans = TRANS_NONE;
+ p->beginTrigFired = 0;
unlockBtreeIfUnused(pBt);
}
@@ -8141,3 +8146,15 @@
pBt->doNotUseWAL = 0;
return rc;
}
+
+#ifndef SQLITE_OMIT_SHARED_TRIGGER
+int sqlite3BtreeAfterBeginTriggerFiredCheck(Btree *p){
+ assert( p->beginTrigFired==0 || p->inTrans==TRANS_WRITE );
+ return ( p->inTrans==TRANS_WRITE && p->beginTrigFired==1 );
+}
+
+void sqlite3BtreeAfterBeginTriggerFired(Btree *p){
+ assert( p->beginTrigFired==0 || p->inTrans==TRANS_WRITE );
+ p->beginTrigFired = 1;
+}
+#endif /* SQLITE_OMIT_TRIGGER */
diff -ru sqlite-src-3070602-orig/src/btree.h sqlite-src-3070602/src/btree.h
--- sqlite-src-3070602-orig/src/btree.h 2011-04-17 12:53:51.000000000 -0500
+++ sqlite-src-3070602/src/btree.h 2011-05-14 17:57:36.626577000 -0500
@@ -236,5 +236,9 @@
# define sqlite3SchemaMutexHeld(X,Y,Z) 1
#endif
+#ifdef SQLITE_OMIT_TRIGGER
+ int sqlite3BtreeAfterBeginTriggerFiredCheck(Btree *p);
+ void sqlite3BtreeAfterBeginTriggerFired(Btree *p);
+#endif /* SQLITE_OMIT_TRIGGER */
#endif /* _BTREE_H_ */
diff -ru sqlite-src-3070602-orig/src/btreeInt.h
sqlite-src-3070602/src/btreeInt.h
--- sqlite-src-3070602-orig/src/btreeInt.h 2011-04-17 12:53:51.000000000
-0500
+++ sqlite-src-3070602/src/btreeInt.h 2011-05-16 12:05:32.492119901 -0500
@@ -345,6 +345,9 @@
u8 inTrans; /* TRANS_NONE, TRANS_READ or TRANS_WRITE */
u8 sharable; /* True if we can share pBt with another db */
u8 locked; /* True if db currently has pBt locked */
+#ifndef SQLITE_OMIT_TRIGGER
+ u8 beginTrigFired; /* True if inTrans == TRANS_WRITE && BEGIN
trigger fired */
+#endif
int wantToLock; /* Number of nested calls to sqlite3BtreeEnter() */
int nBackup; /* Number of backup operations reading this btree */
Btree *pNext; /* List of other sharable Btrees from the same db */
diff -ru sqlite-src-3070602-orig/src/build.c sqlite-src-3070602/src/build.c
--- sqlite-src-3070602-orig/src/build.c 2011-04-17 12:53:51.000000000 -0500
+++ sqlite-src-3070602/src/build.c 2011-05-16 12:07:53.916119901 -0500
@@ -155,6 +155,10 @@
if( (mask & pParse->cookieMask)==0 ) continue;
sqlite3VdbeUsesBtree(v, iDb);
sqlite3VdbeAddOp2(v,OP_Transaction, iDb, (mask &
pParse->writeMask)!=0);
+#ifndef SQLITE_OMIT_TRIGGER
+ if( mask & pParse->writeMask )
+ sqlite3CodeDbTrigger(pParse, iDb, TK_AFTER, TK_BEGIN);
+#endif
if( db->init.busy==0 ){
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
sqlite3VdbeAddOp3(v, OP_VerifyCookie,
@@ -3362,6 +3366,10 @@
for(i=0; i<db->nDb; i++){
sqlite3VdbeAddOp2(v, OP_Transaction, i, (type==TK_EXCLUSIVE)+1);
sqlite3VdbeUsesBtree(v, i);
+#ifndef SQLITE_OMIT_TRIGGER
+ if( ((type==TK_EXCLUSIVE)+1) )
+ sqlite3CodeDbTrigger(pParse, i, TK_AFTER, TK_BEGIN);
+#endif
}
}
sqlite3VdbeAddOp2(v, OP_AutoCommit, 0, 0);
@@ -3383,6 +3391,12 @@
}
v = sqlite3GetVdbe(pParse);
if( v ){
+#ifndef SQLITE_OMIT_TRIGGER
+ int i;
+
+ for(i=0; i<db->nDb; i++)
+ sqlite3CodeDbTrigger(pParse, i, TK_BEFORE, TK_COMMIT);
+#endif
sqlite3VdbeAddOp2(v, OP_AutoCommit, 1, 0);
}
}
diff -ru sqlite-src-3070602-orig/src/mutex.c sqlite-src-3070602/src/mutex.c
--- sqlite-src-3070602-orig/src/mutex.c 2011-04-17 12:53:51.000000000 -0500
+++ sqlite-src-3070602/src/mutex.c 2011-05-16 12:04:03.192119900 -0500
@@ -137,7 +137,7 @@
}
}
-#ifndef NDEBUG
+#if !defined(NDEBUG) || defined(SQLITE_DEBUG)
/*
** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routine are
** intended for use inside assert() statements.
diff -ru sqlite-src-3070602-orig/src/parse.y sqlite-src-3070602/src/parse.y
--- sqlite-src-3070602-orig/src/parse.y 2011-04-17 12:53:51.000000000 -0500
+++ sqlite-src-3070602/src/parse.y 2011-05-16 12:08:31.272119902 -0500
@@ -1179,6 +1179,13 @@
sqlite3FinishTrigger(pParse, S, &all);
}
+cmd ::= createkw db_trigger_decl(A) BEGIN trigger_cmd_list(S) END(Z). {
+ Token all;
+ all.z = A.z;
+ all.n = (int)(Z.z - A.z) + Z.n;
+ sqlite3FinishTrigger(pParse, S, &all);
+}
+
trigger_decl(A) ::= temp(T) TRIGGER ifnotexists(NOERR) nm(B) dbnm(Z)
trigger_time(C) trigger_event(D)
ON fullname(E) foreach_clause when_clause(G). {
@@ -1186,6 +1193,43 @@
A = (Z.n==0?B:Z);
}
+db_trigger_decl(A) ::= temp(T) TRIGGER ifnotexists(NOERR) nm(B) dbnm(Z)
+ AFTER DATABASE CONNECT. {
+ sqlite3BeginTrigger(pParse, &B, &Z, TK_INSTEAD, TK_CONNECT, 0, 0,
0, T, NOERR);
+ A = (Z.n==0?B:Z);
+}
+db_trigger_decl(A) ::= temp(T) TRIGGER ifnotexists(NOERR) nm(B) dbnm(Z)
+ BEFORE DATABASE DISCONNECT. {
+ sqlite3ErrorMsg(pParse,
+ "BEFORE DATABASE DISCONNECT triggers not yet supported");
+ sqlite3BeginTrigger(pParse, &B, &Z, TK_INSTEAD, TK_DISCONNECT, 0,
0, 0, T, NOERR);
+ A = (Z.n==0?B:Z);
+}
+db_trigger_decl(A) ::= temp(T) TRIGGER ifnotexists(NOERR) nm(B) dbnm(Z)
+ AFTER TRANSACTION BEGIN. {
+ sqlite3BeginTrigger(pParse, &B, &Z, TK_AFTER, TK_BEGIN, 0, 0, 0, T, NOERR);
+ A = (Z.n==0?B:Z);
+}
+db_trigger_decl(A) ::= temp(T) TRIGGER ifnotexists(NOERR) nm(B) dbnm(Z)
+ BEFORE TRANSACTION COMMIT. {
+ sqlite3BeginTrigger(pParse, &B, &Z, TK_BEFORE, TK_COMMIT, 0, 0, 0, T, NOERR);
+ A = (Z.n==0?B:Z);
+}
+db_trigger_decl(A) ::= temp(T) TRIGGER ifnotexists(NOERR) nm(B) dbnm(Z)
+ AFTER TRANSACTION COMMIT. {
+ sqlite3ErrorMsg(pParse,
+ "AFTER TRANSACTION COMMIT triggers not yet supported");
+ sqlite3BeginTrigger(pParse, &B, &Z, TK_AFTER, TK_COMMIT, 0, 0, 0, T, NOERR);
+ A = (Z.n==0?B:Z);
+}
+db_trigger_decl(A) ::= temp(T) TRIGGER ifnotexists(NOERR) nm(B) dbnm(Z)
+ AFTER TRANSACTION ROLLBACK. {
+ sqlite3ErrorMsg(pParse,
+ "AFTER TRANSACTION ROLLBACK triggers not yet supported");
+ sqlite3BeginTrigger(pParse, &B, &Z, TK_AFTER, TK_ROLLBACK, 0, 0, 0,
T, NOERR);
+ A = (Z.n==0?B:Z);
+}
+
%type trigger_time {int}
trigger_time(A) ::= BEFORE. { A = TK_BEFORE; }
trigger_time(A) ::= AFTER. { A = TK_AFTER; }
diff -ru sqlite-src-3070602-orig/src/pragma.c sqlite-src-3070602/src/pragma.c
--- sqlite-src-3070602-orig/src/pragma.c 2011-04-17 12:53:51.000000000
-0500
+++ sqlite-src-3070602/src/pragma.c 2011-05-16 12:08:57.496119901 -0500
@@ -199,6 +199,9 @@
#if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER)
{ "foreign_keys", SQLITE_ForeignKeys },
#endif
+#ifndef SQLITE_OMIT_TRIGGER
+ { "connect_triggers", SQLITE_ConnTriggers },
+#endif
};
int i;
const struct sPragmaType *p;
@@ -224,6 +227,21 @@
}else{
db->flags &= ~mask;
}
+#ifndef SQLITE_OMIT_TRIGGER
+ if ( sqlite3StrICmp(zLeft, "connect_triggers") == 0 &&
+ ( db->flags & SQLITE_ConnTriggers ) &&
+ sqlite3dBTriggersExist(pParse, -1, TK_INSTEAD, TK_CONNECT) &&
+ !db->connectTrigFired ){
+ /* Fire connect trigger, ignore errors */
+ sqlite3_exec(db, "BEGIN;", 0, 0, 0);
+ sqlite3_exec(db,
+ "INSERT INTO sqlite_intview_connect DEFAULT VALUES",
+ 0, 0, 0);
+ sqlite3_exec(db, "COMMIT;", 0, 0, 0);
+ sqlite3_exec(db, "ROLLBACK;", 0, 0, 0);
+ db->connectTrigFired = 1; /* We don't reset this */
+ }
+#endif
/* Many of the flag-pragmas modify the code generated by the SQL
** compiler (eg. count_changes). So add an opcode to expire all
diff -ru sqlite-src-3070602-orig/src/sqliteInt.h
sqlite-src-3070602/src/sqliteInt.h
--- sqlite-src-3070602-orig/src/sqliteInt.h 2011-04-17 12:53:51.000000000
-0500
+++ sqlite-src-3070602/src/sqliteInt.h 2011-05-16 11:28:25.036119888 -0500
@@ -903,6 +903,10 @@
void (*xUnlockNotify)(void **, int); /* Unlock notify callback */
sqlite3 *pNextBlocked; /* Next in list of all blocked connections */
#endif
+
+#ifndef SQLITE_OMIT_TRIGGER
+ u8 connectTrigFired; /* Whether the DATABASE CONNECT trigger has fired */
+#endif
};
/*
@@ -940,6 +944,7 @@
#define SQLITE_PreferBuiltin 0x10000000 /* Preference to built-in funcs */
#define SQLITE_LoadExtension 0x20000000 /* Enable load_extension */
#define SQLITE_EnableTrigger 0x40000000 /* True to enable triggers */
+#define SQLITE_ConnTriggers 0x80000000 /* True to enable connect triggers */
/*
** Bits of the sqlite3.flags field that are used by the
@@ -2814,6 +2819,7 @@
#ifndef SQLITE_OMIT_TRIGGER
void sqlite3BeginTrigger(Parse*, Token*,Token*,int,int,IdList*,SrcList*,
Expr*,int, int);
+ int sqlite3dBTriggersExist(Parse *, int, int, int);
void sqlite3FinishTrigger(Parse*, TriggerStep*, Token*);
void sqlite3DropTrigger(Parse*, SrcList*, int);
void sqlite3DropTriggerPtr(Parse*, Trigger*);
@@ -2822,6 +2828,7 @@
void sqlite3CodeRowTrigger(Parse*, Trigger *, int, ExprList*, int, Table *,
int, int, int);
void sqlite3CodeRowTriggerDirect(Parse *, Trigger *, Table *, int, int, int);
+ void sqlite3CodeDbTrigger(Parse *, int, int, int);
void sqliteViewTriggers(Parse*, Table*, Expr*, int, ExprList*);
void sqlite3DeleteTriggerStep(sqlite3*, TriggerStep*);
TriggerStep *sqlite3TriggerSelectStep(sqlite3*,Select*);
diff -ru sqlite-src-3070602-orig/src/trigger.c sqlite-src-3070602/src/trigger.c
--- sqlite-src-3070602-orig/src/trigger.c 2011-04-17 12:53:51.000000000
-0500
+++ sqlite-src-3070602/src/trigger.c 2011-05-16 11:06:02.352119900 -0500
@@ -13,6 +13,7 @@
#include "sqliteInt.h"
#ifndef SQLITE_OMIT_TRIGGER
+
/*
** Delete a linked list of TriggerStep structures.
*/
@@ -69,6 +70,8 @@
return (pList ? pList : pTab->pTrigger);
}
+static void getDbTriggerViewName(int dBTriggerWhen, int
dBTriggerType, Token *viewName);
+
/*
** This is called by the parser when it sees a CREATE TRIGGER statement
** up to the point of the BEGIN before the trigger actions. A Trigger
@@ -97,11 +100,35 @@
Token *pName; /* The unqualified db name */
DbFixer sFix; /* State vector for the DB fixer */
int iTabDb; /* Index of the database holding pTab */
+ int isDbTrig; /* Whether this is a database trigger */
assert( pName1!=0 ); /* pName1->z might be NULL, but not pName1 itself */
assert( pName2!=0 );
- assert( op==TK_INSERT || op==TK_UPDATE || op==TK_DELETE );
+ /* Normal triggers are on insert, update, or delete, and they are
+ ** before, after, or instead of triggers.
+ **
+ ** DB triggers are instead of insert triggers under the hood, but the
+ ** tr_tm and op combinations are as below (see also parse.y).
+ */
+ assert( op==TK_INSERT || op==TK_UPDATE || op==TK_DELETE ||
+ op==TK_CONNECT || op==TK_DISCONNECT ||
+ op==TK_BEGIN || op==TK_COMMIT || op==TK_ROLLBACK );
+ assert( tr_tm==TK_BEFORE || tr_tm==TK_BEFORE || tr_tm==TK_AFTER );
+ assert( op==TK_INSERT || op==TK_UPDATE || op==TK_DELETE ||
+ ( tr_tm==TK_AFTER && op==TK_BEGIN ) ||
+ ( tr_tm==TK_BEFORE && op==TK_COMMIT ) ||
+ ( tr_tm==TK_AFTER && op==TK_COMMIT ) ||
+ ( tr_tm==TK_AFTER && op==TK_ROLLBACK ) );
assert( op>0 && op<0xff );
+
+ isDbTrig = ( ( tr_tm==TK_INSTEAD && (op==TK_CONNECT ||
op==TK_DISCONNECT ) ) ||
+ ( tr_tm==TK_AFTER && op==TK_BEGIN ) ||
+ ( tr_tm==TK_BEFORE && op==TK_COMMIT ) ||
+ ( tr_tm==TK_AFTER && op==TK_COMMIT ) ||
+ ( tr_tm==TK_AFTER && op==TK_ROLLBACK ) );
+
+ assert( !isDbTrig || pTableName == 0 );
+
if( isTemp ){
/* If TEMP was specified, then the trigger name may not be qualified. */
if( pName2->n>0 ){
@@ -118,6 +145,54 @@
}
}
+ /* If the trigger is a DATABASE trigger then we pretend it's an
+ ** INSTEAD OF trigger on an internal view. We have one such view
+ ** per-event that we support database triggers on. The pTableName in
+ ** this case will be 0 coming in, so we need make a pTableName value
+ ** corresponding to the view in question -- this will allow us to
+ ** continue below.
+ */
+ if( isDbTrig ){
+ Token dbTrigViewName;
+ Token dbName = { db->aDb[iDb].zName, sqlite3Strlen30(db->aDb[iDb].zName) };
+ Table *pTrigView;
+
+ getDbTriggerViewName(tr_tm, op, &dbTrigViewName);
+
+ /* DB Triggers are instead of insert triggers under the hood */
+ tr_tm = TK_INSTEAD;
+ op = TK_INSERT;
+
+ pTableName = sqlite3SrcListAppend(db, 0, &dbName, &dbTrigViewName);
+ if( !pTableName || db->mallocFailed ){
+ goto trigger_cleanup;
+ }
+
+ if( (pTrigView = sqlite3FindTable(db, dbTrigViewName.z,
db->aDb[iDb].zName))==0 ){
+ sqlite3_stmt *pStmt;
+ int oflags = db->flags;;
+ char *zSql;
+
+ /* We need to create the "crutch" view as we create the trigger. */
+ zSql = sqlite3MPrintf(db, "CREATE VIEW %T.%T AS SELECT 0 AS
nothing", &dbName, &dbTrigViewName);
+ if( zSql==0 )
+ goto trigger_cleanup;
+ db->flags |= SQLITE_WriteSchema; /* XXX is this still needed? */
+ pParse->rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0);
+ db->flags = oflags;
+ if( pParse->rc != SQLITE_OK )
+ goto trigger_cleanup;
+ db->flags |= SQLITE_WriteSchema;
+ pParse->rc = sqlite3_step(pStmt);
+ db->flags = oflags;
+ if( pParse->rc != SQLITE_DONE )
+ goto trigger_cleanup;
+ pParse->rc = sqlite3VdbeFinalize((Vdbe*)pStmt);
+ if( pParse->rc != SQLITE_OK )
+ goto trigger_cleanup;
+ }
+ }
+
/* If the trigger name was unqualified, and the table is a temp table,
** then set iDb to 1 to create the trigger in the temporary database.
** If sqlite3SrcListLookup() returns 0, indicating the table does not
@@ -179,7 +254,7 @@
}
/* Do not create a trigger on a system table */
- if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 ){
+ if( !isDbTrig && sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 ){
sqlite3ErrorMsg(pParse, "cannot create trigger on system table");
pParse->nErr++;
goto trigger_cleanup;
@@ -1108,4 +1183,230 @@
return mask;
}
+static void getDbTriggerViewName(int dBTriggerWhen, int
dBTriggerType, Token *viewName){
+ switch (dBTriggerType) {
+ case TK_CONNECT:
+ viewName->z = "sqlite_intview_connect";
+ viewName->n = sizeof("sqlite_intview_connect") - 1;
+ break;
+ case TK_DISCONNECT:
+ viewName->z = "sqlite_intview_disconnect";
+ viewName->n = sizeof("sqlite_intview_disconnect") - 1;
+ break;
+ case TK_BEGIN:
+ viewName->z = "sqlite_intview_begin";
+ viewName->n = sizeof("sqlite_intview_begin") - 1;
+ break;
+ case TK_ROLLBACK:
+ viewName->z = "sqlite_intview_rollback";
+ viewName->n = sizeof("sqlite_intview_rollback") - 1;
+ break;
+ default: assert( dBTriggerType==TK_COMMIT );
+ if( dBTriggerWhen==TK_BEFORE ){
+ viewName->z = "sqlite_intview_bcommit";
+ viewName->n = sizeof("sqlite_intview_bcommit") - 1;
+ }else{
+ viewName->z = "sqlite_intview_acommit";
+ viewName->n = sizeof("sqlite_intview_acommit") - 1;
+ }
+ break;
+ }
+}
+
+/*
+** Indicate whether there are triggers to fire of the given database
+** trigger type.
+ */
+int sqlite3dBTriggersExist(Parse *pParse, int iDb, int dBTriggerWhen,
int dBTriggerType){
+ sqlite3 *db = pParse->db; /* The database connection */
+ Token viewName; /* The crutch view name for the DB trigger */
+ Table *pTrigView; /* The crutch view for the DB trigger */
+
+ getDbTriggerViewName(dBTriggerWhen, dBTriggerType, &viewName);
+
+ if( (pTrigView = sqlite3FindTable(db, viewName.z, db->aDb[iDb].zName))==0 )
+ return 0;
+
+ return ( sqlite3TriggersExist(pParse, pTrigView, TK_INSERT, 0, 0)!= 0);
+}
+
+/*
+ * This function codes a DB trigger.
+ *
+ * Current approach: call sqlite3Insert() to insert on the crutch view.
+ *
+ * Problems: lots.
+ *
+ * Next approach(?): call codeTriggerProgram() directly here for all the
+ * steps of the triggers on the crutch views. On the top-level VM? or
+ * in sub-VMs? Hard to tell!
+ */
+void sqlite3CodeDbTrigger(Parse *pParse, int iDb, int dBTriggerWhen,
int dBTriggerType){
+ sqlite3 *db = pParse->db; /* The database connection */
+ Parse *pTop; /* Root parse context */
+ Token dbName; /* The name of the DB for iDb */
+ Token viewName; /* The crutch view name for the DB trigger */
+ Table *pTrigView; /* The crutch view for the DB trigger */
+ SrcList *pInto; /* The crutch view to insert into */
+ Parse *pSubParse; /* Parse context for sub-vdbe */
+ TriggerPrg *pPrg; /* XXX issues */
+ SubProgram *pProgram = 0; /* Sub-program for the insert we do here */
+ NameContext sNC; /* Name context for sub-vdbe */
+ Vdbe *vTop; /* Top-level VM */
+ Vdbe *vSub; /* Temporary VM */
+ const char *trigType; /* String form of dBTriggerType */
+ int reg; /* Unclear if we need any; see below */
+ int endOfDbTrigCrutchIns; /* End of crutch insert label in parent program */
+ int addrIfSkipDbTrig; /* End of crutch insert in sub-program */
+
+ dbName.z = db->aDb[iDb].zName;
+ dbName.n = sqlite3Strlen30(db->aDb[iDb].zName);
+ getDbTriggerViewName(dBTriggerWhen, dBTriggerType, &viewName);
+
+ /* DB triggers only fire from the top-level */
+ pTop = sqlite3ParseToplevel(pParse);
+ if( pTop!=pParse ) return;
+
+ if( (pTrigView = sqlite3FindTable(db, viewName.z, db->aDb[iDb].zName))==0 )
+ return;
+
+ if( !sqlite3TriggersExist(pParse, pTrigView, TK_INSERT, 0, 0) )
+ return;
+
+ pInto = sqlite3SrcListAppend(db, 0, &dbName, &viewName);
+ if ( pInto==0 ){
+ /* XXX Set error message and all that, eh? */
+ pParse->rc = SQLITE_ERROR;
+ return;
+ }
+
+ pPrg = sqlite3DbMallocZero(db, sizeof(TriggerPrg));
+ if( !pPrg ) return; /* XXX need to do more? */
+ pPrg->pNext = pTop->pTriggerPrg;
+ pTop->pTriggerPrg = pPrg;
+ pPrg->pProgram = pProgram = sqlite3DbMallocZero(db, sizeof(SubProgram));
+ if( !pProgram ) return; /* XXX need to do more? */
+ sqlite3VdbeLinkSubProgram(pTop->pVdbe, pProgram);
+ /*
+ ** XXX we're not coding a trigger here, but an insert [into a view]
+ ** that will trigger the triggers we care about, so we have nothing to
+ ** put in pPrg->pTrigger!
+ */
+ pPrg->pTrigger = 0;
+
+ /*
+ ** Since we're a database trigger there's no way to override our
+ ** on-conflict handling, thus we use OE_Default.
+ */
+ pPrg->orconf = OE_Default;
+
+ /* DB triggers must not use any NEW or OLD values (see below) */
+ pPrg->aColmask[0] = 0;
+ pPrg->aColmask[1] = 0;
+
+ pSubParse = sqlite3StackAllocZero(db, sizeof(Parse));
+ if( !pSubParse ) return; /* XXX need to do more? */
+ /* XXX sNC is unused here as we have no expressions in which to
+ ** resolve names, so this should go */
+ memset(&sNC, 0, sizeof(sNC));
+ sNC.pParse = pSubParse;
+ pSubParse->db = db;
+ pSubParse->pTriggerTab = pTrigView; /* a lie: we're not a trigger
on a table here, we're a DB trigger */
+ pSubParse->pToplevel = pTop;
+ pSubParse->zAuthContext = viewName.z; /* yes, it's NUL-terminated */
+ pSubParse->eTriggerOp = TK_INSERT;
+ pSubParse->nQueryLoop = pParse->nQueryLoop;
+
+ vTop = sqlite3GetVdbe(pParse);
+ vSub = sqlite3GetVdbe(pSubParse);
+
+ if ( vTop==0 ) goto out;
+ if ( vSub==0 ) goto out;
+
+ switch ( dBTriggerType ){
+ case TK_CONNECT:
+ trigType = "CONNECT";
+ break;
+ case TK_DISCONNECT:
+ trigType = "DISCONNECT";
+ break;
+ case TK_BEGIN:
+ trigType = "AFTER BEGIN";
+ break;
+ case TK_ROLLBACK:
+ trigType = "BEFORE ROLLBACK";
+ break;
+ default: assert( dBTriggerType==TK_COMMIT );
+ trigType = "BEFORE COMMIT";
+ }
+ VdbeComment((vSub, "Start DB trigger: %s.%s (%s)",
+ dbName.z, viewName.z, trigType));
+
+ /* DB triggers are coded as inserts on their crutch views. We only
+ ** want AFTER BEGIN triggers to fire once per-transaction, so we wrap
+ ** the trigger firing with an OP_IfBegun/OP_SetBegun.
+ */
+ if( dBTriggerType==TK_BEGIN )
+ addrIfSkipDbTrig = sqlite3VdbeAddOp1(vSub, OP_IfBegun, iDb);
+ sqlite3Insert(pSubParse, pInto, 0, 0, 0, OE_Default);
+ if( dBTriggerType==TK_BEGIN ){
+ sqlite3VdbeAddOp1(vSub, OP_SetBegun, iDb);
+ sqlite3VdbeJumpHere(vSub, addrIfSkipDbTrig);
+ }else if( dBTriggerWhen==TK_BEFORE && dBTriggerType==TK_COMMIT ){
+ /* The commit trigger will have resulted in activeVdbeCnt, and
+ ** possibly writeVdbeCnt, being incremented as a result of the DB
+ ** trigger sub-program. We need to decrement those two counts in
+ ** order for the commit to complete.
+ */
+ sqlite3VdbeAddOp1(vSub, OP_AdjustVdbeCount, -1);
+ }
+ sqlite3VdbeAddOp0(vSub, OP_Halt);
+ VdbeComment((vSub, "End DB trigger: %s.%s (%s)",
+ dbName.z, viewName.z, trigType));
+ transferParseError(pParse, pSubParse);
+ if( db->mallocFailed==0 ){
+ pProgram->aOp = sqlite3VdbeTakeOpArray(vSub, &pProgram->nOp,
&pTop->nMaxArg);
+ }
+ pProgram->nMem = pSubParse->nMem;
+ pProgram->nCsr = pSubParse->nTab;
+ pProgram->token = (void *)viewName.z;
+ /*
+ ** DB triggers have and use no NEW or OLD values (though, because we
+ ** use a crutch view with one column, there is, in fact, a single
+ ** column, plus a rowid, so two columns, but DB triggers should make
+ ** no reference to either).
+ */
+ pPrg->aColmask[0] = 0;
+ pPrg->aColmask[1] = 0;
+ sqlite3VdbeDelete(vSub);
+
+ /*
+ ** OP_Program takes a list of registers holding OLD/NEW columns. We
+ ** don't really need any, so it'd be nice to be able to pass a
+ ** constant here, but it's unclear if we can. (XXX When the
+ ** OP_Integer instruction is left out this seems to work anyways.)
+ */
+ reg = sqlite3GetTempRange(pParse, 1);
+ sqlite3VdbeAddOp2(vTop, OP_Integer, -1, reg);
+ endOfDbTrigCrutchIns = sqlite3VdbeMakeLabel(vTop);
+
+ /* Code the OP_Program opcode in the parent VDBE. P4 of the OP_Program
+ ** is a pointer to the sub-vdbe containing the trigger program. */
+ sqlite3VdbeAddOp3(vTop, OP_Program, reg, endOfDbTrigCrutchIns,
++pParse->nMem);
+ sqlite3VdbeChangeP4(vTop, -1, (const char *)pPrg->pProgram, P4_SUBPROGRAM);
+ VdbeComment((vTop, "Call DB trigger: %s.%s (%s)",
+ dbName.z, viewName.z, trigType));
+ sqlite3VdbeChangeP5(vTop, (u8)( (pParse->db->flags&SQLITE_RecTriggers)==0 ));
+ sqlite3ReleaseTempRange(pParse, reg, 1);
+
+ /* End */
+ sqlite3VdbeResolveLabel(vTop, endOfDbTrigCrutchIns);
+ sqlite3VdbeAddOp0(vTop, OP_Noop);
+
+out:
+ assert( !pSubParse->pAinc && !pSubParse->pZombieTab );
+ assert( !pSubParse->pTriggerPrg && !pSubParse->nMaxArg );
+ sqlite3StackFree(db, pSubParse);
+}
+
#endif /* !defined(SQLITE_OMIT_TRIGGER) */
diff -ru sqlite-src-3070602-orig/src/vdbe.c sqlite-src-3070602/src/vdbe.c
--- sqlite-src-3070602-orig/src/vdbe.c 2011-04-17 12:53:51.000000000 -0500
+++ sqlite-src-3070602/src/vdbe.c 2011-05-16 10:59:23.200120142 -0500
@@ -5026,6 +5026,51 @@
break;
}
+/* Opcode: BegunCheck P1 P2 * * *
+**
+** Jump to the address in P2 if the AFTER BEGIN trigger for the current
+** transaction on the DB identified by P1 has already fired.
+*/
+case OP_IfBegun: {
+ Btree *pBt;
+
+ assert( pOp->p1>=0 && pOp->p1<db->nDb );
+ assert( (p->btreeMask & (((yDbMask)1)<<pOp->p1))!=0 );
+
+ pBt = db->aDb[pOp->p1].pBt;
+ if( sqlite3BtreeAfterBeginTriggerFiredCheck(pBt) ){
+ pc = pOp->p2 - 1;
+ }
+ break;
+}
+
+/* Opcode: Begun P1 * * * *
+**
+** Mark the AFTER BEGIN trigger for this transaction, on the DB
+** identified by P1, as having been run.
+*/
+case OP_SetBegun: {
+ Btree *pBt;
+
+ assert( pOp->p1>=0 && pOp->p1<db->nDb );
+ assert( (p->btreeMask & (((yDbMask)1)<<pOp->p1))!=0 );
+
+ pBt = db->aDb[pOp->p1].pBt;
+ sqlite3BtreeAfterBeginTriggerFired(pBt);
+ break;
+}
+
+/* Opcode: AdjustVdbeCount P1 * * * *
+**
+** Add P1 to the activeVdbeCnt, and possibly writeVdbeCnt. This is
+** needed so that DB BEFORE/AFTER COMMIT triggers can complete.
+ */
+case OP_AdjustVdbeCount: {
+ db->activeVdbeCnt += pOp->p1;
+ if( p->readOnly==0 )
+ db->writeVdbeCnt += pOp->p1;
+ break;
+}
#endif /* #ifndef SQLITE_OMIT_TRIGGER */
#ifndef SQLITE_OMIT_FOREIGN_KEY
diff -ru sqlite-src-3070602-orig/tool/mkkeywordhash.c
sqlite-src-3070602/tool/mkkeywordhash.c
--- sqlite-src-3070602-orig/tool/mkkeywordhash.c 2011-04-17
12:53:52.000000000 -0500
+++ sqlite-src-3070602/tool/mkkeywordhash.c 2011-05-06 19:09:07.268668084
-0500
@@ -167,6 +167,7 @@
{ "COLUMN", "TK_COLUMNKW", ALTER },
{ "COMMIT", "TK_COMMIT", ALWAYS },
{ "CONFLICT", "TK_CONFLICT", CONFLICT },
+ { "CONNECT", "TK_CONNECT", TRIGGER },
{ "CONSTRAINT", "TK_CONSTRAINT", ALWAYS },
{ "CREATE", "TK_CREATE", ALWAYS },
{ "CROSS", "TK_JOIN_KW", ALWAYS },
@@ -180,6 +181,7 @@
{ "DELETE", "TK_DELETE", ALWAYS },
{ "DESC", "TK_DESC", ALWAYS },
{ "DETACH", "TK_DETACH", ATTACH },
+ { "DISCONNECT", "TK_DISCONNECT", TRIGGER },
{ "DISTINCT", "TK_DISTINCT", ALWAYS },
{ "DROP", "TK_DROP", ALWAYS },
{ "END", "TK_END", ALWAYS },
_______________________________________________
sqlite-users mailing list
[email protected]
http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-users