Update the sessions branch with the latest trunk changes.

FossilOrigin-Name: d09355050a74344c1cb6d303af9f601cd41e2368
This commit is contained in:
drh 2013-05-15 18:45:14 +00:00
commit ad1818d9ed
10 changed files with 457 additions and 54 deletions

114
ext/misc/rot13.c Normal file
View File

@ -0,0 +1,114 @@
/*
** 2013-05-15
**
** 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 SQLite extension implements a rot13() function and a rot13
** collating sequence.
*/
#include "sqlite3ext.h"
SQLITE_EXTENSION_INIT1
#include <assert.h>
#include <string.h>
/*
** Perform rot13 encoding on a single ASCII character.
*/
static unsigned char rot13(unsigned char c){
if( c>='a' && c<='z' ){
c += 13;
if( c>'z' ) c -= 26;
}else if( c>='A' && c<='Z' ){
c += 13;
if( c>'Z' ) c -= 26;
}
return c;
}
/*
** Implementation of the rot13() function.
**
** Rotate ASCII alphabetic characters by 13 character positions.
** Non-ASCII characters are unchanged. rot13(rot13(X)) should always
** equal X.
*/
static void rot13func(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
const unsigned char *zIn;
int nIn;
unsigned char *zOut;
char *zToFree = 0;
int i;
char zTemp[100];
assert( argc==1 );
if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
zIn = (const unsigned char*)sqlite3_value_text(argv[0]);
nIn = sqlite3_value_bytes(argv[0]);
if( nIn<sizeof(zTemp)-1 ){
zOut = zTemp;
}else{
zOut = zToFree = sqlite3_malloc( nIn+1 );
if( zOut==0 ){
sqlite3_result_error_nomem(context);
return;
}
}
for(i=0; i<nIn; i++) zOut[i] = rot13(zIn[i]);
zOut[i] = 0;
sqlite3_result_text(context, (char*)zOut, i, SQLITE_TRANSIENT);
sqlite3_free(zToFree);
}
/*
** Implement the rot13 collating sequence so that if
**
** x=y COLLATE rot13
**
** Then
**
** rot13(x)=rot13(y) COLLATE binary
*/
static int rot13CollFunc(
void *notUsed,
int nKey1, const void *pKey1,
int nKey2, const void *pKey2
){
const char *zA = (const char*)pKey1;
const char *zB = (const char*)pKey2;
int i, x;
for(i=0; i<nKey1 && i<nKey2; i++){
x = (int)rot13(zA[i]) - (int)rot13(zB[i]);
if( x!=0 ) return x;
}
return nKey1 - nKey2;
}
#ifdef _WIN32
__declspec(dllexport)
#endif
int sqlite3_rot_init(
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
){
int rc = SQLITE_OK;
SQLITE_EXTENSION_INIT2(pApi);
(void)pzErrMsg; /* Unused parameter */
rc = sqlite3_create_function(db, "rot13", 1, SQLITE_UTF8, 0,
rot13func, 0, 0);
if( rc==SQLITE_OK ){
rc = sqlite3_create_collation(db, "rot13", SQLITE_UTF8, 0, rot13CollFunc);
}
return rc;
}

View File

@ -297,6 +297,7 @@ TESTSRC2 = \
$(TOP)/src/func.c \
$(TOP)/src/insert.c \
$(TOP)/src/wal.c \
$(TOP)/src/main.c \
$(TOP)/src/mem5.c \
$(TOP)/src/os.c \
$(TOP)/src/os_unix.c \

View File

@ -1,5 +1,5 @@
C Merge\strunk\schanges\sinto\sthe\ssessions\sbranch.
D 2013-05-09T23:40:59.000
C Update\sthe\ssessions\sbranch\swith\sthe\slatest\strunk\schanges.
D 2013-05-15T18:45:14.080
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
F Makefile.in e2acdd75b30e5f2fd8739c923c746d9d2228fe9a
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@ -89,6 +89,7 @@ F ext/misc/fuzzer.c fb64a15af978ae73fa9075b9b1dfbe82b8defc6f
F ext/misc/ieee754.c 2565ce373d842977efe0922dc50b8a41b3289556
F ext/misc/nextchar.c 1131e2b36116ffc6fe6b2e3464bfdace27978b1e
F ext/misc/regexp.c c25c65fe775f5d9801fb8573e36ebe73f2c0c2e0
F ext/misc/rot13.c 1ac6f95f99b575907b9b09c81a349114cf9be45a
F ext/misc/spellfix.c f9d24a2b2617cee143b7841b453e4e1fd8f189cc
F ext/misc/wholenumber.c 784b12543d60702ebdd47da936e278aa03076212
F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761
@ -125,7 +126,7 @@ F ext/session/test_session.c 23eddaf713708ae063d278ec6297652e3672dc38
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
F magic.txt 3f820e18c43504b25da40ff4b4cdb66dc4c4907e
F main.mk 06e980ed70c3fa4c27c35ff38735af6e05a64304
F main.mk e1aa28b8104784696a7ca9c15d0b6c4bf156ae02
F mkdll.sh 7d09b23c05d56532e9d44a50868eb4b12ff4f74a
F mkextu.sh 416f9b7089d80e5590a29692c9d9280a10dbad9f
F mkextw.sh 4123480947681d9b434a5e7b1ee08135abe409ac
@ -149,16 +150,16 @@ F src/auth.c 523da7fb4979469955d822ff9298352d6b31de34
F src/backup.c b266767351ae2d847716c56fcb2a1fea7c761c03
F src/bitvec.c 19a4ba637bd85f8f63fc8c9bae5ade9fb05ec1cb
F src/btmutex.c 976f45a12e37293e32cae0281b15a21d48a8aaa7
F src/btree.c 480a6d255cc4f066029daf23dd54acf152cd0e13
F src/btree.c fcfbe61a311e54224b23527bbf7586ce320e7b40
F src/btree.h 6fa8a3ff2483d0bb64a9f0105a8cedeac9e00cca
F src/btreeInt.h eecc84f02375b2bb7a44abbcbbe3747dde73edb2
F src/build.c 083da8466fd7e481cb8bd5264398f537507f6176
F src/build.c 92ef9483189389828966153c5950f2e5a49c1182
F src/callback.c d7e46f40c3cf53c43550b7da7a1d0479910b62cc
F src/complete.c dc1d136c0feee03c2f7550bafc0d29075e36deac
F src/ctime.c 4262c227bc91cecc61ae37ed3a40f08069cfa267
F src/date.c 067a81c9942c497aafd2c260e13add8a7d0c7dd4
F src/delete.c 39a770e9729b1acd2de347f8f614584841d0083e
F src/expr.c 437c03d5bb4fe3a53ecab3ad0286d6c5260da7ed
F src/expr.c e40d198a719aba1d2514f6e1541f9154f976ceb6
F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb
F src/fkey.c e16942bd5c8a868ac53287886464a5ed0e72b179
F src/func.c d3fdcff9274bc161152e67ed3f626841c247f4b9
@ -171,7 +172,7 @@ F src/journal.c b4124532212b6952f42eb2c12fa3c25701d8ba8d
F src/legacy.c 0df0b1550b9cc1f58229644735e317ac89131f12
F src/lempar.c cdf0a000315332fc9b50b62f3b5e22e080a0952b
F src/loadext.c c48f7f3f170e502fe0cc20748e03c6e0b5a016c2
F src/main.c dca921cb27c2221fe726a962b74aad19255a5bf4
F src/main.c 393460eeb03d98f52863c1def596f988b33fe2a1
F src/malloc.c fe085aa851b666b7c375c1ff957643dc20a04bf6
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
F src/mem1.c 437c7c4af964895d4650f29881df63535caaa1fa
@ -353,7 +354,7 @@ F test/closure01.test 6194a899cdbba561d0439c0d6cc7bcdf4fc413e7
F test/coalesce.test cee0dccb9fbd2d494b77234bccf9dc6c6786eb91
F test/collate1.test fd02c4d8afc71879c4bb952586389961a21fb0ce
F test/collate2.test 04cebe4a033be319d6ddbb3bbc69464e01700b49
F test/collate3.test d28d2cfab2c3a3d4628ae4b2b7afc9965daa3b4c
F test/collate3.test 79558a286362cb9ed603c6fa543f1cda7f563f0f
F test/collate4.test 031f7265c13308b724ba3c49f41cc04612bd92b1
F test/collate5.test 65d928034d30d2d263a80f6359f7549ee1598ec6
F test/collate6.test 8be65a182abaac8011a622131486dafb8076e907
@ -750,6 +751,7 @@ F test/shared6.test 866bb4982c45ce216c61ded5e8fde4e7e2f3ffa9
F test/shared7.test 960760bc8d03e1419e70dea69cf41db62853616e
F test/shared8.test b27befbefbe7f4517f1d6b7ff8f64a41ec74165d
F test/shared9.test 5f2a8f79b4d6c7d107a01ffa1ed05ae7e6333e21
F test/sharedA.test 0cdf1a76dfa00e6beee66af5b534b1e8df2720f5
F test/shared_err.test 0079c05c97d88cfa03989b7c20a8b266983087aa
F test/sharedlock.test ffa0a3c4ac192145b310f1254f8afca4d553eabf
F test/shell1.test 4a2f57952719972c6f862134463f8712e953c038
@ -1075,7 +1077,7 @@ F tool/vdbe-compress.tcl f12c884766bd14277f4fcedcae07078011717381
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381
F tool/win/sqlite.vsix 97894c2790eda7b5bce3cc79cb2a8ec2fde9b3ac
P ae6c4a0906ad8caabd8c605bb39c5fb979ab39a2 cf5c3642247fdd34d87f0368594cd7b8f081636a
R aa02d52ce458f281b5bc9615e6d33430
P 512f8a1ef8c4cf50723cfc4a78b7370dc37358d9 867b3e3b29a357f68e48f0898bf323c5dd0575a4
R ffa29f370c90d286799404471155a28b
U drh
Z 94e59add3d7670652b5bb83515516e8f
Z 1d96d56a3ef3c0775b8112327033ec85

View File

@ -1 +1 @@
512f8a1ef8c4cf50723cfc4a78b7370dc37358d9
d09355050a74344c1cb6d303af9f601cd41e2368

View File

@ -2517,6 +2517,29 @@ page1_init_failed:
return rc;
}
#ifndef NDEBUG
/*
** Return the number of cursors open on pBt. This is for use
** in assert() expressions, so it is only compiled if NDEBUG is not
** defined.
**
** Only write cursors are counted if wrOnly is true. If wrOnly is
** false then all cursors are counted.
**
** For the purposes of this routine, a cursor is any cursor that
** is capable of reading or writing to the databse. Cursors that
** have been tripped into the CURSOR_FAULT state are not counted.
*/
static int countValidCursors(BtShared *pBt, int wrOnly){
BtCursor *pCur;
int r = 0;
for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){
if( (wrOnly==0 || pCur->wrFlag) && pCur->eState!=CURSOR_FAULT ) r++;
}
return r;
}
#endif
/*
** If there are no outstanding cursors and we are not in the middle
** of a transaction but there is a read lock on the database, then
@ -2527,7 +2550,7 @@ page1_init_failed:
*/
static void unlockBtreeIfUnused(BtShared *pBt){
assert( sqlite3_mutex_held(pBt->mutex) );
assert( pBt->pCursor==0 || pBt->inTransaction>TRANS_NONE );
assert( countValidCursors(pBt,0)==0 || pBt->inTransaction>TRANS_NONE );
if( pBt->inTransaction==TRANS_NONE && pBt->pPage1!=0 ){
assert( pBt->pPage1->aData );
assert( sqlite3PagerRefcount(pBt->pPager)==1 );
@ -3255,7 +3278,6 @@ static void btreeEndTransaction(Btree *p){
#ifndef SQLITE_OMIT_AUTOVACUUM
pBt->bDoTruncate = 0;
#endif
btreeClearHasContent(pBt);
if( p->inTrans>TRANS_NONE && p->db->activeVdbeCnt>1 ){
/* If there are other active statements that belong to this database
** handle, downgrade to a read-only transaction. The other statements
@ -3330,6 +3352,7 @@ int sqlite3BtreeCommitPhaseTwo(Btree *p, int bCleanup){
return rc;
}
pBt->inTransaction = TRANS_READ;
btreeClearHasContent(pBt);
}
btreeEndTransaction(p);
@ -3351,27 +3374,6 @@ int sqlite3BtreeCommit(Btree *p){
return rc;
}
#ifndef NDEBUG
/*
** Return the number of write-cursors open on this handle. This is for use
** in assert() expressions, so it is only compiled if NDEBUG is not
** defined.
**
** For the purposes of this routine, a write-cursor is any cursor that
** is capable of writing to the databse. That means the cursor was
** originally opened for writing and the cursor has not be disabled
** by having its state changed to CURSOR_FAULT.
*/
static int countWriteCursors(BtShared *pBt){
BtCursor *pCur;
int r = 0;
for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){
if( pCur->wrFlag && pCur->eState!=CURSOR_FAULT ) r++;
}
return r;
}
#endif
/*
** This routine sets the state to CURSOR_FAULT and the error
** code to errCode for every cursor on BtShared that pBtree
@ -3451,8 +3453,9 @@ int sqlite3BtreeRollback(Btree *p, int tripCode){
pBt->nPage = nPage;
releasePage(pPage1);
}
assert( countWriteCursors(pBt)==0 );
assert( countValidCursors(pBt, 1)==0 );
pBt->inTransaction = TRANS_READ;
btreeClearHasContent(pBt);
}
btreeEndTransaction(p);

View File

@ -2659,10 +2659,8 @@ Index *sqlite3CreateIndex(
for(i=0; i<pList->nExpr; i++){
Expr *pExpr = pList->a[i].pExpr;
if( pExpr ){
CollSeq *pColl = sqlite3ExprCollSeq(pParse, pExpr);
if( pColl ){
nExtra += (1 + sqlite3Strlen30(pColl->zName));
}
assert( pExpr->op==TK_COLLATE );
nExtra += (1 + sqlite3Strlen30(pExpr->u.zToken));
}
}
@ -2723,7 +2721,6 @@ Index *sqlite3CreateIndex(
const char *zColName = pListItem->zName;
Column *pTabCol;
int requestedSortOrder;
CollSeq *pColl; /* Collating sequence */
char *zColl; /* Collation sequence name */
for(j=0, pTabCol=pTab->aCol; j<pTab->nCol; j++, pTabCol++){
@ -2736,11 +2733,10 @@ Index *sqlite3CreateIndex(
goto exit_create_index;
}
pIndex->aiColumn[i] = j;
if( pListItem->pExpr
&& (pColl = sqlite3ExprCollSeq(pParse, pListItem->pExpr))!=0
){
if( pListItem->pExpr ){
int nColl;
zColl = pColl->zName;
assert( pListItem->pExpr->op==TK_COLLATE );
zColl = pListItem->pExpr->u.zToken;
nColl = sqlite3Strlen30(zColl) + 1;
assert( nExtra>=nColl );
memcpy(zExtra, zColl, nColl);
@ -2749,9 +2745,7 @@ Index *sqlite3CreateIndex(
nExtra -= nColl;
}else{
zColl = pTab->aCol[j].zColl;
if( !zColl ){
zColl = "BINARY";
}
if( !zColl ) zColl = "BINARY";
}
if( !db->init.busy && !sqlite3LocateCollSeq(pParse, zColl) ){
goto exit_create_index;

View File

@ -116,12 +116,7 @@ CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr){
}
assert( op!=TK_REGISTER || p->op2!=TK_COLLATE );
if( op==TK_COLLATE ){
if( db->init.busy ){
/* Do not report errors when parsing while the schema */
pColl = sqlite3FindCollSeq(db, ENC(db), p->u.zToken, 0);
}else{
pColl = sqlite3GetCollSeq(pParse, ENC(db), 0, p->u.zToken);
}
pColl = sqlite3GetCollSeq(pParse, ENC(db), 0, p->u.zToken);
break;
}
if( p->pTab!=0

View File

@ -848,6 +848,12 @@ static int sqlite3Close(sqlite3 *db, int forceZombie){
return SQLITE_BUSY;
}
/* If a transaction is open, roll it back. This also ensures that if
** any database schemas have been modified by the current transaction
** they are reset. And that the required b-tree mutex is held to make
** the the pager rollback and schema reset an atomic operation. */
sqlite3RollbackAll(db, SQLITE_OK);
#ifdef SQLITE_ENABLE_SQLLOG
if( sqlite3GlobalConfig.xSqllog ){
/* Closing the handle. Fourth parameter is passed the value 2. */
@ -1002,6 +1008,7 @@ void sqlite3RollbackAll(sqlite3 *db, int tripCode){
int inTrans = 0;
assert( sqlite3_mutex_held(db->mutex) );
sqlite3BeginBenignMalloc();
sqlite3BtreeEnterAll(db);
for(i=0; i<db->nDb; i++){
Btree *p = db->aDb[i].pBt;
if( p ){
@ -1019,6 +1026,7 @@ void sqlite3RollbackAll(sqlite3 *db, int tripCode){
sqlite3ExpirePreparedStatements(db);
sqlite3ResetAllSchemasOfConnection(db);
}
sqlite3BtreeLeaveAll(db);
/* Any deferred constraint violations have now been resolved. */
db->nDeferredCons = 0;

View File

@ -55,6 +55,104 @@ execsql {
DROP TABLE collate3t1;
}
proc caseless {a b} { string compare -nocase $a $b }
do_test collate3-1.4 {
db collate caseless caseless
execsql {
CREATE TABLE t1(a COLLATE caseless);
INSERT INTO t1 VALUES('Abc2');
INSERT INTO t1 VALUES('abc1');
INSERT INTO t1 VALUES('aBc3');
}
execsql { SELECT * FROM t1 ORDER BY a }
} {abc1 Abc2 aBc3}
do_test collate3-1.5 {
db close
sqlite3 db test.db
catchsql { SELECT * FROM t1 ORDER BY a }
} {1 {no such collation sequence: caseless}}
do_test collate3-1.6.1 {
db collate caseless caseless
execsql { CREATE INDEX i1 ON t1(a) }
execsql { SELECT * FROM t1 ORDER BY a }
} {abc1 Abc2 aBc3}
do_test collate3-1.6.2 {
db close
sqlite3 db test.db
catchsql { SELECT * FROM t1 ORDER BY a }
} {1 {no such collation sequence: caseless}}
do_test collate3-1.6.3 {
db close
sqlite3 db test.db
catchsql { PRAGMA integrity_check }
} {1 {no such collation sequence: caseless}}
do_test collate3-1.6.4 {
db close
sqlite3 db test.db
catchsql { REINDEX }
} {1 {no such collation sequence: caseless}}
do_test collate3-1.7.1 {
db collate caseless caseless
execsql {
DROP TABLE t1;
CREATE TABLE t1(a);
CREATE INDEX i1 ON t1(a COLLATE caseless);
INSERT INTO t1 VALUES('Abc2');
INSERT INTO t1 VALUES('abc1');
INSERT INTO t1 VALUES('aBc3');
SELECT * FROM t1 ORDER BY a COLLATE caseless;
}
} {abc1 Abc2 aBc3}
do_test collate3-1.7.2 {
db close
sqlite3 db test.db
catchsql { SELECT * FROM t1 ORDER BY a COLLATE caseless}
} {1 {no such collation sequence: caseless}}
do_test collate3-1.7.4 {
db close
sqlite3 db test.db
catchsql { REINDEX }
} {1 {no such collation sequence: caseless}}
do_test collate3-1.7.3 {
db close
sqlite3 db test.db
catchsql { PRAGMA integrity_check }
} {1 {no such collation sequence: caseless}}
do_test collate3-1.7.4 {
db close
sqlite3 db test.db
catchsql { REINDEX }
} {1 {no such collation sequence: caseless}}
do_test collate3-1.7.5 {
db close
sqlite3 db test.db
db collate caseless caseless
catchsql { PRAGMA integrity_check }
} {0 ok}
proc needed {nm} { db collate caseless caseless }
do_test collate3-1.7.6 {
db close
sqlite3 db test.db
db collation_needed needed
catchsql { PRAGMA integrity_check }
} {0 ok}
do_test collate3-1.8 {
execsql { DROP TABLE t1 }
} {}
#
# Create a table with a default collation sequence, then close
# and re-open the database without re-registering the collation

188
test/sharedA.test Normal file
View File

@ -0,0 +1,188 @@
# 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
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