1# 2004 Feb 8 2# 3# The author disclaims copyright to this source code. In place of 4# a legal notice, here is a blessing: 5# 6# May you do good and not evil. 7# May you find forgiveness for yourself and forgive others. 8# May you share freely, never taking more than you give. 9# 10#*********************************************************************** 11# This file implements regression tests for SQLite library. The 12# focus of this script is the sqlite_interrupt() API. 13# 14# $Id: interrupt.test,v 1.15 2007/06/13 16:49:49 danielk1977 Exp $ 15 16 17set testdir [file dirname $argv0] 18source $testdir/tester.tcl 19set DB [sqlite3_connection_pointer db] 20 21# Compute a checksum on the entire database. 22# 23proc cksum {{db db}} { 24 set txt [$db eval {SELECT name, type, sql FROM sqlite_master}]\n 25 foreach tbl [$db eval {SELECT name FROM sqlite_master WHERE type='table'}] { 26 append txt [$db eval "SELECT * FROM $tbl"]\n 27 } 28 foreach prag {default_synchronous default_cache_size} { 29 append txt $prag-[$db eval "PRAGMA $prag"]\n 30 } 31 set cksum [string length $txt]-[md5 $txt] 32 # puts $cksum-[file size test.db] 33 return $cksum 34} 35 36# This routine attempts to execute the sql in $sql. It triggers an 37# interrupt at progressively later and later points during the processing 38# and checks to make sure SQLITE_INTERRUPT is returned. Eventually, 39# the routine completes successfully. 40# 41proc interrupt_test {testid sql result {initcnt 0}} { 42 set orig_sum [cksum] 43 set i $initcnt 44 while 1 { 45 incr i 46 set ::sqlite_interrupt_count $i 47 do_test $testid.$i.1 [format { 48 set ::r [catchsql %s] 49 set ::code [db errorcode] 50 expr {$::code==0 || $::code==9} 51 } [list $sql]] 1 52 if {$::code==9} { 53 do_test $testid.$i.2 { 54 cksum 55 } $orig_sum 56 } else { 57 do_test $testid.$i.99 { 58 set ::r 59 } [list 0 $result] 60 break 61 } 62 } 63 set ::sqlite_interrupt_count 0 64} 65 66do_test interrupt-1.1 { 67 execsql { 68 CREATE TABLE t1(a,b); 69 SELECT name FROM sqlite_master; 70 } 71} {t1} 72interrupt_test interrupt-1.2 {DROP TABLE t1} {} 73do_test interrupt-1.3 { 74 execsql { 75 SELECT name FROM sqlite_master; 76 } 77} {} 78integrity_check interrupt-1.4 79 80do_test interrrupt-2.1 { 81 execsql { 82 BEGIN; 83 CREATE TABLE t1(a,b); 84 INSERT INTO t1 VALUES(1,randstr(300,400)); 85 INSERT INTO t1 SELECT a+1, randstr(300,400) FROM t1; 86 INSERT INTO t1 SELECT a+2, a || '-' || b FROM t1; 87 INSERT INTO t1 SELECT a+4, a || '-' || b FROM t1; 88 INSERT INTO t1 SELECT a+8, a || '-' || b FROM t1; 89 INSERT INTO t1 SELECT a+16, a || '-' || b FROM t1; 90 INSERT INTO t1 SELECT a+32, a || '-' || b FROM t1; 91 COMMIT; 92 UPDATE t1 SET b=substr(b,-5,5); 93 SELECT count(*) from t1; 94 } 95} 64 96set origsize [file size test.db] 97set cksum [db eval {SELECT md5sum(a || b) FROM t1}] 98ifcapable {vacuum} { 99 interrupt_test interrupt-2.2 {VACUUM} {} 100 100} 101do_test interrupt-2.3 { 102 execsql { 103 SELECT md5sum(a || b) FROM t1; 104 } 105} $cksum 106ifcapable {vacuum && !default_autovacuum} { 107 do_test interrupt-2.4 { 108 expr {$::origsize>[file size test.db]} 109 } 1 110} 111ifcapable {explain} { 112 do_test interrupt-2.5 { 113 set sql {EXPLAIN SELECT max(a,b), a, b FROM t1} 114 execsql $sql 115 set rc [catch {db eval $sql {sqlite3_interrupt $DB}} msg] 116 lappend rc $msg 117 } {1 interrupted} 118} 119integrity_check interrupt-2.6 120 121# Ticket #594. If an interrupt occurs in the middle of a transaction 122# and that transaction is later rolled back, the internal schema tables do 123# not reset. 124# 125# UPDATE: Interrupting a DML statement in the middle of a transaction now 126# causes the transaction to roll back. Leaving the transaction open after 127# an SQL statement was interrupted halfway through risks database corruption. 128# 129ifcapable tempdb { 130 for {set i 1} {$i<50} {incr i 5} { 131 do_test interrupt-3.$i.1 { 132 execsql { 133 BEGIN; 134 CREATE TEMP TABLE t2(x,y); 135 SELECT name FROM sqlite_temp_master; 136 } 137 } {t2} 138 do_test interrupt-3.$i.2 { 139 set ::sqlite_interrupt_count $::i 140 catchsql { 141 INSERT INTO t2 SELECT * FROM t1; 142 } 143 } {1 interrupted} 144 do_test interrupt-3.$i.3 { 145 execsql { 146 SELECT name FROM sqlite_temp_master; 147 } 148 } {} 149 do_test interrupt-3.$i.4 { 150 catchsql { 151 ROLLBACK 152 } 153 } {1 {cannot rollback - no transaction is active}} 154 do_test interrupt-3.$i.5 { 155 catchsql {SELECT name FROM sqlite_temp_master}; 156 execsql { 157 SELECT name FROM sqlite_temp_master; 158 } 159 } {} 160 } 161} 162 163# There are reports of a memory leak if an interrupt occurs during 164# the beginning of a complex query - before the first callback. We 165# will try to reproduce it here: 166# 167execsql { 168 CREATE TABLE t2(a,b,c); 169 INSERT INTO t2 SELECT round(a/10), randstr(50,80), randstr(50,60) FROM t1; 170} 171set sql { 172 SELECT max(min(b,c)), min(max(b,c)), a FROM t2 GROUP BY a ORDER BY a; 173} 174set sqlite_interrupt_count 1000000 175execsql $sql 176set max_count [expr {1000000-$sqlite_interrupt_count}] 177for {set i 1} {$i<$max_count-5} {incr i 1} { 178 do_test interrupt-4.$i.1 { 179 set ::sqlite_interrupt_count $::i 180 catchsql $sql 181 } {1 interrupted} 182} 183 184# Interrupt during parsing 185# 186do_test interrupt-5.1 { 187 proc fake_interrupt {args} { 188 db collate fake_collation no-op 189 sqlite3_interrupt db 190 return SQLITE_OK 191 } 192 db collation_needed fake_interrupt 193 catchsql { 194 CREATE INDEX fake ON fake1(a COLLATE fake_collation, b, c DESC); 195 } 196} {1 interrupt} 197 198finish_test 199