/ Check-in [bf34284f]
Login

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

Overview
Comment:Make sure statement journals are initiated when doing DROP operations (since the DROP might fail after sqlite_master changes). Also make sure statement journals are initiated if there are pending SELECT statements. Ticket #2820. (CVS 4591)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: bf34284ff0c60ae6e735e09bb29cd48b158e8dce
User & Date: drh 2007-12-04 16:54:53
Context
2007-12-05
01:38
Add the ability to change the autovacuum status of an existing database by setting the auto_vacuum pragma then running the VACUUM command. (CVS 4592) check-in: bdfc19e8 user: drh tags: trunk
2007-12-04
16:54
Make sure statement journals are initiated when doing DROP operations (since the DROP might fail after sqlite_master changes). Also make sure statement journals are initiated if there are pending SELECT statements. Ticket #2820. (CVS 4591) check-in: bf34284f user: drh tags: trunk
13:41
Fix a bug in the TCL code for sqlite3_analyzer. (CVS 4590) check-in: 7c2cf454 user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/build.c.

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
....
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
....
2804
2805
2806
2807
2808
2809
2810

2811
2812
2813
2814
2815
2816
2817
**     CREATE INDEX
**     DROP INDEX
**     creating ID lists
**     BEGIN TRANSACTION
**     COMMIT
**     ROLLBACK
**
** $Id: build.c,v 1.449 2007/12/02 11:46:35 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>

/*
** This routine is called when a new SQL statement is beginning to
** be parsed.  Initialize the pParse structure as needed.
................................................................................
  /* Generate code to remove the table from the master table
  ** on disk.
  */
  v = sqlite3GetVdbe(pParse);
  if( v ){
    Trigger *pTrigger;
    Db *pDb = &db->aDb[iDb];
    sqlite3BeginWriteOperation(pParse, 0, iDb);

#ifndef SQLITE_OMIT_VIRTUALTABLE
    if( IsVirtual(pTab) ){
      Vdbe *v = sqlite3GetVdbe(pParse);
      if( v ){
        sqlite3VdbeAddOp(v, OP_VBegin, 0, 0);
      }
................................................................................
    }
  }
#endif

  /* Generate code to remove the index and from the master table */
  v = sqlite3GetVdbe(pParse);
  if( v ){

    sqlite3NestedParse(pParse,
       "DELETE FROM %Q.%s WHERE name=%Q",
       db->aDb[iDb].zName, SCHEMA_TABLE(iDb),
       pIndex->zName
    );
    sqlite3ChangeCookie(db, v, iDb);
    destroyRootPage(pParse, pIndex->tnum, iDb);







|







 







|







 







>







18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
....
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
....
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
**     CREATE INDEX
**     DROP INDEX
**     creating ID lists
**     BEGIN TRANSACTION
**     COMMIT
**     ROLLBACK
**
** $Id: build.c,v 1.450 2007/12/04 16:54:53 drh Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>

/*
** This routine is called when a new SQL statement is beginning to
** be parsed.  Initialize the pParse structure as needed.
................................................................................
  /* Generate code to remove the table from the master table
  ** on disk.
  */
  v = sqlite3GetVdbe(pParse);
  if( v ){
    Trigger *pTrigger;
    Db *pDb = &db->aDb[iDb];
    sqlite3BeginWriteOperation(pParse, 1, iDb);

#ifndef SQLITE_OMIT_VIRTUALTABLE
    if( IsVirtual(pTab) ){
      Vdbe *v = sqlite3GetVdbe(pParse);
      if( v ){
        sqlite3VdbeAddOp(v, OP_VBegin, 0, 0);
      }
................................................................................
    }
  }
#endif

  /* Generate code to remove the index and from the master table */
  v = sqlite3GetVdbe(pParse);
  if( v ){
    sqlite3BeginWriteOperation(pParse, 1, iDb);
    sqlite3NestedParse(pParse,
       "DELETE FROM %Q.%s WHERE name=%Q",
       db->aDb[iDb].zName, SCHEMA_TABLE(iDb),
       pIndex->zName
    );
    sqlite3ChangeCookie(db, v, iDb);
    destroyRootPage(pParse, pIndex->tnum, iDb);

Changes to src/vdbe.c.

39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
....
2406
2407
2408
2409
2410
2411
2412
2413

2414
2415
2416
2417
2418
2419
2420
....
4084
4085
4086
4087
4088
4089
4090

4091
4092
4093
4094
4095
4096
4097
**
** Various scripts scan this source file in order to generate HTML
** documentation, headers files, or other derived files.  The formatting
** of the code in this file is, therefore, important.  See other comments
** in this file for details.  If in doubt, do not deviate from existing
** commenting and indentation practices when changing or adding code.
**
** $Id: vdbe.c,v 1.655 2007/11/29 17:05:18 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>
#include "vdbeInt.h"

/*
** The following global variable is incremented every time a cursor
................................................................................
** The statement is begun on the database file with index P1.  The main
** database file has an index of 0 and the file used for temporary tables
** has an index of 1.
*/
case OP_Statement: {       /* no-push */
  int i = pOp->p1;
  Btree *pBt;
  if( i>=0 && i<db->nDb && (pBt = db->aDb[i].pBt)!=0 && !(db->autoCommit) ){

    assert( sqlite3BtreeIsInTrans(pBt) );
    assert( (p->btreeMask & (1<<i))!=0 );
    if( !sqlite3BtreeIsInStmt(pBt) ){
      rc = sqlite3BtreeBeginStmt(pBt);
      p->openedStatement = 1;
    }
  }
................................................................................
    }
  }
#else
  iCnt = db->activeVdbeCnt;
#endif
  if( iCnt>1 ){
    rc = SQLITE_LOCKED;

  }else{
    assert( iCnt==1 );
    assert( (p->btreeMask & (1<<pOp->p2))!=0 );
    rc = sqlite3BtreeDropTable(db->aDb[pOp->p2].pBt, pOp->p1, &iMoved);
    pTos++;
    pTos->flags = MEM_Int;
    pTos->u.i = iMoved;







|







 







|
>







 







>







39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
....
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
....
4085
4086
4087
4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
**
** Various scripts scan this source file in order to generate HTML
** documentation, headers files, or other derived files.  The formatting
** of the code in this file is, therefore, important.  See other comments
** in this file for details.  If in doubt, do not deviate from existing
** commenting and indentation practices when changing or adding code.
**
** $Id: vdbe.c,v 1.656 2007/12/04 16:54:53 drh Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>
#include "vdbeInt.h"

/*
** The following global variable is incremented every time a cursor
................................................................................
** The statement is begun on the database file with index P1.  The main
** database file has an index of 0 and the file used for temporary tables
** has an index of 1.
*/
case OP_Statement: {       /* no-push */
  int i = pOp->p1;
  Btree *pBt;
  if( i>=0 && i<db->nDb && (pBt = db->aDb[i].pBt)!=0
        && (db->autoCommit==0 || db->activeVdbeCnt>1) ){
    assert( sqlite3BtreeIsInTrans(pBt) );
    assert( (p->btreeMask & (1<<i))!=0 );
    if( !sqlite3BtreeIsInStmt(pBt) ){
      rc = sqlite3BtreeBeginStmt(pBt);
      p->openedStatement = 1;
    }
  }
................................................................................
    }
  }
#else
  iCnt = db->activeVdbeCnt;
#endif
  if( iCnt>1 ){
    rc = SQLITE_LOCKED;
    p->errorAction = OE_Abort;
  }else{
    assert( iCnt==1 );
    assert( (p->btreeMask & (1<<pOp->p2))!=0 );
    rc = sqlite3BtreeDropTable(db->aDb[pOp->p2].pBt, pOp->p1, &iMoved);
    pTos++;
    pTos->flags = MEM_Int;
    pTos->u.i = iMoved;

Changes to src/vdbeaux.c.

266
267
268
269
270
271
272
273







274
275
276
277
278
279
280
281
282
283
...
295
296
297
298
299
300
301


302
303
304
305
306
307
308
....
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
** to an OP_Function, OP_AggStep or OP_VFilter opcode. This is used by 
** sqlite3VdbeMakeReady() to size the Vdbe.apArg[] array.
**
** The integer *pMaxStack is set to the maximum number of vdbe stack
** entries that static analysis reveals this program might need.
**
** This routine also does the following optimization:  It scans for
** Halt instructions where P1==SQLITE_CONSTRAINT or P2==OE_Abort or for







** IdxInsert instructions where P2!=0.  If no such instruction is
** found, then every Statement instruction is changed to a Noop.  In
** this way, we avoid creating the statement journal file unnecessarily.
*/
static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs, int *pMaxStack){
  int i;
  int nMaxArgs = 0;
  int nMaxStack = p->nOp;
  Op *pOp;
  int *aLabel = p->aLabel;
................................................................................
    }
    if( opcode==OP_Halt ){
      if( pOp->p1==SQLITE_CONSTRAINT && pOp->p2==OE_Abort ){
        doesStatementRollback = 1;
      }
    }else if( opcode==OP_Statement ){
      hasStatementBegin = 1;


#ifndef SQLITE_OMIT_VIRTUALTABLE
    }else if( opcode==OP_VUpdate || opcode==OP_VRename ){
      doesStatementRollback = 1;
    }else if( opcode==OP_VFilter ){
      int n;
      assert( p->nOp - i >= 3 );
      assert( pOp[-2].opcode==OP_Integer );
................................................................................

    /* Lock all btrees used by the statement */
    sqlite3BtreeMutexArrayEnter(&p->aMutex);

    /* Check for one of the special errors */
    mrc = p->rc & 0xff;
    isSpecialError = mrc==SQLITE_NOMEM || mrc==SQLITE_IOERR
                     || mrc==SQLITE_INTERRUPT || mrc==SQLITE_FULL ;
    if( isSpecialError ){
      /* This loop does static analysis of the query to see which of the
      ** following three categories it falls into:
      **
      **     Read-only
      **     Query with statement journal
      **     Query without statement journal







|
>
>
>
>
>
>
>
|
|
|







 







>
>







 







|







266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
...
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
....
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
** to an OP_Function, OP_AggStep or OP_VFilter opcode. This is used by 
** sqlite3VdbeMakeReady() to size the Vdbe.apArg[] array.
**
** The integer *pMaxStack is set to the maximum number of vdbe stack
** entries that static analysis reveals this program might need.
**
** This routine also does the following optimization:  It scans for
** instructions that might cause a statement rollback.  Such instructions
** are:
**
**   *  OP_Halt with P1=SQLITE_CONSTRAINT and P2=OE_Abort.
**   *  OP_Destroy
**   *  OP_VUpdate
**   *  OP_VRename
**
** If no such instruction is found, then every Statement instruction 
** is changed to a Noop.  In this way, we avoid creating the statement 
** journal file unnecessarily.
*/
static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs, int *pMaxStack){
  int i;
  int nMaxArgs = 0;
  int nMaxStack = p->nOp;
  Op *pOp;
  int *aLabel = p->aLabel;
................................................................................
    }
    if( opcode==OP_Halt ){
      if( pOp->p1==SQLITE_CONSTRAINT && pOp->p2==OE_Abort ){
        doesStatementRollback = 1;
      }
    }else if( opcode==OP_Statement ){
      hasStatementBegin = 1;
    }else if( opcode==OP_Destroy ){
      doesStatementRollback = 1;
#ifndef SQLITE_OMIT_VIRTUALTABLE
    }else if( opcode==OP_VUpdate || opcode==OP_VRename ){
      doesStatementRollback = 1;
    }else if( opcode==OP_VFilter ){
      int n;
      assert( p->nOp - i >= 3 );
      assert( pOp[-2].opcode==OP_Integer );
................................................................................

    /* Lock all btrees used by the statement */
    sqlite3BtreeMutexArrayEnter(&p->aMutex);

    /* Check for one of the special errors */
    mrc = p->rc & 0xff;
    isSpecialError = mrc==SQLITE_NOMEM || mrc==SQLITE_IOERR
                     || mrc==SQLITE_INTERRUPT || mrc==SQLITE_FULL;
    if( isSpecialError ){
      /* This loop does static analysis of the query to see which of the
      ** following three categories it falls into:
      **
      **     Read-only
      **     Query with statement journal
      **     Query without statement journal

Added test/tkt2820.test.





























































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
# 2007 Dec 4
#
# 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 file is to test that ticket #2820 has been fixed.
# Ticket #2820 observes that a DROP TABLE statement that
# occurs while a query is in process will fail with a
# "database is locked" error, but the entry in the sqlite_master
# table will still be removed.  This is incorrect.  The
# entry in the sqlite_master table should persist when 
# the DROP fails due to an error.
#
# $Id: tkt2820.test,v 1.1 2007/12/04 16:54:53 drh Exp $
#

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

proc test_schema_change {testid init ddl res} {
  db close
  file delete -force test.db test.db-journal
  sqlite3 db test.db
  execsql $init
  do_test tkt2820-$testid.1 {
    set STMT [sqlite3_prepare db {SELECT * FROM sqlite_master} -1 DUMMY]
    sqlite3_step $STMT
  } {SQLITE_ROW}
#if {$testid==3} {execsql {PRAGMA vdbe_trace=ON}}
  do_test tkt2820-$testid.2 "catchsql [list $ddl]" \
       {1 {database table is locked}}
  do_test tkt2820-$testid.3 {
    sqlite3_finalize $STMT
    execsql {SELECT name FROM sqlite_master ORDER BY 1}
  } $res
  integrity_check tkt2820-$testid.4
  db close
  sqlite3 db test.db
  integrity_check tkt2820-$testid.5
}

test_schema_change 1 {
  CREATE TABLE t1(a);
} {
  DROP TABLE t1
} {t1}
test_schema_change 2 {
  CREATE TABLE t1(a);
  CREATE TABLE t2(b);
} {
  DROP TABLE t2
} {t1 t2}
test_schema_change 3 {
  CREATE TABLE t1(a);
  CREATE INDEX i1 ON t1(a);
} {
  DROP INDEX i1
} {i1 t1}

# We further observe that prior to the fix associated with ticket #2820,
# no statement journal would be created on an SQL statement that was run
# while a second statement was active, as long as we are in autocommit
# mode.  This is incorrect.
#
do_test tkt2820-4.1 {
  db close
  file delete -force test.db test.db-journal
  sqlite3 db test.db
  db eval {
    CREATE TABLE t1(a INTEGER PRIMARY KEY);
    INSERT INTO t1 VALUES(1);
    INSERT INTO t1 VALUES(2);
  }

  # The INSERT statement within the loop should fail on a
  # constraint violation on the second inserted row.  This
  # should cause the entire INSERT to rollback using a statement
  # journal.
  #
  db eval {SELECT name FROM sqlite_master} {
    catch {db eval {
      INSERT INTO t1 SELECT a+1 FROM t1 ORDER BY a DESC
    }}
  }
  db eval {SELECT a FROM t1 ORDER BY a}
} {1 2}

finish_test

Changes to test/vtab7.test.

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
...
110
111
112
113
114
115
116



117
118
119
120
121
122
123
124
125




126
127
128
129
130
131
132
...
194
195
196
197
198
199
200
201
#    May you share freely, never taking more than you give.
#
#***********************************************************************
# This file implements regression tests for SQLite library. The focus
# of this test is reading and writing to the database from within a
# virtual table xSync() callback.
#
# $Id: vtab7.test,v 1.3 2007/10/09 08:29:33 danielk1977 Exp $

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

ifcapable !vtab {
  finish_test
  return
................................................................................
  execsql {
    INSERT INTO abc2 VALUES(1, 2, 3);
    SELECT name FROM sqlite_master ORDER BY name;
  }
} {abc abc2 log newtab}

# Drop a database table from within xSync callback.



do_test vtab7-2.6 {
  set ::callbacks(xSync,abc) {
    execsql { DROP TABLE newtab }
  }
  execsql {
    INSERT INTO abc2 VALUES(1, 2, 3);
    SELECT name FROM sqlite_master ORDER BY name;
  }
} {abc abc2 log}





# Write to an attached database from xSync().
ifcapable attach {
  do_test vtab7-3.1 {
    file delete -force test2.db
    file delete -force test2.db-journal
    execsql {
................................................................................
  set ::error
} {1 {database table is locked}}

trace remove variable ::echo_module write echo_module_trace
unset -nocomplain ::callbacks

finish_test








|







 







>
>
>


|





|
>
>
>
>







 







<
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
...
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
...
201
202
203
204
205
206
207

#    May you share freely, never taking more than you give.
#
#***********************************************************************
# This file implements regression tests for SQLite library. The focus
# of this test is reading and writing to the database from within a
# virtual table xSync() callback.
#
# $Id: vtab7.test,v 1.4 2007/12/04 16:54:53 drh Exp $

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

ifcapable !vtab {
  finish_test
  return
................................................................................
  execsql {
    INSERT INTO abc2 VALUES(1, 2, 3);
    SELECT name FROM sqlite_master ORDER BY name;
  }
} {abc abc2 log newtab}

# Drop a database table from within xSync callback.
# This is not allowed.  Tables cannot be dropped while
# any other statement is active.
#
do_test vtab7-2.6 {
  set ::callbacks(xSync,abc) {
    set ::rc [catchsql { DROP TABLE newtab }]
  }
  execsql {
    INSERT INTO abc2 VALUES(1, 2, 3);
    SELECT name FROM sqlite_master ORDER BY name;
  }
} {abc abc2 log newtab}
do_test vtab7-2.6.1 {
  set ::rc
} {1 {database table is locked}}
execsql {DROP TABLE newtab}

# Write to an attached database from xSync().
ifcapable attach {
  do_test vtab7-3.1 {
    file delete -force test2.db
    file delete -force test2.db-journal
    execsql {
................................................................................
  set ::error
} {1 {database table is locked}}

trace remove variable ::echo_module write echo_module_trace
unset -nocomplain ::callbacks

finish_test