/ Check-in [a2228351]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Check for schema updates if the parser fails to find a table. More locking test updates. (CVS 1555)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: a22283512afe2df09d5783d189fbd7389ed313ad
User & Date: drh 2004-06-10 00:29:09
Context
2004-06-10
00:51
Fix a buffer overrun in the atomic multifile commit logic of the pager. (CVS 1556) check-in: 3b78ffe0 user: drh tags: trunk
00:29
Check for schema updates if the parser fails to find a table. More locking test updates. (CVS 1555) check-in: a2228351 user: drh tags: trunk
2004-06-09
23:15
Website changes for version 2.8.14. (CVS 1553) check-in: 73afa14a user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/build.c.

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
...
160
161
162
163
164
165
166

167
168
169
170
171
172
173
....
1381
1382
1383
1384
1385
1386
1387

1388
1389
1390
1391
1392
1393
1394
....
1981
1982
1983
1984
1985
1986
1987

1988
1989
1990
1991
1992
1993
1994
**     DROP INDEX
**     creating ID lists
**     BEGIN TRANSACTION
**     COMMIT
**     ROLLBACK
**     PRAGMA
**
** $Id: build.c,v 1.212 2004/06/09 12:30:05 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>

/*
** This routine is called when a new SQL statement is beginning to
** be parsed.  Check to see if the schema for the database needs
................................................................................
      sqlite3ErrorMsg(pParse, "no such table: %s.%s", zDbase, zName);
    }else if( sqlite3FindTable(pParse->db, zName, 0)!=0 ){
      sqlite3ErrorMsg(pParse, "table \"%s\" is not in database \"%s\"",
         zName, zDbase);
    }else{
      sqlite3ErrorMsg(pParse, "no such table: %s", zName);
    }

  }
  return p;
}

/*
** Locate the in-memory structure that describes 
** a particular index given the name of that index
................................................................................
  Table *pTab;
  zName = sqlite3TableNameFromToken(pTok);
  if( zName==0 ) return 0;
  pTab = sqlite3FindTable(pParse->db, zName, 0);
  sqliteFree(zName);
  if( pTab==0 ){
    sqlite3ErrorMsg(pParse, "no such table: %T", pTok);

  }
  return pTab;
}

/*
** This routine is called to do the work of a DROP TABLE statement.
** pName is the name of the table to be dropped.
................................................................................
  sqlite *db = pParse->db;

  if( pParse->nErr || sqlite3_malloc_failed ) return;
  assert( pName->nSrc==1 );
  pIndex = sqlite3FindIndex(db, pName->a[0].zName, pName->a[0].zDatabase);
  if( pIndex==0 ){
    sqlite3ErrorMsg(pParse, "no such index: %S", pName, 0);

    goto exit_drop_index;
  }
  if( pIndex->autoIndex ){
    sqlite3ErrorMsg(pParse, "index associated with UNIQUE "
      "or PRIMARY KEY constraint cannot be dropped", 0);
    goto exit_drop_index;
  }







|







 







>







 







>







 







>







19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
...
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
....
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
....
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
**     DROP INDEX
**     creating ID lists
**     BEGIN TRANSACTION
**     COMMIT
**     ROLLBACK
**     PRAGMA
**
** $Id: build.c,v 1.213 2004/06/10 00:29:09 drh Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>

/*
** This routine is called when a new SQL statement is beginning to
** be parsed.  Check to see if the schema for the database needs
................................................................................
      sqlite3ErrorMsg(pParse, "no such table: %s.%s", zDbase, zName);
    }else if( sqlite3FindTable(pParse->db, zName, 0)!=0 ){
      sqlite3ErrorMsg(pParse, "table \"%s\" is not in database \"%s\"",
         zName, zDbase);
    }else{
      sqlite3ErrorMsg(pParse, "no such table: %s", zName);
    }
    pParse->checkSchema = 1;
  }
  return p;
}

/*
** Locate the in-memory structure that describes 
** a particular index given the name of that index
................................................................................
  Table *pTab;
  zName = sqlite3TableNameFromToken(pTok);
  if( zName==0 ) return 0;
  pTab = sqlite3FindTable(pParse->db, zName, 0);
  sqliteFree(zName);
  if( pTab==0 ){
    sqlite3ErrorMsg(pParse, "no such table: %T", pTok);
    pParse->checkSchema = 1;
  }
  return pTab;
}

/*
** This routine is called to do the work of a DROP TABLE statement.
** pName is the name of the table to be dropped.
................................................................................
  sqlite *db = pParse->db;

  if( pParse->nErr || sqlite3_malloc_failed ) return;
  assert( pName->nSrc==1 );
  pIndex = sqlite3FindIndex(db, pName->a[0].zName, pName->a[0].zDatabase);
  if( pIndex==0 ){
    sqlite3ErrorMsg(pParse, "no such index: %S", pName, 0);
    pParse->checkSchema = 1;
    goto exit_drop_index;
  }
  if( pIndex->autoIndex ){
    sqlite3ErrorMsg(pParse, "index associated with UNIQUE "
      "or PRIMARY KEY constraint cannot be dropped", 0);
    goto exit_drop_index;
  }

Changes to src/main.c.

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
850
851
852
853
854
855
856




























857
858
859
860
861
862
863
...
907
908
909
910
911
912
913



914
915
916
917
918
919
920
**
*************************************************************************
** Main file for the SQLite library.  The routines in this file
** implement the programmer interface to the library.  Routines in
** other files are for internal use by SQLite and should not be
** accessed by users of the library.
**
** $Id: main.c,v 1.210 2004/06/09 20:03:09 drh Exp $
*/
#include "sqliteInt.h"
#include "os.h"
#include <ctype.h>

/*
** A pointer to this structure is used to communicate information
................................................................................
  }
  return db->zErrMsg16;
}

int sqlite3_errcode(sqlite3 *db){
  return db->errCode;
}





























/*
** Compile the UTF-8 encoded SQL statement zSql into a statement handle.
*/
int sqlite3_prepare(
  sqlite3 *db,              /* Database handle. */
  const char *zSql,         /* UTF-8 encoded SQL statement. */
................................................................................
    rc = SQLITE_NOMEM;
    sqlite3RollbackAll(db);
    sqlite3ResetInternalSchema(db, 0);
    db->flags &= ~SQLITE_InTrans;
    goto prepare_out;
  }
  if( sParse.rc==SQLITE_DONE ) sParse.rc = SQLITE_OK;



  if( sParse.rc==SQLITE_SCHEMA ){
    sqlite3ResetInternalSchema(db, 0);
  }
  assert( ppStmt );
  *ppStmt = (sqlite3_stmt*)sParse.pVdbe;
  if( pzTail ) *pzTail = sParse.zTail;
  rc = sParse.rc;







|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>







10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
...
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
**
*************************************************************************
** Main file for the SQLite library.  The routines in this file
** implement the programmer interface to the library.  Routines in
** other files are for internal use by SQLite and should not be
** accessed by users of the library.
**
** $Id: main.c,v 1.211 2004/06/10 00:29:09 drh Exp $
*/
#include "sqliteInt.h"
#include "os.h"
#include <ctype.h>

/*
** A pointer to this structure is used to communicate information
................................................................................
  }
  return db->zErrMsg16;
}

int sqlite3_errcode(sqlite3 *db){
  return db->errCode;
}

/*
** Check schema cookies in all databases except TEMP.  If any cookie is out
** of date, return 0.  If all schema cookies are current, return 1.
*/
static int schemaIsValid(sqlite *db){
  int iDb;
  int rc;
  BtCursor *curTemp;
  int cookie;
  int allOk = 1;

  for(iDb=0; allOk && iDb<db->nDb; iDb++){
    Btree *pBt;
    if( iDb==1 ) continue;
    pBt = db->aDb[iDb].pBt;
    if( pBt==0 ) continue;
    rc = sqlite3BtreeCursor(pBt, MASTER_ROOT, 0, 0, 0, &curTemp);
    if( rc==SQLITE_OK ){
      rc = sqlite3BtreeGetMeta(pBt, 1, &cookie);
      if( rc==SQLITE_OK && cookie!=db->aDb[iDb].schema_cookie ){
        allOk = 0;
      }
      sqlite3BtreeCloseCursor(curTemp);
    }
  }
  return allOk;
}

/*
** Compile the UTF-8 encoded SQL statement zSql into a statement handle.
*/
int sqlite3_prepare(
  sqlite3 *db,              /* Database handle. */
  const char *zSql,         /* UTF-8 encoded SQL statement. */
................................................................................
    rc = SQLITE_NOMEM;
    sqlite3RollbackAll(db);
    sqlite3ResetInternalSchema(db, 0);
    db->flags &= ~SQLITE_InTrans;
    goto prepare_out;
  }
  if( sParse.rc==SQLITE_DONE ) sParse.rc = SQLITE_OK;
  if( sParse.checkSchema && !schemaIsValid(db) ){
    sParse.rc = SQLITE_SCHEMA;
  }
  if( sParse.rc==SQLITE_SCHEMA ){
    sqlite3ResetInternalSchema(db, 0);
  }
  assert( ppStmt );
  *ppStmt = (sqlite3_stmt*)sParse.pVdbe;
  if( pzTail ) *pzTail = sParse.zTail;
  rc = sParse.rc;

Changes to src/sqliteInt.h.

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
....
1006
1007
1008
1009
1010
1011
1012

1013
1014
1015
1016
1017
1018
1019
**    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.
**
*************************************************************************
** Internal interface definitions for SQLite.
**
** @(#) $Id: sqliteInt.h,v 1.276 2004/06/09 20:03:09 drh Exp $
*/
#include "config.h"
#include "sqlite3.h"
#include "hash.h"
#include "parse.h"
#include <stdio.h>
#include <stdlib.h>
................................................................................
  Table *pNewTable;    /* A table being constructed by CREATE TABLE */
  Vdbe *pVdbe;         /* An engine for executing database bytecode */
  u8 colNamesSet;      /* TRUE after OP_ColumnName has been issued to pVdbe */
  u8 explain;          /* True if the EXPLAIN flag is found on the query */
  u8 nameClash;        /* A permanent table name clashes with temp table name */
  u8 useAgg;           /* If true, extract field values from the aggregator
                       ** while generating expressions.  Normally false */

  int nErr;            /* Number of errors seen */
  int nTab;            /* Number of previously allocated VDBE cursors */
  int nMem;            /* Number of memory cells used so far */
  int nSet;            /* Number of sets used so far */
  int nAgg;            /* Number of aggregate expressions */
  int nVar;            /* Number of '?' variables seen in the SQL so far */
  AggExpr *aAgg;       /* An array of aggregate expressions */







|







 







>







7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
....
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
**    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.
**
*************************************************************************
** Internal interface definitions for SQLite.
**
** @(#) $Id: sqliteInt.h,v 1.277 2004/06/10 00:29:10 drh Exp $
*/
#include "config.h"
#include "sqlite3.h"
#include "hash.h"
#include "parse.h"
#include <stdio.h>
#include <stdlib.h>
................................................................................
  Table *pNewTable;    /* A table being constructed by CREATE TABLE */
  Vdbe *pVdbe;         /* An engine for executing database bytecode */
  u8 colNamesSet;      /* TRUE after OP_ColumnName has been issued to pVdbe */
  u8 explain;          /* True if the EXPLAIN flag is found on the query */
  u8 nameClash;        /* A permanent table name clashes with temp table name */
  u8 useAgg;           /* If true, extract field values from the aggregator
                       ** while generating expressions.  Normally false */
  u8 checkSchema;      /* Causes schema cookie check after an error */
  int nErr;            /* Number of errors seen */
  int nTab;            /* Number of previously allocated VDBE cursors */
  int nMem;            /* Number of memory cells used so far */
  int nSet;            /* Number of sets used so far */
  int nAgg;            /* Number of aggregate expressions */
  int nVar;            /* Number of '?' variables seen in the SQL so far */
  AggExpr *aAgg;       /* An array of aggregate expressions */

Changes to test/lock.test.

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
..
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
..
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
...
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
...
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
...
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300

301
302
303
304
305
306
307
...
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
...
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
#    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.  The
# focus of this script is database locks.
#
# $Id: lock.test,v 1.22 2004/06/09 23:15:22 drh Exp $


set testdir [file dirname $argv0]
source $testdir/tester.tcl

# Create an alternative connection to the database
#
................................................................................
  execsql {INSERT INTO t1 VALUES(1,2)}
  execsql {SELECT * FROM t1}
} {1 2}
do_test lock-1.7.1 {
  catchsql {SELECT * FROM t1} db2
} {1 {no such table: t1}}
do_test lock-1.7.2 {
  execsql {SELECT * FROM sqlite_master LIMIT 1} db2
  catchsql {SELECT * FROM t1} db2
} {0 {1 2}}
do_test lock-1.8 {
  execsql {UPDATE t1 SET a=b, b=a} db2
  execsql {SELECT * FROM t1} db2
} {2 1}
do_test lock-1.9 {
................................................................................
do_test lock-1.10 {
  execsql {BEGIN TRANSACTION}
  execsql {UPDATE t1 SET a = 0 WHERE 0}
  execsql {SELECT * FROM t1}
} {2 1}
do_test lock-1.11 {
  catchsql {SELECT * FROM t1} db2
} {1 {database is locked}}
do_test lock-1.12 {
  execsql {ROLLBACK}
  catchsql {SELECT * FROM t1}
} {0 {2 1}}

do_test lock-1.13 {
  execsql {CREATE TABLE t2(x int, y int)}
................................................................................
    }
    set r
  } {0 2}
}
integrity_check lock-1.23

# If one thread has a transaction another thread cannot start
# a transaction.

#
do_test lock-2.1 {
  execsql {BEGIN TRANSACTION}
  execsql {UPDATE t1 SET a = 0 WHERE 0}
  execsql {BEGIN TRANSACTION} db2
  set r [catch {execsql {UPDATE t1 SET a = 0 WHERE 0} db2} msg]
  execsql {ROLLBACK} db2
  lappend r $msg
} {1 {database is locked}}

# Nor can the other thread do a query.
#
do_test lock-2.2 {
  set r [catch {execsql {SELECT * FROM t2} db2} msg]
  lappend r $msg
} {1 {database is locked}}

# If the other thread (the one that does not hold the transaction)
# tries to start a transaction, we get a busy callback.
#
do_test lock-2.3 {
  proc callback {args} {
    set ::callback_value $args
    break
  }
  set ::callback_value {}
  db2 busy callback
  set r [catch {execsql {BEGIN TRANSACTION} db2} msg]
  lappend r $msg
  lappend r $::callback_value
} {1 {database is locked} {{} 1}}
do_test lock-2.4 {
  proc callback {file count} {
    lappend ::callback_value $count
    if {$count>4} break
  }
  set ::callback_value {}
  db2 busy callback
  set r [catch {execsql {BEGIN TRANSACTION} db2} msg]
  lappend r $msg
  lappend r $::callback_value
} {1 {database is locked} {1 2 3 4 5}}
do_test lock-2.5 {
  proc callback {file count} {
    lappend ::callback_value $count
    if {$count>4} break
  }
  set ::callback_value {}
  db2 busy callback
  set r [catch {execsql {SELECT * FROM t1} db2} msg]
  lappend r $msg
  lappend r $::callback_value
} {1 {database is locked} {1 2 3 4 5}}

# In this test, the 3rd invocation of the busy callback causes
# the first thread to release its transaction.  That allows the
# second thread to continue.
#
do_test lock-2.6 {
  proc callback {file count} {
................................................................................
    }
  }
  set ::callback_value {}
  db2 busy callback
  set r [catch {execsql {SELECT * FROM t2} db2} msg]
  lappend r $msg
  lappend r $::callback_value
} {0 {9 8} {1 2 3}}
do_test lock-2.7 {
  execsql {BEGIN TRANSACTION}
  execsql {UPDATE t1 SET a = 0 WHERE 0}
  proc callback {file count} {
    lappend ::callback_value $count
    if {$count>2} {
      execsql {ROLLBACK}
    }
  }
  set ::callback_value {}
................................................................................
#
do_test lock-4.1 {
  db2 close
  catch {db eval ROLLBACK}
  db eval BEGIN
  db eval {UPDATE t1 SET a=0 WHERE 0}
  sqlite db2 ./test.db
  set rc [catch {db2 eval {SELECT * FROM t1}} msg]
  lappend rc $msg
} {1 {database is locked}}
do_test lock-4.2 {
  set ::callback_value {}
  set rc [catch {db2 eval {SELECT * FROM t1}} msg]
  lappend rc $msg $::callback_value
} {1 {database is locked} {}}
do_test lock-4.3 {
  proc callback {file count} {
    lappend ::callback_value $count
    if {$count>4} break
  }
  db2 busy callback
  set rc [catch {db2 eval {SELECT * FROM t1}} msg]
  lappend rc $msg $::callback_value
} {1 {database is locked} {1 2 3 4 5}}
execsql {ROLLBACK}

# When one thread is writing, other threads cannot read.  Except if the
# writing thread is writing to its temporary tables, the other threads
# can still read.

#
proc tx_exec {sql} {
  db2 eval $sql
}
do_test lock-5.1 {
  execsql {
    SELECT * FROM t1
................................................................................
  }
} {2 1}
do_test lock-5.2 {
  db function tx_exec tx_exec
  catchsql {
    INSERT INTO t1(a,b) SELECT 3, tx_exec('SELECT y FROM t2 LIMIT 1');
  }
} {1 {database is locked}}
do_test lock-5.3 {
  execsql {
    CREATE TEMP TABLE t3(x);
    SELECT * FROM t3;
  }
} {}
do_test lock-5.4 {
................................................................................
    SELECT * FROM t3;
  }
} {8}
do_test lock-5.6 {
  catchsql {
    UPDATE t1 SET a=tx_exec('SELECT x FROM t2');
  }
} {1 {database is locked}}
do_test lock-5.7 {
  execsql {
    SELECT * FROM t1;
  }
} {2 1}
do_test lock-5.8 {
  catchsql {
    UPDATE t3 SET x=tx_exec('SELECT x FROM t2');
  }
} {0 {}}
do_test lock-5.9 {
  execsql {







|







 







<







 







|







 







|
>










|


|
|
<

|
|








|










|













|







 







|

<
<







 







|
<



|








|






|
>







 







|







 







|




|







7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
..
43
44
45
46
47
48
49

50
51
52
53
54
55
56
..
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
...
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
...
217
218
219
220
221
222
223
224
225


226
227
228
229
230
231
232
...
269
270
271
272
273
274
275
276

277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
...
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
...
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
#    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.  The
# focus of this script is database locks.
#
# $Id: lock.test,v 1.23 2004/06/10 00:29:12 drh Exp $


set testdir [file dirname $argv0]
source $testdir/tester.tcl

# Create an alternative connection to the database
#
................................................................................
  execsql {INSERT INTO t1 VALUES(1,2)}
  execsql {SELECT * FROM t1}
} {1 2}
do_test lock-1.7.1 {
  catchsql {SELECT * FROM t1} db2
} {1 {no such table: t1}}
do_test lock-1.7.2 {

  catchsql {SELECT * FROM t1} db2
} {0 {1 2}}
do_test lock-1.8 {
  execsql {UPDATE t1 SET a=b, b=a} db2
  execsql {SELECT * FROM t1} db2
} {2 1}
do_test lock-1.9 {
................................................................................
do_test lock-1.10 {
  execsql {BEGIN TRANSACTION}
  execsql {UPDATE t1 SET a = 0 WHERE 0}
  execsql {SELECT * FROM t1}
} {2 1}
do_test lock-1.11 {
  catchsql {SELECT * FROM t1} db2
} {0 {2 1}}
do_test lock-1.12 {
  execsql {ROLLBACK}
  catchsql {SELECT * FROM t1}
} {0 {2 1}}

do_test lock-1.13 {
  execsql {CREATE TABLE t2(x int, y int)}
................................................................................
    }
    set r
  } {0 2}
}
integrity_check lock-1.23

# If one thread has a transaction another thread cannot start
# a transaction.  -> Not true in version 3.0.  But if one thread
# as a RESERVED lock another thread cannot acquire one.
#
do_test lock-2.1 {
  execsql {BEGIN TRANSACTION}
  execsql {UPDATE t1 SET a = 0 WHERE 0}
  execsql {BEGIN TRANSACTION} db2
  set r [catch {execsql {UPDATE t1 SET a = 0 WHERE 0} db2} msg]
  execsql {ROLLBACK} db2
  lappend r $msg
} {1 {database is locked}}

# A thread can read when another has a RESERVED lock.
#
do_test lock-2.2 {
  catchsql {SELECT * FROM t2} db2
} {0 {9 8}}


# If the other thread (the one that does not hold the transaction with
# a RESERVED lock) tries to get a RESERVED lock, we get a busy callback.
#
do_test lock-2.3 {
  proc callback {args} {
    set ::callback_value $args
    break
  }
  set ::callback_value {}
  db2 busy callback
  set r [catch {execsql {UPDATE t1 SET a=b, b=a} db2} msg]
  lappend r $msg
  lappend r $::callback_value
} {1 {database is locked} {{} 1}}
do_test lock-2.4 {
  proc callback {file count} {
    lappend ::callback_value $count
    if {$count>4} break
  }
  set ::callback_value {}
  db2 busy callback
  set r [catch {execsql {UPDATE t1 SET a=b, b=a} db2} msg]
  lappend r $msg
  lappend r $::callback_value
} {1 {database is locked} {1 2 3 4 5}}
do_test lock-2.5 {
  proc callback {file count} {
    lappend ::callback_value $count
    if {$count>4} break
  }
  set ::callback_value {}
  db2 busy callback
  set r [catch {execsql {SELECT * FROM t1} db2} msg]
  lappend r $msg
  lappend r $::callback_value
} {0 {2 1} {}}

# In this test, the 3rd invocation of the busy callback causes
# the first thread to release its transaction.  That allows the
# second thread to continue.
#
do_test lock-2.6 {
  proc callback {file count} {
................................................................................
    }
  }
  set ::callback_value {}
  db2 busy callback
  set r [catch {execsql {SELECT * FROM t2} db2} msg]
  lappend r $msg
  lappend r $::callback_value
} {0 {9 8} {}}
do_test lock-2.7 {


  proc callback {file count} {
    lappend ::callback_value $count
    if {$count>2} {
      execsql {ROLLBACK}
    }
  }
  set ::callback_value {}
................................................................................
#
do_test lock-4.1 {
  db2 close
  catch {db eval ROLLBACK}
  db eval BEGIN
  db eval {UPDATE t1 SET a=0 WHERE 0}
  sqlite db2 ./test.db
  catchsql {UPDATE t1 SET a=0} db2

} {1 {database is locked}}
do_test lock-4.2 {
  set ::callback_value {}
  set rc [catch {db2 eval {UPDATE t1 SET a=0}} msg]
  lappend rc $msg $::callback_value
} {1 {database is locked} {}}
do_test lock-4.3 {
  proc callback {file count} {
    lappend ::callback_value $count
    if {$count>4} break
  }
  db2 busy callback
  set rc [catch {db2 eval {UPDATE t1 SET a=0}} msg]
  lappend rc $msg $::callback_value
} {1 {database is locked} {1 2 3 4 5}}
execsql {ROLLBACK}

# When one thread is writing, other threads cannot read.  Except if the
# writing thread is writing to its temporary tables, the other threads
# can still read.  -> Not so in 3.0.  One thread can read while another
# holds a RESERVED lock.
#
proc tx_exec {sql} {
  db2 eval $sql
}
do_test lock-5.1 {
  execsql {
    SELECT * FROM t1
................................................................................
  }
} {2 1}
do_test lock-5.2 {
  db function tx_exec tx_exec
  catchsql {
    INSERT INTO t1(a,b) SELECT 3, tx_exec('SELECT y FROM t2 LIMIT 1');
  }
} {0 {}}
do_test lock-5.3 {
  execsql {
    CREATE TEMP TABLE t3(x);
    SELECT * FROM t3;
  }
} {}
do_test lock-5.4 {
................................................................................
    SELECT * FROM t3;
  }
} {8}
do_test lock-5.6 {
  catchsql {
    UPDATE t1 SET a=tx_exec('SELECT x FROM t2');
  }
} {0 {}}
do_test lock-5.7 {
  execsql {
    SELECT * FROM t1;
  }
} {9 1 9 8}
do_test lock-5.8 {
  catchsql {
    UPDATE t3 SET x=tx_exec('SELECT x FROM t2');
  }
} {0 {}}
do_test lock-5.9 {
  execsql {