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

Reply via email to