]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
When materializing a view for an UPDATE or DELETE make use of the WHERE
authordrh <drh@noemail.net>
Tue, 12 Feb 2008 16:52:14 +0000 (16:52 +0000)
committerdrh <drh@noemail.net>
Tue, 12 Feb 2008 16:52:14 +0000 (16:52 +0000)
clause to limit the number of rows materialized.  Ticket #2938. (CVS 4782)

FossilOrigin-Name: 5ab71c3a79cac04cb2c576f83a62218d05571006

manifest
manifest.uuid
src/delete.c
src/sqliteInt.h
src/update.c
test/auth.test
test/triggerA.test [new file with mode: 0644]

index 2fb030f0288f852250a3ce52f5171d8677f2ce51..fc1ff17d92658b05be6c978b3292a1affef36c53 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C ALTER\sTABLE\suses\sdouble-quotes\sfor\squoting\stable\snames.\s(CVS\s4781)
-D 2008-02-09T14:30:30
+C When\smaterializing\sa\sview\sfor\san\sUPDATE\sor\sDELETE\smake\suse\sof\sthe\sWHERE\nclause\sto\slimit\sthe\snumber\sof\srows\smaterialized.\s\sTicket\s#2938.\s(CVS\s4782)
+D 2008-02-12T16:52:14
 F Makefile.arm-wince-mingw32ce-gcc ac5f7b2cef0cd850d6f755ba6ee4ab961b1fadf7
 F Makefile.in bc2b5df3e3d0d4b801b824b7ef6dec43812b049b
 F Makefile.linux-gcc d53183f4aa6a9192d249731c90dbdffbd2c68654
@@ -90,7 +90,7 @@ F src/build.c 7d93ee565b5676746d014acdc638adee87cfd27e
 F src/callback.c 77b302b0d41468dcda78c70e706e5b84577f0fa0
 F src/complete.c 4cf68fd75d60257524cbe74f87351b9848399131
 F src/date.c 8ce763c68143b1e8fb6f79dcfc8b801853c97017
-F src/delete.c 220570cc99f1461b00a8e1b417d9254ec3588f94
+F src/delete.c fa13c296262e89c32d28949f15be275e52d7f524
 F src/experimental.c 1b2d1a6cd62ecc39610e97670332ca073c50792b
 F src/expr.c e6fb42c6e55fd9526174b1cb8296b69a60a6688a
 F src/fault.c 049b88b8ba0a1db3240aeaf9695cd08b9a3ba9e1
@@ -137,7 +137,7 @@ F src/server.c 087b92a39d883e3fa113cae259d64e4c7438bc96
 F src/shell.c ca06cb687c40a8bff6307b5fad41a0e86a0f8558
 F src/sqlite.h.in 690736613958e0f462e08ae2a9136fa335214edc
 F src/sqlite3ext.h a93f59cdee3638dc0c9c086f80df743a4e68c3cb
-F src/sqliteInt.h 9dabb5a68e1952a556b78558c87e26af2fdb5ddb
+F src/sqliteInt.h e4ca11fff0cdac38551b75a2a278edb8ad9e1f00
 F src/sqliteLimit.h ee4430f88f69bf63527967bb35ca52af7b0ccb1e
 F src/table.c 46ccf9b7892a86f57420ae7bac69ecd5e72d26b5
 F src/tclsqlite.c 9923abeffc9b3d7dad58e92b319661521f60debf
@@ -166,7 +166,7 @@ F src/test_tclvar.c b2d1115e4d489179d3f029e765211b2ad527ba59
 F src/test_thread.c e297dd41db0b249646e69f97d36ec13e56e8b730
 F src/tokenize.c c4b79fd48ddb709b2b8522b7d93a5a3d98168ca4
 F src/trigger.c 9bd3b6fa0beff4a02d262c96466f752ec15a7fc3
-F src/update.c 31edd9c9764e80753930bd5f9b43e0edb404636f
+F src/update.c 9b3be169cd2a0b065717164aa0f90aa48f34aed1
 F src/utf.c ef4b7d83bae533b76c3e1bf635b113fdad86a736
 F src/util.c c56e41ed4769c1f2b8af9ffde4757a7b4fb08ed1
 F src/vacuum.c 3f34f278809bf3eb0b62ec46ff779e9c385b28f0
@@ -195,7 +195,7 @@ F test/attach.test 4ab582932e3c815689f61afcdb9bce245f0bac53
 F test/attach2.test a295d2d7061adcee5884ef4a93c7c96a82765437
 F test/attach3.test 7b92dc8e40c1ebca9732ca6f2d3fefbd46f196df
 F test/attachmalloc.test 56c5e55563dba6d64641ef2f70ce06900df16912
-F test/auth.test be181f70ced0c84ecb5e2515d9b620f89f6a7b87
+F test/auth.test d5896499f901eeda88c9f441b13aaccf22d5ceee
 F test/auth2.test 65ac294b8d52cbdd463f61e77ad0165268373126
 F test/autoinc.test 0555aa5c789520f16d86a39c6c49b87998e01bea
 F test/autovacuum.test 4339e66003b9cf813dd667a83aed2dee27c4c36d
@@ -507,6 +507,7 @@ F test/trigger6.test 0e411654f122552da6590f0b4e6f781048a4a9b9
 F test/trigger7.test 0afa870be2ce1b132cdb85b17a4a4ef45aa8cece
 F test/trigger8.test 3a09275aa2214fdff56f731b1e775d8dfee4408a
 F test/trigger9.test b42703c378916d52a5e240ba98b25b155d3927a3
+F test/triggerA.test 8dbf5bffa3190bd513785a24a573a166a885fc1b
 F test/types.test 98e7a631bddf0806204358b452b02d0e319318a6
 F test/types2.test 3555aacf8ed8dc883356e59efc314707e6247a84
 F test/types3.test b730a7db03ef69f0fdb85b2addc20d1a0a04039b
@@ -616,7 +617,7 @@ F www/tclsqlite.tcl 8be95ee6dba05eabcd27a9d91331c803f2ce2130
 F www/vdbe.tcl 87a31ace769f20d3627a64fa1fade7fed47b90d0
 F www/version3.tcl 890248cf7b70e60c383b0e84d77d5132b3ead42b
 F www/whentouse.tcl fc46eae081251c3c181bd79c5faef8195d7991a5
-P 63915b54cfb41c2361c387636aa904145b166411
-R 4cb42b2925ae8f45e9151c56cf51f1f9
+P 607247c27b80520b8c25c489757288b8ea186f9e
+R 6e7459c99c887be282a1c6ce2d461da7
 U drh
-Z 3b52650fd58593f0d3eb3e17a3cd9c37
+Z 584629669827c756e74626290b2a5564
index f933e51e672aa567599e6812ddc2af8476b16759..1db4f72be5da67130b3b46216a72d6f391b1fff6 100644 (file)
@@ -1 +1 @@
-607247c27b80520b8c25c489757288b8ea186f9e
\ No newline at end of file
+5ab71c3a79cac04cb2c576f83a62218d05571006
\ No newline at end of file
index 5fffc8eab95f56ab75f8dc19c63a136bd4701eff..94839c9699528452c1c106b246b84857a1e7d140 100644 (file)
@@ -12,7 +12,7 @@
 ** This file contains C code routines that are called by the parser
 ** in order to generate code for DELETE FROM statements.
 **
-** $Id: delete.c,v 1.160 2008/01/25 15:04:50 drh Exp $
+** $Id: delete.c,v 1.161 2008/02/12 16:52:14 drh Exp $
 */
 #include "sqliteInt.h"
 
@@ -81,6 +81,39 @@ void sqlite3OpenTable(
 }
 
 
+#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
+/*
+** Evaluate a view and store its result in an ephemeral table.  The
+** pWhere argument is an optional WHERE clause that restricts the
+** set of rows in the view that are to be added to the ephemeral table.
+*/
+void sqlite3MaterializeView(
+  Parse *pParse,       /* Parsing context */
+  Select *pView,       /* View definition */
+  Expr *pWhere,        /* Optional WHERE clause to be added */
+  u32 col_mask,        /* Render only the columns in this mask. */
+  int iCur             /* Cursor number for ephemerial table */
+){
+  SelectDest dest;
+  Select *pDup;
+  sqlite3 *db = pParse->db;
+
+  pDup = sqlite3SelectDup(db, pView);
+  if( pWhere ){
+    SrcList *pFrom;
+    
+    pWhere = sqlite3ExprDup(db, pWhere);
+    pFrom = sqlite3SrcListAppendFromTerm(pParse, 0, 0, 0, 0, pDup, 0, 0);
+    pDup = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, 0, 0, 0, 0);
+  }
+  sqlite3SelectMask(pParse, pDup, col_mask);
+  sqlite3SelectDestInit(&dest, SRT_EphemTab, iCur);
+  sqlite3Select(pParse, pDup, &dest, 0, 0, 0, 0);
+  sqlite3SelectDelete(pDup);
+}
+#endif /* !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) */
+
+
 /*
 ** Generate code for a DELETE FROM statement.
 **
@@ -170,19 +203,13 @@ void sqlite3DeleteFrom(
     oldIdx = pParse->nTab++;
   }
 
-  /* Resolve the column names in the WHERE clause.
+  /* Assign  cursor number to the table and all its indices.
   */
   assert( pTabList->nSrc==1 );
   iCur = pTabList->a[0].iCursor = pParse->nTab++;
   for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
     pParse->nTab++;
   }
-  memset(&sNC, 0, sizeof(sNC));
-  sNC.pParse = pParse;
-  sNC.pSrcList = pTabList;
-  if( sqlite3ExprResolveNames(&sNC, pWhere) ){
-    goto delete_from_cleanup;
-  }
 
   /* Start the view context
   */
@@ -221,14 +248,16 @@ void sqlite3DeleteFrom(
   ** a ephemeral table.
   */
   if( isView ){
-    SelectDest dest;
-    Select *pView;
-
-    pView = sqlite3SelectDup(db, pTab->pSelect);
-    sqlite3SelectMask(pParse, pView, old_col_mask);
-    sqlite3SelectDestInit(&dest, SRT_EphemTab, iCur);
-    sqlite3Select(pParse, pView, &dest, 0, 0, 0, 0);
-    sqlite3SelectDelete(pView);
+    sqlite3MaterializeView(pParse, pTab->pSelect, pWhere, old_col_mask, iCur);
+  }
+
+  /* Resolve the column names in the WHERE clause.
+  */
+  memset(&sNC, 0, sizeof(sNC));
+  sNC.pParse = pParse;
+  sNC.pSrcList = pTabList;
+  if( sqlite3ExprResolveNames(&sNC, pWhere) ){
+    goto delete_from_cleanup;
   }
 
   /* Initialize the counter of the number of rows deleted, if
index 2f26f588e3eaf26c928dcdfc05ed056de7a6b5d4..f9087f7c92b0efdf666d003f79491d20ed6dc6fa 100644 (file)
@@ -11,7 +11,7 @@
 *************************************************************************
 ** Internal interface definitions for SQLite.
 **
-** @(#) $Id: sqliteInt.h,v 1.659 2008/02/02 04:47:09 danielk1977 Exp $
+** @(#) $Id: sqliteInt.h,v 1.660 2008/02/12 16:52:14 drh Exp $
 */
 #ifndef _SQLITEINT_H_
 #define _SQLITEINT_H_
@@ -1826,6 +1826,7 @@ void sqlite3RegisterDateTimeFunctions(sqlite3*);
 int sqlite3SafetyCheckOk(sqlite3*);
 int sqlite3SafetyCheckSickOrOk(sqlite3*);
 void sqlite3ChangeCookie(Parse*, int);
+void sqlite3MaterializeView(Parse*, Select*, Expr*, u32, int);
 
 #ifndef SQLITE_OMIT_TRIGGER
   void sqlite3BeginTrigger(Parse*, Token*,Token*,int,int,IdList*,SrcList*,
index fb45563ac44cd6d71bd3b8ffab7a2e34b43223e7..5c21ab8ec011f73fa391b3091494685c4d711446 100644 (file)
@@ -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.170 2008/01/19 03:35:59 drh Exp $
+** $Id: update.c,v 1.171 2008/02/12 16:52:14 drh Exp $
 */
 #include "sqliteInt.h"
 
@@ -289,13 +289,6 @@ void sqlite3Update(
   }
 #endif
 
-  /* Resolve the column names in all the expressions in the
-  ** WHERE clause.
-  */
-  if( sqlite3ExprResolveNames(&sNC, pWhere) ){
-    goto update_cleanup;
-  }
-
   /* Start the view context
   */
   if( isView ){
@@ -335,14 +328,15 @@ void sqlite3Update(
   ** a ephemeral table.
   */
   if( isView ){
-    Select *pView;
-    SelectDest dest;
-
-    pView = sqlite3SelectDup(db, pTab->pSelect);
-    sqlite3SelectMask(pParse, pView, old_col_mask|new_col_mask);
-    sqlite3SelectDestInit(&dest, SRT_EphemTab, iCur);
-    sqlite3Select(pParse, pView, &dest, 0, 0, 0, 0);
-    sqlite3SelectDelete(pView);
+    sqlite3MaterializeView(pParse, pTab->pSelect, pWhere,
+                           old_col_mask|new_col_mask, iCur);
+  }
+
+  /* Resolve the column names in all the expressions in the
+  ** WHERE clause.
+  */
+  if( sqlite3ExprResolveNames(&sNC, pWhere) ){
+    goto update_cleanup;
   }
 
   /* Begin the database scan
index 591d4d99d2b6615bdb52b7b36d144a70ac7fda68..320826980dd24676b902ba278494551120fd4b72 100644 (file)
@@ -12,7 +12,7 @@
 # focus of this script is testing the sqlite3_set_authorizer() API
 # and related functionality.
 #
-# $Id: auth.test,v 1.40 2008/01/01 19:02:09 danielk1977 Exp $
+# $Id: auth.test,v 1.41 2008/02/12 16:52:14 drh Exp $
 #
 
 set testdir [file dirname $argv0]
@@ -2245,13 +2245,15 @@ do_test auth-4.3 {
   set authargs
 } [list \
   SQLITE_UPDATE v1     x  main {} \
-  SQLITE_READ   v1     x  main {} \
   SQLITE_INSERT v1chng {} main r2 \
   SQLITE_READ   v1     x  main r2 \
   SQLITE_READ   v1     x  main r2 \
   SQLITE_READ   t2     a  main v1 \
   SQLITE_READ   t2     b  main v1 \
-  SQLITE_SELECT {}     {} {}   v1]
+  SQLITE_SELECT {}     {} {}   v1 \
+  SQLITE_SELECT {}     {} {}   v1 \
+  SQLITE_READ   v1     x  main v1 \
+]
 do_test auth-4.4 {
   execsql {
     CREATE TRIGGER r3 INSTEAD OF DELETE ON v1 BEGIN
@@ -2268,12 +2270,14 @@ do_test auth-4.5 {
   set authargs
 } [list \
   SQLITE_DELETE v1     {} main {} \
-  SQLITE_READ   v1     x  main {} \
   SQLITE_INSERT v1chng {} main r3 \
   SQLITE_READ   v1     x  main r3 \
   SQLITE_READ   t2     a  main v1 \
   SQLITE_READ   t2     b  main v1 \
-  SQLITE_SELECT {}     {} {}   v1]
+  SQLITE_SELECT {}     {} {}   v1 \
+  SQLITE_SELECT {}     {} {}   v1 \
+  SQLITE_READ   v1     x  main v1 \
+]
 
 } ;# ifcapable view && trigger
 
diff --git a/test/triggerA.test b/test/triggerA.test
new file mode 100644 (file)
index 0000000..3ce8af1
--- /dev/null
@@ -0,0 +1,230 @@
+# 2008 February 12
+#
+# 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 implements regression tests for SQLite library. Specifically,
+# it tests issues relating to firing an INSTEAD OF trigger on a VIEW
+# when one tries to UPDATE or DELETE from the view.  Does the WHERE
+# clause of the UPDATE or DELETE statement get passed down correctly 
+# into the query that manifests the view?
+#
+# Ticket #2938
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+ifcapable {!trigger} {
+  finish_test
+  return
+}
+
+# Create two table containing some sample data
+#
+do_test triggerA-1.1 {
+  db eval {
+    CREATE TABLE t1(x INTEGER PRIMARY KEY, y TEXT UNIQUE);
+    CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER UNIQUE, c TEXT);
+  }
+  set i 1
+  foreach word {one two three four five six seven eight nine ten} {
+    set j [expr {$i*100 + [string length $word]}]
+    db eval {
+       INSERT INTO t1 VALUES($i,$word);
+       INSERT INTO t2 VALUES(20-$i,$j,$word);
+    }
+    incr i
+  }
+  db eval {
+    SELECT count(*) FROM t1 UNION ALL SELECT count(*) FROM t2;
+  }
+} {10 10}
+
+# Create views of various forms against one or both of the two tables.
+#
+do_test triggerA-1.2 {
+  db eval {
+     CREATE VIEW v1 AS SELECT y, x FROM t1;
+     SELECT * FROM v1 ORDER BY 1;
+  }
+} {eight 8 five 5 four 4 nine 9 one 1 seven 7 six 6 ten 10 three 3 two 2}
+do_test triggerA-1.3 {
+  db eval {
+     CREATE VIEW v2 AS SELECT x, y FROM t1 WHERE y GLOB '*e*';
+     SELECT * FROM v2 ORDER BY 1;
+  }
+} {1 one 3 three 5 five 7 seven 8 eight 9 nine 10 ten}
+do_test triggerA-1.4 {
+  db eval {
+     CREATE VIEW v3 AS
+       SELECT CAST(x AS TEXT) AS c1 FROM t1 UNION SELECT y FROM t1;
+     SELECT * FROM v3 ORDER BY c1;
+  }
+} {1 10 2 3 4 5 6 7 8 9 eight five four nine one seven six ten three two}
+do_test triggerA-1.5 {
+  db eval {
+     CREATE VIEW v4 AS
+        SELECT CAST(x AS TEXT) AS c1 FROM t1
+        UNION SELECT y FROM t1 WHERE x BETWEEN 3 and 5;
+     SELECT * FROM v4 ORDER BY 1;
+  }
+} {1 10 2 3 4 5 6 7 8 9 five four three}
+do_test triggerA-1.6 {
+  db eval {
+     CREATE VIEW v5 AS SELECT x, b FROM t1, t2 WHERE y=c;
+     SELECT * FROM v5;
+  }
+} {1 103 2 203 3 305 4 404 5 504 6 603 7 705 8 805 9 904 10 1003}
+
+# Create INSTEAD OF triggers on the views.  Run UPDATE and DELETE statements
+# using those triggers.  Verify correct operation.
+#
+do_test triggerA-2.1 {
+  db eval {
+     CREATE TABLE result2(a,b);
+     CREATE TRIGGER r1d INSTEAD OF DELETE ON v1 BEGIN
+       INSERT INTO result2(a,b) VALUES(old.y, old.x);
+     END;
+     DELETE FROM v1 WHERE x=5;
+     SELECT * FROM result2;
+  }
+} {five 5}
+do_test triggerA-2.2 {
+  db eval {
+     CREATE TABLE result4(a,b,c,d);
+     CREATE TRIGGER r1u INSTEAD OF UPDATE ON v1 BEGIN
+       INSERT INTO result4(a,b,c,d) VALUES(old.y, old.x, new.y, new.x);
+     END;
+     UPDATE v1 SET y=y||'-extra' WHERE x BETWEEN 3 AND 5;
+     SELECT * FROM result4 ORDER BY a;
+  }
+} {five 5 five-extra 5 four 4 four-extra 4 three 3 three-extra 3}
+
+
+do_test triggerA-2.3 {
+  db eval {
+     DELETE FROM result2;
+     CREATE TRIGGER r2d INSTEAD OF DELETE ON v2 BEGIN
+       INSERT INTO result2(a,b) VALUES(old.y, old.x);
+     END;
+     DELETE FROM v2 WHERE x=5;
+     SELECT * FROM result2;
+  }
+} {five 5}
+do_test triggerA-2.4 {
+  db eval {
+     DELETE FROM result4;
+     CREATE TRIGGER r2u INSTEAD OF UPDATE ON v2 BEGIN
+       INSERT INTO result4(a,b,c,d) VALUES(old.y, old.x, new.y, new.x);
+     END;
+     UPDATE v2 SET y=y||'-extra' WHERE x BETWEEN 3 AND 5;
+     SELECT * FROM result4 ORDER BY a;
+  }
+} {five 5 five-extra 5 three 3 three-extra 3}
+
+
+do_test triggerA-2.5 {
+  db eval {
+     CREATE TABLE result1(a);
+     CREATE TRIGGER r3d INSTEAD OF DELETE ON v3 BEGIN
+       INSERT INTO result1(a) VALUES(old.c1);
+     END;
+     DELETE FROM v3 WHERE c1 BETWEEN '8' AND 'eight';
+     SELECT * FROM result1 ORDER BY a;
+  }
+} {8 9 eight}
+do_test triggerA-2.6 {
+  db eval {
+     DELETE FROM result2;
+     CREATE TRIGGER r3u INSTEAD OF UPDATE ON v3 BEGIN
+       INSERT INTO result2(a,b) VALUES(old.c1, new.c1);
+     END;
+     UPDATE v3 SET c1 = c1 || '-extra' WHERE c1 BETWEEN '8' and 'eight';
+     SELECT * FROM result2 ORDER BY a;
+  }
+} {8 8-extra 9 9-extra eight eight-extra}
+
+
+do_test triggerA-2.7 {
+  db eval {
+     DELETE FROM result1;
+     CREATE TRIGGER r4d INSTEAD OF DELETE ON v4 BEGIN
+       INSERT INTO result1(a) VALUES(old.c1);
+     END;
+     DELETE FROM v4 WHERE c1 BETWEEN '8' AND 'eight';
+     SELECT * FROM result1 ORDER BY a;
+  }
+} {8 9}
+do_test triggerA-2.8 {
+  db eval {
+     DELETE FROM result2;
+     CREATE TRIGGER r4u INSTEAD OF UPDATE ON v4 BEGIN
+       INSERT INTO result2(a,b) VALUES(old.c1, new.c1);
+     END;
+     UPDATE v4 SET c1 = c1 || '-extra' WHERE c1 BETWEEN '8' and 'eight';
+     SELECT * FROM result2 ORDER BY a;
+  }
+} {8 8-extra 9 9-extra}
+
+
+do_test triggerA-2.9 {
+  db eval {
+     DELETE FROM result2;
+     CREATE TRIGGER r5d INSTEAD OF DELETE ON v5 BEGIN
+       INSERT INTO result2(a,b) VALUES(old.x, old.b);
+     END;
+     DELETE FROM v5 WHERE x=5;
+     SELECT * FROM result2;
+  }
+} {5 504}
+do_test triggerA-2.10 {
+  db eval {
+     DELETE FROM result4;
+     CREATE TRIGGER r5u INSTEAD OF UPDATE ON v5 BEGIN
+       INSERT INTO result4(a,b,c,d) VALUES(old.x, old.b, new.x, new.b);
+     END;
+     UPDATE v5 SET b = b+9900000 WHERE x BETWEEN 3 AND 5;
+     SELECT * FROM result4 ORDER BY a;
+  }
+} {3 305 3 9900305 4 404 4 9900404 5 504 5 9900504}
+
+# Only run the reamining tests if memory debugging is turned on.
+#
+ifcapable !memdebug {
+   puts "Skipping triggerA malloc tests: not compiled with -DSQLITE_MEMDEBUG..."
+   finish_test
+   return
+}
+source $testdir/malloc_common.tcl
+
+# Save a copy of the current database configuration.
+#
+db close
+file delete -force test.db-triggerA
+file copy test.db test.db-triggerA
+sqlite3 db test.db
+
+# Run malloc tests on the INSTEAD OF trigger firing.
+#
+do_malloc_test triggerA-3 -tclprep {
+  db close
+  file delete -force test.db test.db-journal
+  file copy -force test.db-triggerA test.db
+  sqlite3 db test.db
+  sqlite3_extended_result_codes db 1  
+  db eval {SELECT * FROM v5; -- warm up the cache}
+} -sqlbody {
+   DELETE FROM v5 WHERE x=5;
+   UPDATE v5 SET b=b+9900000 WHERE x BETWEEN 3 AND 5;
+}
+
+# Clean up the saved database copy.
+#
+file delete -force test.db-triggerA
+
+finish_test