SQLite

Check-in [110c000d86]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:The sqlite3_trace() callback now prints a message as each trigger fires within a statement. (CVS 4709)
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 110c000d86bd4a0b4b946c62d11a435426b02d16
User & Date: drh 2008-01-12 21:35:57.000
Context
2008-01-13
19:02
Fix some issues with out-of-memory recovery. (CVS 4710) (check-in: 23181f8689 user: drh tags: trunk)
2008-01-12
21:35
The sqlite3_trace() callback now prints a message as each trigger fires within a statement. (CVS 4709) (check-in: 110c000d86 user: drh tags: trunk)
19:03
Continuing work toward converting the VM to a register machine. (CVS 4708) (check-in: 426f31ecdd user: drh tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/build.c.
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
**     CREATE INDEX
**     DROP INDEX
**     creating ID lists
**     BEGIN TRANSACTION
**     COMMIT
**     ROLLBACK
**
** $Id: build.c,v 1.464 2008/01/12 12:48:08 drh Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>

/*
** This routine is called when a new SQL statement is beginning to
** be parsed.  Initialize the pParse structure as needed.







|







18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
**     CREATE INDEX
**     DROP INDEX
**     creating ID lists
**     BEGIN TRANSACTION
**     COMMIT
**     ROLLBACK
**
** $Id: build.c,v 1.465 2008/01/12 21:35:57 drh Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>

/*
** This routine is called when a new SQL statement is beginning to
** be parsed.  Initialize the pParse structure as needed.
180
181
182
183
184
185
186


187
188
189
190
191
192
193

194


195
196
197
198
199
200
201
      ** shared-cache feature is enabled.
      */
      codeTableLocks(pParse);
      sqlite3VdbeAddOp2(v, OP_Goto, 0, pParse->cookieGoto);
    }

#ifndef SQLITE_OMIT_TRACE


    /* Add a No-op that contains the complete text of the compiled SQL
    ** statement as its P4 argument.  This does not change the functionality
    ** of the program. 
    **
    ** This is used to implement sqlite3_trace().
    */
    sqlite3VdbeAddOp4(v, OP_Noop, 0, 0, 0,

                      pParse->zSql, pParse->zTail-pParse->zSql);


#endif /* SQLITE_OMIT_TRACE */
  }


  /* Get the VDBE program ready for execution
  */
  if( v && pParse->nErr==0 && !db->mallocFailed ){







>
>
|
<
<
<
<
|
|
>
|
>
>







180
181
182
183
184
185
186
187
188
189




190
191
192
193
194
195
196
197
198
199
200
201
202
      ** shared-cache feature is enabled.
      */
      codeTableLocks(pParse);
      sqlite3VdbeAddOp2(v, OP_Goto, 0, pParse->cookieGoto);
    }

#ifndef SQLITE_OMIT_TRACE
    if( !db->init.busy ){
      /* Change the P4 argument of the first opcode (which will always be
      ** an OP_Trace) to be the complete text of the current SQL statement.




      */
      VdbeOp *pOp = sqlite3VdbeGetOp(v, 0);
      if( pOp && pOp->opcode==OP_Trace ){
        sqlite3VdbeChangeP4(v, 0, pParse->zSql, pParse->zTail-pParse->zSql);
      }
    }
#endif /* SQLITE_OMIT_TRACE */
  }


  /* Get the VDBE program ready for execution
  */
  if( v && pParse->nErr==0 && !db->mallocFailed ){
Changes to src/pragma.c.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
** 2003 April 6
**
** 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 contains code used to implement the PRAGMA command.
**
** $Id: pragma.c,v 1.165 2008/01/10 23:50:11 drh Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>

/* Ignore this whole file if pragmas are disabled
*/
#if !defined(SQLITE_OMIT_PRAGMA) && !defined(SQLITE_OMIT_PARSER)













|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
** 2003 April 6
**
** 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 contains code used to implement the PRAGMA command.
**
** $Id: pragma.c,v 1.166 2008/01/12 21:35:57 drh Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>

/* Ignore this whole file if pragmas are disabled
*/
#if !defined(SQLITE_OMIT_PRAGMA) && !defined(SQLITE_OMIT_PARSER)
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
  char *zLeft = 0;       /* Nul-terminated UTF-8 string <id> */
  char *zRight = 0;      /* Nul-terminated UTF-8 string <value>, or NULL */
  const char *zDb = 0;   /* The database name */
  Token *pId;            /* Pointer to <id> token */
  int iDb;               /* Database index for <database> */
  sqlite3 *db = pParse->db;
  Db *pDb;
  Vdbe *v = sqlite3GetVdbe(pParse);
  if( v==0 ) return;
  pParse->nMem = 1;

  /* Interpret the [database.] part of the pragma statement. iDb is the
  ** index of the database this pragma is being applied to in db.aDb[]. */
  iDb = sqlite3TwoPartName(pParse, pId1, pId2, &pId);
  if( iDb<0 ) return;







|







246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
  char *zLeft = 0;       /* Nul-terminated UTF-8 string <id> */
  char *zRight = 0;      /* Nul-terminated UTF-8 string <value>, or NULL */
  const char *zDb = 0;   /* The database name */
  Token *pId;            /* Pointer to <id> token */
  int iDb;               /* Database index for <database> */
  sqlite3 *db = pParse->db;
  Db *pDb;
  Vdbe *v = pParse->pVdbe = sqlite3VdbeCreate(db);
  if( v==0 ) return;
  pParse->nMem = 1;

  /* Interpret the [database.] part of the pragma statement. iDb is the
  ** index of the database this pragma is being applied to in db.aDb[]. */
  iDb = sqlite3TwoPartName(pParse, pId1, pId2, &pId);
  if( iDb<0 ) return;
Changes to src/select.c.
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** This file contains C code routines that are called by the parser
** to handle SELECT statements in SQLite.
**
** $Id: select.c,v 1.399 2008/01/12 19:03:49 drh Exp $
*/
#include "sqliteInt.h"


/*
** Delete all the content of a Select structure but do not deallocate
** the select structure itself.







|







8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** This file contains C code routines that are called by the parser
** to handle SELECT statements in SQLite.
**
** $Id: select.c,v 1.400 2008/01/12 21:35:57 drh Exp $
*/
#include "sqliteInt.h"


/*
** Delete all the content of a Select structure but do not deallocate
** the select structure itself.
1718
1719
1720
1721
1722
1723
1724





1725
1726
1727
1728
1729
1730
1731
** Get a VDBE for the given parser context.  Create a new one if necessary.
** If an error occurs, return NULL and leave a message in pParse.
*/
Vdbe *sqlite3GetVdbe(Parse *pParse){
  Vdbe *v = pParse->pVdbe;
  if( v==0 ){
    v = pParse->pVdbe = sqlite3VdbeCreate(pParse->db);





  }
  return v;
}


/*
** Compute the iLimit and iOffset fields of the SELECT based on the







>
>
>
>
>







1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
** Get a VDBE for the given parser context.  Create a new one if necessary.
** If an error occurs, return NULL and leave a message in pParse.
*/
Vdbe *sqlite3GetVdbe(Parse *pParse){
  Vdbe *v = pParse->pVdbe;
  if( v==0 ){
    v = pParse->pVdbe = sqlite3VdbeCreate(pParse->db);
#ifndef SQLITE_OMIT_TRACE
    if( v ){
      sqlite3VdbeAddOp0(v, OP_Trace);
    }
#endif
  }
  return v;
}


/*
** Compute the iLimit and iOffset fields of the SELECT based on the
Changes to src/trigger.c.
802
803
804
805
806
807
808





809
810
811
812
813
814
815
 
    if( fire_this ){
      int endTrigger;
      Expr * whenExpr;
      AuthContext sContext;
      NameContext sNC;






      memset(&sNC, 0, sizeof(sNC));
      sNC.pParse = pParse;

      /* Push an entry on to the trigger stack */
      trigStackEntry.pTrigger = p;
      trigStackEntry.newIdx = newIdx;
      trigStackEntry.oldIdx = oldIdx;







>
>
>
>
>







802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
 
    if( fire_this ){
      int endTrigger;
      Expr * whenExpr;
      AuthContext sContext;
      NameContext sNC;

#ifndef SQLITE_OMIT_TRACE
      sqlite3VdbeAddOp4(pParse->pVdbe, OP_Trace, 0, 0, 0,
                        sqlite3_mprintf("-- TRIGGER %s", p->name),
                        P4_DYNAMIC);
#endif
      memset(&sNC, 0, sizeof(sNC));
      sNC.pParse = pParse;

      /* Push an entry on to the trigger stack */
      trigStackEntry.pTrigger = p;
      trigStackEntry.newIdx = newIdx;
      trigStackEntry.oldIdx = oldIdx;
Changes to src/vdbe.c.
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
**
** Various scripts scan this source file in order to generate HTML
** documentation, headers files, or other derived files.  The formatting
** of the code in this file is, therefore, important.  See other comments
** in this file for details.  If in doubt, do not deviate from existing
** commenting and indentation practices when changing or adding code.
**
** $Id: vdbe.c,v 1.694 2008/01/12 19:03:49 drh Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>
#include "vdbeInt.h"

/*
** The following global variable is incremented every time a cursor







|







39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
**
** Various scripts scan this source file in order to generate HTML
** documentation, headers files, or other derived files.  The formatting
** of the code in this file is, therefore, important.  See other comments
** in this file for details.  If in doubt, do not deviate from existing
** commenting and indentation practices when changing or adding code.
**
** $Id: vdbe.c,v 1.695 2008/01/12 21:35:57 drh Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>
#include "vdbeInt.h"

/*
** The following global variable is incremented every time a cursor
5054
5055
5056
5057
5058
5059
5060





















5061
5062
5063
5064
5065
5066
5067
      assert( nArg>1 && apArg[0] && (apArg[0]->flags&MEM_Null) );
      db->lastRowid = rowid;
    }
  }
  break;
}
#endif /* SQLITE_OMIT_VIRTUALTABLE */






















/* An other opcode is illegal...
*/
default: {
  assert( 0 );
  break;
}







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







5054
5055
5056
5057
5058
5059
5060
5061
5062
5063
5064
5065
5066
5067
5068
5069
5070
5071
5072
5073
5074
5075
5076
5077
5078
5079
5080
5081
5082
5083
5084
5085
5086
5087
5088
      assert( nArg>1 && apArg[0] && (apArg[0]->flags&MEM_Null) );
      db->lastRowid = rowid;
    }
  }
  break;
}
#endif /* SQLITE_OMIT_VIRTUALTABLE */

#ifndef SQLITE_OMIT_TRACE
/* Opcode: Trace * * * P4 *
**
** If tracing is enabled (by the sqlite3_trace()) interface, then
** the UTF-8 string contained in P4 is emitted on the trace callback.
*/
case OP_Trace: {
  if( pOp->p4.z ){
    if( db->xTrace ){
      db->xTrace(db->pTraceArg, pOp->p4.z);
    }
#ifdef SQLITE_DEBUG
    if( (db->flags & SQLITE_SqlTrace)!=0 ){
      sqlite3DebugPrintf("SQL-trace: %s\n", pOp->p4.z);
    }
#endif /* SQLITE_DEBUG */
  }
  break;
}
#endif

/* An other opcode is illegal...
*/
default: {
  assert( 0 );
  break;
}
Changes to src/vdbeapi.c.
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340

341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
    ** from interrupting a statement that has not yet started.
    */
    if( db->activeVdbeCnt==0 ){
      db->u1.isInterrupted = 0;
    }

#ifndef SQLITE_OMIT_TRACE
    /* Invoke the trace callback if there is one
    */
    if( db->xTrace && !db->init.busy ){
      assert( p->nOp>0 );
      assert( p->aOp[p->nOp-1].opcode==OP_Noop );
      assert( p->aOp[p->nOp-1].p4.z!=0 );
      assert( p->aOp[p->nOp-1].p4type==P4_DYNAMIC );
      sqlite3SafetyOff(db);
      db->xTrace(db->pTraceArg, p->aOp[p->nOp-1].p4.z);
      if( sqlite3SafetyOn(db) ){
        p->rc = SQLITE_MISUSE;
        return SQLITE_MISUSE;
      }
    }
    if( db->xProfile && !db->init.busy ){
      double rNow;
      sqlite3OsCurrentTime(db->pVfs, &rNow);
      p->startTime = (rNow - (int)rNow)*3600.0*24.0*1000000000.0;
    }
#endif

    /* Print a copy of SQL as it is executed if the SQL_TRACE pragma is turned
    ** on in debugging mode.
    */
#ifdef SQLITE_DEBUG
    if( (db->flags & SQLITE_SqlTrace)!=0 ){
      sqlite3DebugPrintf("SQL-trace: %s\n", p->aOp[p->nOp-1].p4.z);
    }
#endif /* SQLITE_DEBUG */

    db->activeVdbeCnt++;
    p->pc = 0;
  }
#ifndef SQLITE_OMIT_EXPLAIN
  if( p->explain ){
    rc = sqlite3VdbeList(p);
  }else
#endif /* SQLITE_OMIT_EXPLAIN */
  {
    rc = sqlite3VdbeExec(p);
  }

  if( sqlite3SafetyOff(db) ){
    rc = SQLITE_MISUSE;
  }

#ifndef SQLITE_OMIT_TRACE
  /* Invoke the profile callback if there is one
  */
  if( rc!=SQLITE_ROW && db->xProfile && !db->init.busy ){

    double rNow;
    u64 elapseTime;

    sqlite3OsCurrentTime(db->pVfs, &rNow);
    elapseTime = (rNow - (int)rNow)*3600.0*24.0*1000000000.0 - p->startTime;
    assert( p->nOp>0 );
    assert( p->aOp[p->nOp-1].opcode==OP_Noop );
    assert( p->aOp[p->nOp-1].p4.z!=0 );
    assert( p->aOp[p->nOp-1].p4type==P4_DYNAMIC );
    db->xProfile(db->pProfileArg, p->aOp[p->nOp-1].p4.z, elapseTime);
  }
#endif

  sqlite3Error(p->db, rc, 0);
  p->rc = sqlite3ApiExit(p->db, p->rc);
end_of_step:
  assert( (rc&0xff)==rc );







<
<
<
<
<
<
<
<
<
<
<
<
<
<







<
<
<
<
<
<
<
<
<



















|
>





<
<
<
<
|







284
285
286
287
288
289
290














291
292
293
294
295
296
297









298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323




324
325
326
327
328
329
330
331
    ** from interrupting a statement that has not yet started.
    */
    if( db->activeVdbeCnt==0 ){
      db->u1.isInterrupted = 0;
    }

#ifndef SQLITE_OMIT_TRACE














    if( db->xProfile && !db->init.busy ){
      double rNow;
      sqlite3OsCurrentTime(db->pVfs, &rNow);
      p->startTime = (rNow - (int)rNow)*3600.0*24.0*1000000000.0;
    }
#endif










    db->activeVdbeCnt++;
    p->pc = 0;
  }
#ifndef SQLITE_OMIT_EXPLAIN
  if( p->explain ){
    rc = sqlite3VdbeList(p);
  }else
#endif /* SQLITE_OMIT_EXPLAIN */
  {
    rc = sqlite3VdbeExec(p);
  }

  if( sqlite3SafetyOff(db) ){
    rc = SQLITE_MISUSE;
  }

#ifndef SQLITE_OMIT_TRACE
  /* Invoke the profile callback if there is one
  */
  if( rc!=SQLITE_ROW && db->xProfile && !db->init.busy && p->nOp>0
           && p->aOp[0].opcode==OP_Trace && p->aOp[0].p4.z!=0 ){
    double rNow;
    u64 elapseTime;

    sqlite3OsCurrentTime(db->pVfs, &rNow);
    elapseTime = (rNow - (int)rNow)*3600.0*24.0*1000000000.0 - p->startTime;




    db->xProfile(db->pProfileArg, p->aOp[0].p4.z, elapseTime);
  }
#endif

  sqlite3Error(p->db, rc, 0);
  p->rc = sqlite3ApiExit(p->db, p->rc);
end_of_step:
  assert( (rc&0xff)==rc );
Changes to src/vdbeaux.c.
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
      sqlite3_vtab *pVtab = pOp->p4.pVtab;
      sqlite3_snprintf(nTemp, zTemp, "vtab:%p:%p", pVtab, pVtab->pModule);
      break;
    }
#endif
    default: {
      zP4 = pOp->p4.z;
      if( zP4==0 || pOp->opcode==OP_Noop ){
        zP4 = zTemp;
        zTemp[0] = 0;
      }
    }
  }
  assert( zP4!=0 );
  return zP4;







|







677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
      sqlite3_vtab *pVtab = pOp->p4.pVtab;
      sqlite3_snprintf(nTemp, zTemp, "vtab:%p:%p", pVtab, pVtab->pModule);
      break;
    }
#endif
    default: {
      zP4 = pOp->p4.z;
      if( zP4==0 ){
        zP4 = zTemp;
        zTemp[0] = 0;
      }
    }
  }
  assert( zP4!=0 );
  return zP4;
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
/*
** Print the SQL that was used to generate a VDBE program.
*/
void sqlite3VdbePrintSql(Vdbe *p){
  int nOp = p->nOp;
  VdbeOp *pOp;
  if( nOp<1 ) return;
  pOp = &p->aOp[nOp-1];
  if( pOp->opcode==OP_Noop && pOp->p4.z!=0 ){
    const char *z = pOp->p4.z;
    while( isspace(*(u8*)z) ) z++;
    printf("SQL: [%s]\n", z);
  }
}
#endif

#if !defined(SQLITE_OMIT_TRACE) && defined(SQLITE_ENABLE_IOTRACE)
/*
** Print an IOTRACE message showing SQL content.
*/
void sqlite3VdbeIOTraceSql(Vdbe *p){
  int nOp = p->nOp;
  VdbeOp *pOp;
  if( sqlite3_io_trace==0 ) return;
  if( nOp<1 ) return;
  pOp = &p->aOp[nOp-1];
  if( pOp->opcode==OP_Noop && pOp->p4.p!=0 ){
    int i, j;
    char z[1000];
    sqlite3_snprintf(sizeof(z), z, "%s", pOp->p4.p);
    for(i=0; isspace((unsigned char)z[i]); i++){}
    for(j=0; z[i]; i++){
      if( isspace((unsigned char)z[i]) ){
        if( z[i-1]!=' ' ){
          z[j++] = ' ';
        }
      }else{







|
|
















|
|


|







850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
/*
** Print the SQL that was used to generate a VDBE program.
*/
void sqlite3VdbePrintSql(Vdbe *p){
  int nOp = p->nOp;
  VdbeOp *pOp;
  if( nOp<1 ) return;
  pOp = &p->aOp[0];
  if( pOp->opcode==OP_Trace && pOp->p4.z!=0 ){
    const char *z = pOp->p4.z;
    while( isspace(*(u8*)z) ) z++;
    printf("SQL: [%s]\n", z);
  }
}
#endif

#if !defined(SQLITE_OMIT_TRACE) && defined(SQLITE_ENABLE_IOTRACE)
/*
** Print an IOTRACE message showing SQL content.
*/
void sqlite3VdbeIOTraceSql(Vdbe *p){
  int nOp = p->nOp;
  VdbeOp *pOp;
  if( sqlite3_io_trace==0 ) return;
  if( nOp<1 ) return;
  pOp = &p->aOp[0];
  if( pOp->opcode==OP_Trace && pOp->p4.z!=0 ){
    int i, j;
    char z[1000];
    sqlite3_snprintf(sizeof(z), z, "%s", pOp->p4.z);
    for(i=0; isspace((unsigned char)z[i]); i++){}
    for(j=0; z[i]; i++){
      if( isspace((unsigned char)z[i]) ){
        if( z[i-1]!=' ' ){
          z[j++] = ' ';
        }
      }else{
Changes to test/trace.test.
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#    May you share freely, never taking more than you give.
#
#***********************************************************************
# This file implements regression tests for SQLite library.
#
# This file implements tests for the "sqlite3_trace()" API.
#
# $Id: trace.test,v 1.6 2006/01/03 00:33:50 drh Exp $

set testdir [file dirname $argv0]
source $testdir/tester.tcl

ifcapable !trace {
  finish_test
  return







|







8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#    May you share freely, never taking more than you give.
#
#***********************************************************************
# This file implements regression tests for SQLite library.
#
# This file implements tests for the "sqlite3_trace()" API.
#
# $Id: trace.test,v 1.7 2008/01/12 21:35:57 drh Exp $

set testdir [file dirname $argv0]
source $testdir/tester.tcl

ifcapable !trace {
  finish_test
  return
141
142
143
144
145
146
147





















148
  execsql {SELECT * FROM t1}
} {1 2 2 3 2 3}
do_test trace-4.5 {
  set TRACE_OUT
} {SELECT * FROM t1}
catch {sqlite3_finalize $STMT}






















finish_test







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
  execsql {SELECT * FROM t1}
} {1 2 2 3 2 3}
do_test trace-4.5 {
  set TRACE_OUT
} {SELECT * FROM t1}
catch {sqlite3_finalize $STMT}

# Trigger tracing.
#
do_test trace-5.1 {
  db eval {
    CREATE TRIGGER r1t1 AFTER UPDATE ON t1 BEGIN
      UPDATE t2 SET a=new.a WHERE rowid=new.rowid;
    END;
    CREATE TRIGGER r1t2 AFTER UPDATE ON t2 BEGIN
      SELECT 'hello';
    END;
  }
  set TRACE_OUT {}
  proc trace_proc cmd {
    lappend ::TRACE_OUT [string trim $cmd]
  }
  db eval {
    UPDATE t1 SET a=a+1;
  }
  set TRACE_OUT
} {{UPDATE t1 SET a=a+1;} {-- TRIGGER r1t1} {-- TRIGGER r1t2} {-- TRIGGER r1t1} {-- TRIGGER r1t2} {-- TRIGGER r1t1} {-- TRIGGER r1t2}}

finish_test