1*e91a54e1Sdan# 2010 June 15 2*e91a54e1Sdan# 3*e91a54e1Sdan# The author disclaims copyright to this source code. In place of 4*e91a54e1Sdan# a legal notice, here is a blessing: 5*e91a54e1Sdan# 6*e91a54e1Sdan# May you do good and not evil. 7*e91a54e1Sdan# May you find forgiveness for yourself and forgive others. 8*e91a54e1Sdan# May you share freely, never taking more than you give. 9*e91a54e1Sdan# 10*e91a54e1Sdan#*********************************************************************** 11*e91a54e1Sdan# 12*e91a54e1Sdan 13*e91a54e1Sdanset testdir [file dirname $argv0] 14*e91a54e1Sdansource $testdir/tester.tcl 15*e91a54e1Sdansource $testdir/lock_common.tcl 16*e91a54e1Sdansource $testdir/malloc_common.tcl 17*e91a54e1Sdan 18*e91a54e1Sdanforeach code [list { 19*e91a54e1Sdan set ::code2_chan [launch_testfixture] 20*e91a54e1Sdan set ::code3_chan [launch_testfixture] 21*e91a54e1Sdan proc code2 {tcl} { testfixture $::code2_chan $tcl } 22*e91a54e1Sdan proc code3 {tcl} { testfixture $::code3_chan $tcl } 23*e91a54e1Sdan set tn 1 24*e91a54e1Sdan} { 25*e91a54e1Sdan proc code2 {tcl} { uplevel #0 $tcl } 26*e91a54e1Sdan proc code3 {tcl} { uplevel #0 $tcl } 27*e91a54e1Sdan set tn 2 28*e91a54e1Sdan}] { 29*e91a54e1Sdan 30*e91a54e1Sdan faultsim_delete_and_reopen 31*e91a54e1Sdan 32*e91a54e1Sdan # Open connections [db2] and [db3]. Depending on which iteration this 33*e91a54e1Sdan # is, the connections may be created in this interpreter, or in 34*e91a54e1Sdan # interpreters running in other OS processes. As such, the [db2] and [db3] 35*e91a54e1Sdan # commands should only be accessed within [code2] and [code3] blocks, 36*e91a54e1Sdan # respectively. 37*e91a54e1Sdan # 38*e91a54e1Sdan eval $code 39*e91a54e1Sdan code2 { sqlite3 db2 test.db } 40*e91a54e1Sdan code3 { sqlite3 db3 test.db } 41*e91a54e1Sdan 42*e91a54e1Sdan # Shorthand commands. Execute SQL using database connection [db2] or 43*e91a54e1Sdan # [db3]. Return the results. 44*e91a54e1Sdan # 45*e91a54e1Sdan proc sql1 {sql} { db eval $sql } 46*e91a54e1Sdan proc sql2 {sql} { code2 [list db2 eval $sql] } 47*e91a54e1Sdan proc sql3 {sql} { code3 [list db3 eval $sql] } 48*e91a54e1Sdan 49*e91a54e1Sdan proc csql1 {sql} { list [catch { sql1 $sql } msg] $msg } 50*e91a54e1Sdan proc csql2 {sql} { list [catch { sql2 $sql } msg] $msg } 51*e91a54e1Sdan proc csql3 {sql} { list [catch { sql3 $sql } msg] $msg } 52*e91a54e1Sdan 53*e91a54e1Sdan # Create and populate a database table using connection [db]. Check 54*e91a54e1Sdan # that connections [db2] and [db3] can see the schema and content. 55*e91a54e1Sdan # 56*e91a54e1Sdan do_test pager1-$tn.1 { 57*e91a54e1Sdan sql1 { 58*e91a54e1Sdan CREATE TABLE t1(a PRIMARY KEY, b); 59*e91a54e1Sdan CREATE INDEX i1 ON t1(b); 60*e91a54e1Sdan INSERT INTO t1 VALUES(1, 'one'); 61*e91a54e1Sdan INSERT INTO t1 VALUES(2, 'two'); 62*e91a54e1Sdan } 63*e91a54e1Sdan } {} 64*e91a54e1Sdan do_test pager1-$tn.2 { sql2 { SELECT * FROM t1 } } {1 one 2 two} 65*e91a54e1Sdan do_test pager1-$tn.3 { sql3 { SELECT * FROM t1 } } {1 one 2 two} 66*e91a54e1Sdan 67*e91a54e1Sdan # Open a transaction and add a row using [db]. This puts [db] in 68*e91a54e1Sdan # RESERVED state. Check that connections [db2] and [db3] can still 69*e91a54e1Sdan # read the database content as it was before the transaction was 70*e91a54e1Sdan # opened. [db] should see the inserted row. 71*e91a54e1Sdan # 72*e91a54e1Sdan do_test pager1-$tn.4 { 73*e91a54e1Sdan sql1 { 74*e91a54e1Sdan BEGIN; 75*e91a54e1Sdan INSERT INTO t1 VALUES(3, 'three'); 76*e91a54e1Sdan } 77*e91a54e1Sdan } {} 78*e91a54e1Sdan do_test pager1-$tn.5 { sql2 { SELECT * FROM t1 } } {1 one 2 two} 79*e91a54e1Sdan do_test pager1-$tn.6 { sql3 { SELECT * FROM t1 } } {1 one 2 two} 80*e91a54e1Sdan do_test pager1-$tn.7 { sql1 { SELECT * FROM t1 } } {1 one 2 two 3 three} 81*e91a54e1Sdan 82*e91a54e1Sdan # [db] still has an open write transaction. Check that this prevents 83*e91a54e1Sdan # other connections (specifically [db2]) from writing to the database. 84*e91a54e1Sdan # 85*e91a54e1Sdan # Even if [db2] opens a transaction first, it may not write to the 86*e91a54e1Sdan # database. After the attempt to write the db within a transaction, 87*e91a54e1Sdan # [db2] is left with an open transaction, but not a read-lock on 88*e91a54e1Sdan # the main database. So it does not prevent [db] from committing. 89*e91a54e1Sdan # 90*e91a54e1Sdan do_test pager1-$tn.8 { 91*e91a54e1Sdan csql2 { UPDATE t1 SET a = a + 10 } 92*e91a54e1Sdan } {1 {database is locked}} 93*e91a54e1Sdan do_test pager1-$tn.9 { 94*e91a54e1Sdan csql2 { 95*e91a54e1Sdan BEGIN; 96*e91a54e1Sdan UPDATE t1 SET a = a + 10; 97*e91a54e1Sdan } 98*e91a54e1Sdan } {1 {database is locked}} 99*e91a54e1Sdan 100*e91a54e1Sdan # Have [db] commit its transactions. Check the other connections can 101*e91a54e1Sdan # now see the new database content. 102*e91a54e1Sdan # 103*e91a54e1Sdan do_test pager1-$tn.10 { sql1 { COMMIT } } {} 104*e91a54e1Sdan do_test pager1-$tn.11 { sql1 { SELECT * FROM t1 } } {1 one 2 two 3 three} 105*e91a54e1Sdan do_test pager1-$tn.12 { sql2 { SELECT * FROM t1 } } {1 one 2 two 3 three} 106*e91a54e1Sdan do_test pager1-$tn.13 { sql3 { SELECT * FROM t1 } } {1 one 2 two 3 three} 107*e91a54e1Sdan 108*e91a54e1Sdan # Check that, as noted above, [db2] really did keep an open transaction 109*e91a54e1Sdan # after the attempt to write the database failed. 110*e91a54e1Sdan # 111*e91a54e1Sdan do_test pager1-$tn.14 { 112*e91a54e1Sdan csql2 { BEGIN } 113*e91a54e1Sdan } {1 {cannot start a transaction within a transaction}} 114*e91a54e1Sdan do_test pager1-$tn.15 { sql2 { ROLLBACK } } {} 115*e91a54e1Sdan 116*e91a54e1Sdan # Have [db2] open a transaction and take a read-lock on the database. 117*e91a54e1Sdan # Check that this prevents [db] from writing to the database (outside 118*e91a54e1Sdan # of any transaction). After this fails, check that [db3] can read 119*e91a54e1Sdan # the db (showing that [db] did not take a PENDING lock etc.) 120*e91a54e1Sdan # 121*e91a54e1Sdan do_test pager1-$tn.15 { 122*e91a54e1Sdan sql2 { BEGIN; SELECT * FROM t1; } 123*e91a54e1Sdan } {1 one 2 two 3 three} 124*e91a54e1Sdan do_test pager1-$tn.16 { 125*e91a54e1Sdan csql1 { UPDATE t1 SET a = a + 10 } 126*e91a54e1Sdan } {1 {database is locked}} 127*e91a54e1Sdan do_test pager1-$tn.17 { sql3 { SELECT * FROM t1 } } {1 one 2 two 3 three} 128*e91a54e1Sdan 129*e91a54e1Sdan # This time, have [db] open a transaction before writing the database. 130*e91a54e1Sdan # This works - [db] gets a RESERVED lock which does not conflict with 131*e91a54e1Sdan # the SHARED lock [db2] is holding. 132*e91a54e1Sdan # 133*e91a54e1Sdan do_test pager1-$tn.18 { 134*e91a54e1Sdan sql1 { 135*e91a54e1Sdan BEGIN; 136*e91a54e1Sdan UPDATE t1 SET a = a + 10; 137*e91a54e1Sdan } 138*e91a54e1Sdan } {} 139*e91a54e1Sdan do_test pager1-$tn-19 { 140*e91a54e1Sdan sql1 { PRAGMA lock_status } 141*e91a54e1Sdan } {main reserved temp closed} 142*e91a54e1Sdan do_test pager1-$tn-20 { 143*e91a54e1Sdan sql2 { PRAGMA lock_status } 144*e91a54e1Sdan } {main shared temp closed} 145*e91a54e1Sdan 146*e91a54e1Sdan # Check that all connections can still read the database. Only [db] sees 147*e91a54e1Sdan # the updated content (as the transaction has not been committed yet). 148*e91a54e1Sdan # 149*e91a54e1Sdan do_test pager1-$tn.21 { sql1 { SELECT * FROM t1 } } {11 one 12 two 13 three} 150*e91a54e1Sdan do_test pager1-$tn.22 { sql2 { SELECT * FROM t1 } } {1 one 2 two 3 three} 151*e91a54e1Sdan do_test pager1-$tn.23 { sql3 { SELECT * FROM t1 } } {1 one 2 two 3 three} 152*e91a54e1Sdan 153*e91a54e1Sdan # Because [db2] still has the SHARED lock, [db] is unable to commit the 154*e91a54e1Sdan # transaction. If it tries, an error is returned and the connection 155*e91a54e1Sdan # upgrades to a PENDING lock. 156*e91a54e1Sdan # 157*e91a54e1Sdan # Once this happens, [db] can read the database and see the new content, 158*e91a54e1Sdan # [db2] (still holding SHARED) can still read the old content, but [db3] 159*e91a54e1Sdan # (not holding any lock) is prevented by [db]'s PENDING from reading 160*e91a54e1Sdan # the database. 161*e91a54e1Sdan # 162*e91a54e1Sdan do_test pager1-$tn.24 { csql1 { COMMIT } } {1 {database is locked}} 163*e91a54e1Sdan do_test pager1-$tn-25 { 164*e91a54e1Sdan sql1 { PRAGMA lock_status } 165*e91a54e1Sdan } {main pending temp closed} 166*e91a54e1Sdan do_test pager1-$tn.26 { sql1 { SELECT * FROM t1 } } {11 one 12 two 13 three} 167*e91a54e1Sdan do_test pager1-$tn.27 { sql2 { SELECT * FROM t1 } } {1 one 2 two 3 three} 168*e91a54e1Sdan do_test pager1-$tn.28 { csql3 { SELECT * FROM t1 } } {1 {database is locked}} 169*e91a54e1Sdan 170*e91a54e1Sdan # Have [db2] commit its read transaction, releasing the SHARED lock it 171*e91a54e1Sdan # is holding. Now, neither [db2] nor [db3] may read the database (as [db] 172*e91a54e1Sdan # is still holding a PENDING). 173*e91a54e1Sdan # 174*e91a54e1Sdan do_test pager1-$tn.29 { sql2 { COMMIT } } {} 175*e91a54e1Sdan do_test pager1-$tn.30 { csql2 { SELECT * FROM t1 } } {1 {database is locked}} 176*e91a54e1Sdan do_test pager1-$tn.31 { csql3 { SELECT * FROM t1 } } {1 {database is locked}} 177*e91a54e1Sdan 178*e91a54e1Sdan # [db] is now able to commit the transaction. Once the transaction is 179*e91a54e1Sdan # committed, all three connections can read the new content. 180*e91a54e1Sdan # 181*e91a54e1Sdan do_test pager1-$tn.25 { sql1 { UPDATE t1 SET a = a+10 } } {} 182*e91a54e1Sdan do_test pager1-$tn.26 { sql1 { COMMIT } } {} 183*e91a54e1Sdan do_test pager1-$tn.27 { sql1 { SELECT * FROM t1 } } {21 one 22 two 23 three} 184*e91a54e1Sdan do_test pager1-$tn.27 { sql2 { SELECT * FROM t1 } } {21 one 22 two 23 three} 185*e91a54e1Sdan do_test pager1-$tn.28 { sql3 { SELECT * FROM t1 } } {21 one 22 two 23 three} 186*e91a54e1Sdan 187*e91a54e1Sdan code2 { db2 close } 188*e91a54e1Sdan code3 { db3 close } 189*e91a54e1Sdan catch { close $::code2_chan } 190*e91a54e1Sdan catch { close $::code3_chan } 191*e91a54e1Sdan} 192*e91a54e1Sdan 193*e91a54e1Sdanfinish_test 194*e91a54e1Sdan 195