/ Check-in [46f4eb54]
Login

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

Overview
Comment:When a connection disconnects from a shared-cache database, only delete the in-memory schema if there are no other connections.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | shared-schema
Files: files | file ages | folders
SHA1: 46f4eb5430d7bc9a339cdf7124ff4bd518eaa39b
User & Date: dan 2012-05-15 17:15:34
Context
2012-05-15
18:28
The former sqlite3ResetInternalSchema() routine was really two different routines, selected by parameter, each with a confused mission. So split this routine up into three separate smaller routines, calling each separately as needed. Hopefully this will make further refactoring and schema reset collateral damage containment easier. check-in: aa0c3493 user: drh tags: shared-schema
17:15
When a connection disconnects from a shared-cache database, only delete the in-memory schema if there are no other connections. check-in: 46f4eb54 user: dan tags: shared-schema
12:49
Add assert()s to verify that Table objects in the schema never use lookaside memory. check-in: 736d6ea6 user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/main.c.

715
716
717
718
719
720
721
























722
723
724
725
726
727
728
...
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
...
775
776
777
778
779
780
781



782


783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
    pDestructor->nRef--;
    if( pDestructor->nRef==0 ){
      pDestructor->xDestroy(pDestructor->pUserData);
      sqlite3DbFree(db, pDestructor);
    }
  }
}

























/*
** Close an existing SQLite database
*/
int sqlite3_close(sqlite3 *db){
  HashElem *i;                    /* Hash table iterator */
  int j;
................................................................................
    return SQLITE_OK;
  }
  if( !sqlite3SafetyCheckSickOrOk(db) ){
    return SQLITE_MISUSE_BKPT;
  }
  sqlite3_mutex_enter(db->mutex);

  /* Force xDestroy calls on all virtual tables */
  sqlite3ResetInternalSchema(db, -1);

  /* If a transaction is open, the ResetInternalSchema() call above
  ** will not have called the xDisconnect() method on any virtual
  ** tables in the db->aVTrans[] array. The following sqlite3VtabRollback()
  ** call will do so. We need to do this before the check for active
  ** SQL statements below, as the v-table implementation may be storing
  ** some prepared statements internally.
  */
  sqlite3VtabRollback(db);
................................................................................
      sqlite3BtreeClose(pDb->pBt);
      pDb->pBt = 0;
      if( j!=1 ){
        pDb->pSchema = 0;
      }
    }
  }



  sqlite3ResetInternalSchema(db, -1);



  /* Tell the code in notify.c that the connection no longer holds any
  ** locks and does not require any further unlock-notify callbacks.
  */
  sqlite3ConnectionClosed(db);

  assert( db->nDb<=2 );
  assert( db->aDb==db->aDbStatic );
  for(j=0; j<ArraySize(db->aFunc.a); j++){
    FuncDef *pNext, *pHash, *p;
    for(p=db->aFunc.a[j]; p; p=pHash){
      pHash = p->pHash;
      while( p ){
        functionDestroy(db, p);
        pNext = p->pNext;







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







 







|
|

|







 







>
>
>

>
>






<
<







715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
...
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
...
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817


818
819
820
821
822
823
824
    pDestructor->nRef--;
    if( pDestructor->nRef==0 ){
      pDestructor->xDestroy(pDestructor->pUserData);
      sqlite3DbFree(db, pDestructor);
    }
  }
}

/*
** Disconnect all sqlite3_vtab objects that belong to database connection
** db. This is called when db is being closed.
*/
static void disconnectAllVtab(sqlite3 *db){
#ifndef SQLITE_OMIT_VIRTUALTABLE
  int i;
  sqlite3BtreeEnterAll(db);
  for(i=0; i<db->nDb; i++){
    Schema *pSchema = db->aDb[i].pSchema;
    if( db->aDb[i].pSchema ){
      HashElem *p;
      for(p=sqliteHashFirst(&pSchema->tblHash); p; p=sqliteHashNext(p)){
        Table *pTab = (Table *)sqliteHashData(p);
        if( IsVirtual(pTab) ) sqlite3VtabDisconnect(db, pTab);
      }
    }
  }
  sqlite3BtreeLeaveAll(db);
#else
  UNUSED_PARAMETER(db);
#endif
}

/*
** Close an existing SQLite database
*/
int sqlite3_close(sqlite3 *db){
  HashElem *i;                    /* Hash table iterator */
  int j;
................................................................................
    return SQLITE_OK;
  }
  if( !sqlite3SafetyCheckSickOrOk(db) ){
    return SQLITE_MISUSE_BKPT;
  }
  sqlite3_mutex_enter(db->mutex);

  /* Force xDisconnect calls on all virtual tables */
  disconnectAllVtab(db);

  /* If a transaction is open, the disconnectAllVtab() call above
  ** will not have called the xDisconnect() method on any virtual
  ** tables in the db->aVTrans[] array. The following sqlite3VtabRollback()
  ** call will do so. We need to do this before the check for active
  ** SQL statements below, as the v-table implementation may be storing
  ** some prepared statements internally.
  */
  sqlite3VtabRollback(db);
................................................................................
      sqlite3BtreeClose(pDb->pBt);
      pDb->pBt = 0;
      if( j!=1 ){
        pDb->pSchema = 0;
      }
    }
  }

  /* This call frees the schema associated with the temp database only (if
  ** any). It also frees the db->aDb array, if required.  */
  sqlite3ResetInternalSchema(db, -1);
  assert( db->nDb<=2 );
  assert( db->aDb==db->aDbStatic );

  /* Tell the code in notify.c that the connection no longer holds any
  ** locks and does not require any further unlock-notify callbacks.
  */
  sqlite3ConnectionClosed(db);



  for(j=0; j<ArraySize(db->aFunc.a); j++){
    FuncDef *pNext, *pHash, *p;
    for(p=db->aFunc.a[j]; p; p=pHash){
      pHash = p->pHash;
      while( p ){
        functionDestroy(db, p);
        pNext = p->pNext;

Changes to src/sqliteInt.h.

3097
3098
3099
3100
3101
3102
3103

3104
3105
3106
3107
3108
3109
3110
#  define sqlite3VtabLock(X) 
#  define sqlite3VtabUnlock(X)
#  define sqlite3VtabUnlockList(X)
#  define sqlite3VtabSavepoint(X, Y, Z) SQLITE_OK
#  define sqlite3GetVTable(X,Y)  ((VTable*)0)
#else
   void sqlite3VtabClear(sqlite3 *db, Table*);

   int sqlite3VtabSync(sqlite3 *db, char **);
   int sqlite3VtabRollback(sqlite3 *db);
   int sqlite3VtabCommit(sqlite3 *db);
   void sqlite3VtabLock(VTable *);
   void sqlite3VtabUnlock(VTable *);
   void sqlite3VtabUnlockList(sqlite3*);
   int sqlite3VtabSavepoint(sqlite3 *, int, int);







>







3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
#  define sqlite3VtabLock(X) 
#  define sqlite3VtabUnlock(X)
#  define sqlite3VtabUnlockList(X)
#  define sqlite3VtabSavepoint(X, Y, Z) SQLITE_OK
#  define sqlite3GetVTable(X,Y)  ((VTable*)0)
#else
   void sqlite3VtabClear(sqlite3 *db, Table*);
   void sqlite3VtabDisconnect(sqlite3 *db, Table *p);
   int sqlite3VtabSync(sqlite3 *db, char **);
   int sqlite3VtabRollback(sqlite3 *db);
   int sqlite3VtabCommit(sqlite3 *db);
   void sqlite3VtabLock(VTable *);
   void sqlite3VtabUnlock(VTable *);
   void sqlite3VtabUnlockList(sqlite3*);
   int sqlite3VtabSavepoint(sqlite3 *, int, int);

Changes to src/vtab.c.

175
176
177
178
179
180
181

























182
183
184
185
186
187
188
    }
    pVTable = pNext;
  }

  assert( !db || pRet );
  return pRet;
}



























/*
** Disconnect all the virtual table objects in the sqlite3.pDisconnect list.
**
** This function may only be called when the mutexes associated with all
** shared b-tree databases opened using connection db are held by the 







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







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
    }
    pVTable = pNext;
  }

  assert( !db || pRet );
  return pRet;
}

/*
** Table *p is a virtual table. This function removes the VTable object
** for table *p associated with database connection db from the linked
** list in p->pVTab. It also decrements the VTable ref count. This is
** used when closing database connection db to free all of its VTable
** objects without disturbing the rest of the Schema object (which may
** be being used by other shared-cache connections).
*/
void sqlite3VtabDisconnect(sqlite3 *db, Table *p){
  VTable **ppVTab;

  assert( IsVirtual(p) );
  assert( sqlite3BtreeHoldsAllMutexes(db) );
  assert( sqlite3_mutex_held(db->mutex) );

  for(ppVTab=&p->pVTable; *ppVTab; ppVTab=&(*ppVTab)->pNext){
    if( (*ppVTab)->db==db  ){
      VTable *pVTab = *ppVTab;
      *ppVTab = pVTab->pNext;
      sqlite3VtabUnlock(pVTab);
      break;
    }
  }
}


/*
** Disconnect all the virtual table objects in the sqlite3.pDisconnect list.
**
** This function may only be called when the mutexes associated with all
** shared b-tree databases opened using connection db are held by the 

Changes to test/capi3.test.

645
646
647
648
649
650
651




652
653
654
655
656
657
658

659
660
661
662
663
664
665
  set STMT [sqlite3_prepare $DB $sql -1 TAIL]
  expr 0
} {0}
do_test capi3-6.1 {
  db cache flush
  sqlite3_close $DB
} {SQLITE_BUSY}




do_test capi3-6.2 {
  sqlite3_step $STMT
} {SQLITE_ERROR}
#check_data $STMT capi3-6.3 {INTEGER} {1} {1.0} {1}
do_test capi3-6.3 {
  sqlite3_finalize $STMT
} {SQLITE_SCHEMA}

do_test capi3-6.4-misuse {
  db cache flush
  sqlite3_close $DB
} {SQLITE_OK}
db close

# This procedure sets the value of the file-format in file 'test.db'







>
>
>
>


|



|
>







645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
  set STMT [sqlite3_prepare $DB $sql -1 TAIL]
  expr 0
} {0}
do_test capi3-6.1 {
  db cache flush
  sqlite3_close $DB
} {SQLITE_BUSY}

# 6.2 and 6.3 used to return SQLITE_ERROR and SQLITE_SCHEMA, respectively.
# But since attempting to close a connection no longer resets the internal
# schema and expires all statements, this is no longer the case.
do_test capi3-6.2 {
  sqlite3_step $STMT
} {SQLITE_ROW}
#check_data $STMT capi3-6.3 {INTEGER} {1} {1.0} {1}
do_test capi3-6.3 {
  sqlite3_finalize $STMT
} {SQLITE_OK}

do_test capi3-6.4-misuse {
  db cache flush
  sqlite3_close $DB
} {SQLITE_OK}
db close

# This procedure sets the value of the file-format in file 'test.db'

Added test/shared8.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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# 2012 May 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.
#
#***********************************************************************
#
# The tests in this file are intended to show that closing one database
# connection to a shared-cache while there exist other connections (a)
# does not cause the schema to be reloaded and (b) does not cause any
# other problems.
#

set testdir [file dirname $argv0]
source $testdir/tester.tcl
ifcapable !shared_cache { finish_test ; return }
set testprefix shared8

db close
set ::enable_shared_cache [sqlite3_enable_shared_cache 1]
do_test 0.0 { sqlite3_enable_shared_cache } {1}

proc roman {n} {
  array set R {1 i 2 ii 3 iii 4 iv 5 v 6 vi 7 vii 8 viii 9 ix 10 x}
  set R($n)
}

#-------------------------------------------------------------------------
# The following tests work as follows:
#
#    1.0: Open connection [db1] and populate the database.
#
#    1.1: Using "PRAGMA writable_schema", destroy the database schema on
#         disk. The schema is still in memory, so it is possible to keep
#         using it, but any attempt to reload it from disk will fail.
#
#    1.3-4: Open connection db2. Check that it can see the db schema. Then
#           close db1 and check that db2 still works. This shows that closing
#           db1 did not reset the in-memory schema.
#
#    1.5-7: Similar to 1.3-4.
#
#    1.8: Close all database connections (deleting the in-memory schema).
#         Then open a new connection and check that it cannot read the db.
#         
do_test 1.0 {
  sqlite3 db1 test.db
  db1 func roman roman
  execsql {
    CREATE TABLE t1(a, b);
    INSERT INTO t1 VALUES(1, 1);
    INSERT INTO t1 VALUES(2, 2);
    INSERT INTO t1 VALUES(3, 3);
    INSERT INTO t1 VALUES(4, 4);
    CREATE VIEW v1 AS SELECT a, roman(b) FROM t1;
    SELECT * FROM v1;
  } db1
} {1 i 2 ii 3 iii 4 iv}

do_test 1.1 {
  execsql { 
    PRAGMA writable_schema = 1;
    DELETE FROM sqlite_master WHERE 1;
    PRAGMA writable_schema = 0;
    SELECT * FROM sqlite_master;
  } db1
} {}

do_test 1.2 {
  execsql { SELECT * FROM v1 } db1
} {1 i 2 ii 3 iii 4 iv}

do_test 1.3 {
  sqlite3 db2 test.db
  db2 func roman roman
  execsql { SELECT * FROM v1 } db2
} {1 i 2 ii 3 iii 4 iv}

do_test 1.4 {
  db1 close
  execsql { SELECT * FROM v1 } db2
} {1 i 2 ii 3 iii 4 iv}

do_test 1.5 {
  sqlite3 db3 test.db
  db3 func roman roman
  execsql { SELECT * FROM v1 } db3
} {1 i 2 ii 3 iii 4 iv}

do_test 1.6 {
  execsql { SELECT * FROM v1 } db2
} {1 i 2 ii 3 iii 4 iv}

do_test 1.7 {
  db2 close
  execsql { SELECT * FROM v1 } db3
} {1 i 2 ii 3 iii 4 iv}

do_test 1.8 {
  db3 close
  sqlite3 db4 test.db
  catchsql { SELECT * FROM v1 } db4
} {1 {no such table: v1}}


foreach db {db1 db2 db3 db4} { catch { $db close } }
sqlite3_enable_shared_cache $::enable_shared_cache
finish_test