1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 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 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
|
# 2013 May 14
#
# 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.
#
#***********************************************************************
#
# Test some specific circumstances to do with shared cache mode.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
if {[run_thread_tests]==0} { finish_test ; return }
db close
set ::testprefix sharedA
if {[atomic_batch_write test.db]} {
finish_test
return
}
set ::enable_shared_cache [sqlite3_enable_shared_cache 1]
#-------------------------------------------------------------------------
#
do_test 0.1 {
sqlite3 db1 test.db
sqlite3 db2 test.db
db1 eval {
CREATE TABLE t1(x);
INSERT INTO t1 VALUES(randomblob(100));
INSERT INTO t1 SELECT randomblob(100) FROM t1;
INSERT INTO t1 SELECT randomblob(100) FROM t1;
INSERT INTO t1 SELECT randomblob(100) FROM t1;
INSERT INTO t1 SELECT randomblob(100) FROM t1;
INSERT INTO t1 SELECT randomblob(100) FROM t1;
INSERT INTO t1 SELECT randomblob(100) FROM t1;
CREATE INDEX i1 ON t1(x);
}
db1 eval {
BEGIN;
DROP INDEX i1;
}
db2 close
db1 eval {
INSERT INTO t1 SELECT randomblob(100) FROM t1;
ROLLBACK;
PRAGMA integrity_check;
}
} {ok}
db1 close
forcedelete test.db
#-------------------------------------------------------------------------
#
do_test 1.1 {
sqlite3 db1 test.db
sqlite3 db2 test.db
db2 eval {
CREATE TABLE t1(x);
INSERT INTO t1 VALUES(123);
}
db1 eval {
SELECT * FROM t1;
CREATE INDEX i1 ON t1(x);
}
} {123}
do_test 1.2 {
db2 eval { SELECT * FROM t1 ORDER BY x; }
db1 eval {
BEGIN; DROP INDEX i1;
}
db1 close
db2 eval { SELECT * FROM t1 ORDER BY x; }
} {123}
do_test 1.3 {
db2 close
} {}
#-------------------------------------------------------------------------
#
# sqlite3RollbackAll() loops through all attached b-trees and rolls
# back each one separately. Then if the SQLITE_InternChanges flag is
# set, it resets the schema. Both of the above steps must be done
# while holding a mutex, otherwise another thread might slip in and
# try to use the new schema with the old data.
#
# The following sequence of tests attempt to verify that the actions
# taken by sqlite3RollbackAll() are thread-atomic (that they cannot be
# interrupted by a separate thread.)
#
# Note that a TCL interpreter can only be used within the thread in which
# it was originally created (because it uses thread-local-storage).
# The tvfs callbacks must therefore only run on the main thread.
# There is some trickery in the read_callback procedure to ensure that
# this is the case.
#
testvfs tvfs
# Set up two databases and two database connections.
#
# db1: main(test.db), two(test2.db)
# db2: main(test.db)
#
# The cache for test.db is shared between db1 and db2.
#
do_test 2.1 {
forcedelete test.db test.db2
sqlite3 db1 test.db -vfs tvfs
db1 eval { ATTACH 'test.db2' AS two }
db1 eval {
CREATE TABLE t1(x);
INSERT INTO t1 VALUES(1);
INSERT INTO t1 VALUES(2);
INSERT INTO t1 VALUES(3);
CREATE TABLE two.t2(x);
INSERT INTO t2 SELECT * FROM t1;
}
sqlite3 db2 test.db -vfs tvfs
db2 eval { SELECT * FROM t1 }
} {1 2 3}
# Create a prepared statement on db2 that will attempt a schema change
# in test.db. Meanwhile, start a transaction on db1 that changes
# the schema of test.db and that creates a rollback journal on test2.db
#
do_test 2.2 {
set ::STMT [sqlite3_prepare db2 "CREATE INDEX i1 ON t1(x)" -1 tail]
db1 eval {
BEGIN;
CREATE INDEX i1 ON t1(x);
INSERT INTO t2 VALUES('value!');
}
} {}
# Set up a callback that will cause db2 to try to execute its
# schema change when db1 accesses the journal file of test2.db.
#
# This callback will be invoked after the content of test.db has
# be rolled back but before the schema has been reset. If the
# sqlite3RollbackAll() operation is not thread-atomic, then the
# db2 statement in the callback will see old content with the newer
# schema, which is wrong.
#
tvfs filter xRead
tvfs script read_callback
unset -nocomplain ::some_time_laster
unset -nocomplain ::thread_result
proc read_callback {call file args} {
if {[string match *test.db2-journal $file]} {
tvfs filter {} ;# Ensure that tvfs callbacks to do run on the
# child thread
sqlthread spawn ::thread_result [subst -nocommands {
sqlite3_step $::STMT
set rc [sqlite3_finalize $::STMT]
}]
after 1000 { set ::some_time_later 1 }
vwait ::some_time_later
}
}
do_test 2.3 { db1 eval ROLLBACK } {}
# Verify that the db2 statement invoked by the callback detected the
# schema change.
#
if {[info exists ::thread_result]==0} { vwait ::thread_result }
do_test 2.4 {
list $::thread_result [sqlite3_errmsg db2]
} {SQLITE_SCHEMA {database schema has changed}}
db1 close
db2 close
tvfs delete
sqlite3_enable_shared_cache $::enable_shared_cache
finish_test
|