Add part of the btree layer of the shared-cache feature. (CVS 2848)

FossilOrigin-Name: 2afcad990190af97d1ad0010f211a5ca8f0fd745
This commit is contained in:
danielk1977
2005-12-30 16:28:01 +00:00
parent faa59554c3
commit aef0bf6429
19 changed files with 1014 additions and 278 deletions
+535 -144
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -13,7 +13,7 @@
** subsystem. See comments in the source code for a detailed description
** of what each interface routine does.
**
** @(#) $Id: btree.h,v 1.65 2005/12/16 15:24:29 danielk1977 Exp $
** @(#) $Id: btree.h,v 1.66 2005/12/30 16:28:02 danielk1977 Exp $
*/
#ifndef _BTREE_H_
#define _BTREE_H_
@@ -36,10 +36,12 @@
*/
typedef struct Btree Btree;
typedef struct BtCursor BtCursor;
typedef struct BtShared BtShared;
int sqlite3BtreeOpen(
const char *zFilename, /* Name of database file to open */
sqlite3 *db, /* Associated database connection */
Btree **, /* Return open Btree* here */
int flags /* Flags */
);
+2 -11
View File
@@ -14,7 +14,7 @@
** other files are for internal use by SQLite and should not be
** accessed by users of the library.
**
** $Id: main.c,v 1.312 2005/12/16 15:24:29 danielk1977 Exp $
** $Id: main.c,v 1.313 2005/12/30 16:28:02 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include "os.h"
@@ -26,15 +26,6 @@
*/
const int sqlite3one = 1;
#ifndef SQLITE_OMIT_GLOBALRECOVER
/*
** Linked list of all open database handles. This is used by the
** sqlite3_global_recover() function. Entries are added to the list
** by openDatabase() and removed by sqlite3_close().
*/
static sqlite3 *pDbList = 0;
#endif
/*
** The version of the library
*/
@@ -648,7 +639,7 @@ int sqlite3BtreeFactory(
#endif /* SQLITE_OMIT_MEMORYDB */
}
rc = sqlite3BtreeOpen(zFilename, ppBtree, btree_flags);
rc = sqlite3BtreeOpen(zFilename, db, ppBtree, btree_flags);
if( rc==SQLITE_OK ){
sqlite3BtreeSetBusyHandler(*ppBtree, (void*)&db->busyHandler);
sqlite3BtreeSetCacheSize(*ppBtree, nCache);
+123 -71
View File
@@ -18,7 +18,7 @@
** file simultaneously, or one process from reading the database while
** another is writing.
**
** @(#) $Id: pager.c,v 1.228 2005/12/20 09:19:37 danielk1977 Exp $
** @(#) $Id: pager.c,v 1.229 2005/12/30 16:28:02 danielk1977 Exp $
*/
#ifndef SQLITE_OMIT_DISKIO
#include "sqliteInt.h"
@@ -301,12 +301,36 @@ struct Pager {
/*
** These are bits that can be set in Pager.errMask.
**
** TODO: Maybe we just want a variable - Pager.errCode. Can we really
** have two simultaneous error conditions?
**
** Recovering from an SQLITE_FULL, SQLITE_LOCK, SQLITE_CORRUPT or
** SQLITE_IOERR error is not a simple matter, particularly if the pager
** cache is shared between multiple connections.
**
** SQLITE_FULL (PAGER_ERR_FULL):
** Cleared when the transaction is rolled back.
**
** SQLITE_CORRUPT (PAGER_ERR_CORRUPT):
** Cannot be cleared. The upper layer must close the current pager
** and open a new one on the same file to continue.
**
** SQLITE_PROTOCOL (PAGER_ERR_LOCK):
** This error only occurs if an internal error occurs or another process
** is not following the sqlite locking protocol (i.e. someone is
** manipulating the database file using something other than sqlite).
** This is handled in the same way as database corruption - the error
** cannot be cleared except by closing the current pager and opening
** a brand new one on the same file.
**
** SQLITE_IOERR (PAGER_ERR_DISK):
** Cleared when the transaction is rolled back.
*/
#define PAGER_ERR_FULL 0x01 /* a write() failed */
#define PAGER_ERR_MEM 0x02 /* malloc() failed */
#define PAGER_ERR_LOCK 0x04 /* error in the locking protocol */
#define PAGER_ERR_CORRUPT 0x08 /* database or journal corruption */
#define PAGER_ERR_DISK 0x10 /* general disk I/O error - bad hard drive? */
#define PAGER_ERR_LOCK 0x02 /* error in the locking protocol */
#define PAGER_ERR_CORRUPT 0x04 /* database or journal corruption */
#define PAGER_ERR_DISK 0x08 /* general disk I/O error - bad hard drive? */
/*
** Journal files begin with the following magic string. The data
@@ -465,11 +489,32 @@ static int pager_errcode(Pager *pPager){
if( pPager->errMask & PAGER_ERR_LOCK ) rc = SQLITE_PROTOCOL;
if( pPager->errMask & PAGER_ERR_DISK ) rc = SQLITE_IOERR;
if( pPager->errMask & PAGER_ERR_FULL ) rc = SQLITE_FULL;
if( pPager->errMask & PAGER_ERR_MEM ) rc = SQLITE_NOMEM;
if( pPager->errMask & PAGER_ERR_CORRUPT ) rc = SQLITE_CORRUPT;
return rc;
}
/*
** This function should be called when an error occurs within the pager
** code to set the appropriate bits in Pager.errMask.
*/
static int pager_error(Pager *pPager, int rc){
switch( rc ){
case SQLITE_PROTOCOL:
pPager->errMask |= PAGER_ERR_LOCK;
break;
case SQLITE_IOERR:
pPager->errMask |= PAGER_ERR_DISK;
break;
case SQLITE_FULL:
pPager->errMask |= PAGER_ERR_FULL;
break;
case SQLITE_CORRUPT:
pPager->errMask |= PAGER_ERR_CORRUPT;
break;
}
return rc;
}
#ifdef SQLITE_CHECK_PAGES
/*
** Return a 32-bit hash of the page data for pPage.
@@ -739,6 +784,9 @@ static int readJournalHdr(
**
** The master journal page checksum is the sum of the bytes in the master
** journal name.
**
** If zMaster is a NULL pointer (occurs for a single database transaction),
** this call is a no-op.
*/
static int writeMasterJournal(Pager *pPager, const char *zMaster){
int rc;
@@ -861,29 +909,6 @@ static void pager_reset(Pager *pPager){
assert( pPager->journalOpen==0 );
}
/*
** This function is used to reset the pager after a malloc() failure. This
** doesn't work with in-memory databases. If a malloc() fails when an
** in-memory database is in use it is not possible to recover.
**
** If a transaction or statement transaction is active, it is rolled back.
**
** It is an error to call this function if any pages are in use.
*/
#ifndef SQLITE_OMIT_GLOBALRECOVER
int sqlite3pager_reset(Pager *pPager){
if( pPager ){
if( pPager->nRef || MEMDB ){
return SQLITE_ERROR;
}
pPager->errMask &= ~(PAGER_ERR_MEM);
pager_reset(pPager);
}
return SQLITE_OK;
}
#endif
/*
** When this routine is called, the pager has the journal file open and
** a RESERVED or EXCLUSIVE lock on the database. This routine releases
@@ -1586,7 +1611,7 @@ int sqlite3pager_open(
int nExtra, /* Extra bytes append to each in-memory page */
int flags /* flags controlling this file */
){
Pager *pPager;
Pager *pPager = 0;
char *zFullPathname = 0;
int nameLen;
OsFile *fd;
@@ -1600,17 +1625,24 @@ int sqlite3pager_open(
char zTemp[SQLITE_TEMPNAME_SIZE];
SqliteTsd *pTsd = sqlite3Tsd();
/* If malloc() has already failed return SQLITE_NOMEM. Before even
** testing for this, set *ppPager to NULL so the caller knows the pager
** structure was never allocated.
*/
*ppPager = 0;
memset(&fd, 0, sizeof(fd));
if( sqlite3Tsd()->mallocFailed ){
return SQLITE_NOMEM;
}
memset(&fd, 0, sizeof(fd));
/* Open the pager file and set zFullPathname to point at malloc()ed
** memory containing the complete filename (i.e. including the directory).
*/
if( zFilename && zFilename[0] ){
#ifndef SQLITE_OMIT_MEMORYDB
if( strcmp(zFilename,":memory:")==0 ){
memDb = 1;
zFullPathname = sqliteStrDup("");
rc = SQLITE_OK;
}else
#endif
{
@@ -1627,28 +1659,35 @@ int sqlite3pager_open(
tempFile = 1;
}
}
if( !zFullPathname ){
sqlite3OsClose(&fd);
return SQLITE_NOMEM;
/* Allocate the Pager structure. As part of the same allocation, allocate
** space for the full paths of the file, directory and journal
** (Pager.zFilename, Pager.zDirectory and Pager.zJournal).
*/
if( zFullPathname ){
nameLen = strlen(zFullPathname);
pPager = sqliteMalloc( sizeof(*pPager) + nameLen*3 + 30 );
}
if( rc!=SQLITE_OK ){
/* If an error occured in either of the blocks above, free the memory
** pointed to by zFullPathname, free the Pager structure and close the
** file. Since the pager is not allocated there is no need to set
** any Pager.errMask variables.
*/
if( !pPager || !zFullPathname || rc!=SQLITE_OK ){
sqlite3OsClose(&fd);
sqliteFree(zFullPathname);
return rc;
}
nameLen = strlen(zFullPathname);
pPager = sqliteMalloc( sizeof(*pPager) + nameLen*3 + 30 );
if( pPager==0 ){
sqlite3OsClose(&fd);
sqliteFree(zFullPathname);
return SQLITE_NOMEM;
sqliteFree(pPager);
return ((rc==SQLITE_OK)?SQLITE_NOMEM:rc);
}
TRACE3("OPEN %d %s\n", FILEHANDLEID(fd), zFullPathname);
pPager->zFilename = (char*)&pPager[1];
pPager->zDirectory = &pPager->zFilename[nameLen+1];
pPager->zJournal = &pPager->zDirectory[nameLen+1];
strcpy(pPager->zFilename, zFullPathname);
strcpy(pPager->zDirectory, zFullPathname);
for(i=nameLen; i>0 && pPager->zDirectory[i-1]!='/'; i--){}
if( i>0 ) pPager->zDirectory[i-1] = 0;
strcpy(pPager->zJournal, zFullPathname);
@@ -1762,7 +1801,13 @@ void enable_simulated_io_errors(void){
/*
** Read the first N bytes from the beginning of the file into memory
** that pDest points to. No error checking is done.
** that pDest points to.
**
** No error checking is done. The rational for this is that this function
** may be called even if the file does not exist or contain a header. In
** these cases sqlite3OsRead() will return an error, to which the correct
** response is to zero the memory at pDest and continue. A real IO error
** will presumably recur and be picked up later (Todo: Think about this).
*/
void sqlite3pager_read_fileheader(Pager *pPager, int N, unsigned char *pDest){
memset(pDest, 0, N);
@@ -1973,6 +2018,11 @@ int sqlite3pager_truncate(Pager *pPager, Pgno nPage){
** and their memory is freed. Any attempt to use a page associated
** with this page cache after this function returns will likely
** result in a coredump.
**
** This function always succeeds. If a transaction is active an attempt
** is made to roll it back. If an error occurs during the rollback
** a hot journal may be left in the filesystem but no error is returned
** to the caller.
*/
int sqlite3pager_close(Pager *pPager){
PgHdr *pPg, *pNext;
@@ -2037,6 +2087,9 @@ int sqlite3pager_close(Pager *pPager){
*/
#ifndef SQLITE_OMIT_MEMORY_MANAGEMENT
/* Remove the pager from the linked list of pagers starting at
** SqliteTsd.pPager.
*/
if( pPager==pTsd->pPager ){
pTsd->pPager = pPager->pNext;
}else{
@@ -2310,7 +2363,10 @@ static int hasHotJournal(Pager *pPager){
}
/*
** Try to find a page in the cache that can be recycled.
** Try to find a page in the cache that can be recycled.
**
** This routine may return SQLITE_IOERR, SQLITE_FULL or SQLITE_OK. It
** does not set the pPager->errMask variable.
*/
static int pager_recycle(Pager *pPager, int syncOk, PgHdr **ppPg){
PgHdr *pPg;
@@ -2329,8 +2385,7 @@ static int pager_recycle(Pager *pPager, int syncOk, PgHdr **ppPg){
if( pPg==0 && pPager->pFirst && syncOk && !MEMDB){
int rc = syncJournal(pPager);
if( rc!=0 ){
sqlite3pager_rollback(pPager);
return SQLITE_IOERR;
return rc;
}
if( pPager->fullSync ){
/* If in full-sync mode, write a new journal header into the
@@ -2343,8 +2398,7 @@ static int pager_recycle(Pager *pPager, int syncOk, PgHdr **ppPg){
assert( pPager->journalOff > 0 );
rc = writeJournalHdr(pPager);
if( rc!=0 ){
sqlite3pager_rollback(pPager);
return SQLITE_IOERR;
return rc;
}
}
pPg = pPager->pFirst;
@@ -2363,8 +2417,7 @@ static int pager_recycle(Pager *pPager, int syncOk, PgHdr **ppPg){
pPg->pDirty = 0;
rc = pager_write_pagelist( pPg );
if( rc!=SQLITE_OK ){
sqlite3pager_rollback(pPager);
return SQLITE_IOERR;
return rc;
}
}
assert( pPg->dirty==0 );
@@ -2425,9 +2478,9 @@ int sqlite3pager_release_memory(int nReq){
** loop).
*/
while( SQLITE_OK==(rc = pager_recycle(p, i, &pPg)) && pPg) {
/* We've found a page to free. At this point the page has been
/* We've found a page to free. At this point the page has been
** removed from the page hash-table, free-list and synced-list
** (pFirstSynced). It is still in the all pages (pAll) list.
** (pFirstSynced). It is still in the all pages (pAll) list.
** Remove it from this list before freeing.
**
** Todo: Check the Pager.pStmt list to make sure this is Ok. It
@@ -2435,6 +2488,7 @@ int sqlite3pager_release_memory(int nReq){
*/
PgHdr *pTmp;
assert( pPg );
page_remove_from_stmt_list(pPg);
if( pPg==p->pAll ){
p->pAll = pPg->pNextAll;
}else{
@@ -2446,23 +2500,22 @@ int sqlite3pager_release_memory(int nReq){
}
if( rc!=SQLITE_OK ){
/* Assert that fsync() was enabled and the error was an io-error
** or a full database. Nothing else should be able to wrong in
** pager_recycle.
/* An error occured whilst writing to the database file or
** journal in pager_recycle(). The error is not returned to the
** caller of this function. Instead, set the Pager.errMask variable.
** The error will be returned to the user (or users, in the case
** of a shared pager cache) of the pager for which the error occured.
*/
assert( i && (rc==SQLITE_IOERR || rc==SQLITE_FULL) );
/* TODO: Figure out what to do about this. The IO-error
** belongs to the connection that is executing a transaction.
*/
assert(0);
assert( rc==SQLITE_IOERR || rc==SQLITE_FULL );
assert( p->state>=PAGER_RESERVED );
pager_error(p, rc);
}
}
}
return nReleased;
}
#endif
#endif /* SQLITE_OMIT_MEMORY_MANAGEMENT */
/*
** Acquire a page.
@@ -2513,7 +2566,7 @@ int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){
if( !pPager->noReadlock ){
rc = pager_wait_on_lock(pPager, SHARED_LOCK);
if( rc!=SQLITE_OK ){
return rc;
return pager_error(pPager, rc);
}
}
@@ -2538,7 +2591,7 @@ int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){
if( rc!=SQLITE_OK ){
sqlite3OsUnlock(pPager->fd, NO_LOCK);
pPager->state = PAGER_UNLOCK;
return rc;
return pager_error(pPager, rc);
}
pPager->state = PAGER_EXCLUSIVE;
@@ -2567,7 +2620,7 @@ int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){
*/
rc = pager_playback(pPager);
if( rc!=SQLITE_OK ){
return rc;
return pager_error(pPager, rc);
}
}
pPg = 0;
@@ -2588,7 +2641,6 @@ int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){
+ sizeof(u32) + pPager->nExtra
+ MEMDB*sizeof(PgHistory) );
if( pPg==0 ){
// pPager->errMask |= PAGER_ERR_MEM;
return SQLITE_NOMEM;
}
memset(pPg, 0, sizeof(*pPg));
@@ -2606,7 +2658,7 @@ int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){
}else{
rc = pager_recycle(pPager, 1, &pPg);
if( rc!=SQLITE_OK ){
return rc;
return pager_error(pPager, rc);
}
assert(pPg) ;
}
@@ -2662,7 +2714,7 @@ int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){
if( sqlite3OsFileSize(pPager->fd,&fileSize)!=SQLITE_OK
|| fileSize>=pgno*pPager->pageSize ){
sqlite3pager_unref(PGHDR_TO_DATA(pPg));
return rc;
return pager_error(pPager, rc);
}else{
clear_simulated_io_error();
memset(PGHDR_TO_DATA(pPg), 0, pPager->pageSize);
+9 -1
View File
@@ -12,7 +12,7 @@
** This header file defines the interface that the SQLite library
** presents to client programs.
**
** @(#) $Id: sqlite.h.in,v 1.151 2005/12/20 09:19:37 danielk1977 Exp $
** @(#) $Id: sqlite.h.in,v 1.152 2005/12/30 16:28:02 danielk1977 Exp $
*/
#ifndef _SQLITE3_H_
#define _SQLITE3_H_
@@ -1338,6 +1338,14 @@ void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*);
int sqlite3_release_memory(int);
/*
** This function is only available if the library is compiled without
** the SQLITE_OMIT_SHARED_CACHE macro defined. It is used to enable or
** disable (if the argument is true or false, respectively) the
** "shared pager" feature.
*/
int sqlite3_enable_shared_cache(int);
/*
** Undo the hack that converts floating point types to integer for
** builds on processors without floating point support.
+6 -1
View File
@@ -11,7 +11,7 @@
*************************************************************************
** Internal interface definitions for SQLite.
**
** @(#) $Id: sqliteInt.h,v 1.445 2005/12/29 23:33:54 drh Exp $
** @(#) $Id: sqliteInt.h,v 1.446 2005/12/30 16:28:02 danielk1977 Exp $
*/
#ifndef _SQLITEINT_H_
#define _SQLITEINT_H_
@@ -289,6 +289,11 @@ struct SqliteTsd {
Pager *pPager; /* Linked list of all pagers in this thread */
#endif
#ifndef SQLITE_OMIT_SHARED_CACHE
u8 useSharedData; /* True if shared pagers and schemas are enabled */
BtShared *pBtree;
#endif
#ifdef SQLITE_MEMDEBUG
i64 nMaxAlloc; /* High water mark of SqliteTsd.nAlloc */
int mallocAllowed; /* assert() in sqlite3Malloc() if not set */
+5 -3
View File
@@ -11,7 +11,7 @@
*************************************************************************
** A TCL Interface to SQLite
**
** $Id: tclsqlite.c,v 1.141 2005/12/19 14:18:11 danielk1977 Exp $
** $Id: tclsqlite.c,v 1.142 2005/12/30 16:28:02 danielk1977 Exp $
*/
#ifndef NO_TCL /* Omit this whole file if TCL is unavailable */
@@ -1754,11 +1754,12 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
** $db soft_heap_limit N
**
** Set the soft-heap-limit for this thread. Note that the limit is
** per-thread, not per-database. An empty string is returned.
** per-thread, not per-database. The previous limit is returned.
*/
case DB_SOFT_HEAP_LIMIT: {
#ifndef SQLITE_OMIT_MEMORY_MANAGEMENT
int n;
int ret;
if( objc!=3 ){
Tcl_WrongNumArgs(interp, 2, objv, "BYTES");
return TCL_ERROR;
@@ -1766,8 +1767,9 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
if( Tcl_GetIntFromObj(interp, objv[2], &n) ){
return TCL_ERROR;
}
ret = sqlite3Tsd()->nSoftHeapLimit;
sqlite3_soft_heap_limit(n);
Tcl_ResetResult(interp);
Tcl_SetObjResult(interp, Tcl_NewIntObj(ret));
#endif
break;
}
+42 -1
View File
@@ -13,7 +13,7 @@
** is not included in the SQLite library. It is used for automated
** testing of the SQLite library.
**
** $Id: test1.c,v 1.177 2005/12/20 09:19:37 danielk1977 Exp $
** $Id: test1.c,v 1.178 2005/12/30 16:28:02 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include "tcl.h"
@@ -890,6 +890,38 @@ static int sqlite_malloc_outstanding(
}
#endif
/*
** Usage: sqlite3_enable_shared_cache BOOLEAN
**
*/
#ifndef SQLITE_OMIT_SHARED_CACHE
static int test_enable_shared_cache(
ClientData clientData,
Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
int objc, /* Number of arguments */
Tcl_Obj *CONST objv[] /* Command arguments */
){
int rc;
int enable;
SqliteTsd *pTsd = sqlite3Tsd();
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(pTsd->useSharedData));
if( objc!=2 ){
Tcl_WrongNumArgs(interp, 1, objv, "BOOLEAN");
return TCL_ERROR;
}
if( Tcl_GetBooleanFromObj(interp, objv[1], &enable) ){
return TCL_ERROR;
}
rc = sqlite3_enable_shared_cache(enable);
if( rc!=SQLITE_OK ){
Tcl_SetResult(interp, (char *)sqlite3ErrStr(rc), TCL_STATIC);
return TCL_ERROR;
}
return TCL_OK;
}
#endif
/*
** Usage: sqlite_abort
**
@@ -3074,6 +3106,12 @@ static void set_options(Tcl_Interp *interp){
Tcl_SetVar2(interp, "sqlite_options", "schema_version", "1", TCL_GLOBAL_ONLY);
#endif
#ifdef SQLITE_OMIT_SHARED_CACHE
Tcl_SetVar2(interp, "sqlite_options", "shared_cache", "0", TCL_GLOBAL_ONLY);
#else
Tcl_SetVar2(interp, "sqlite_options", "shared_cache", "1", TCL_GLOBAL_ONLY);
#endif
#ifdef SQLITE_OMIT_SUBQUERY
Tcl_SetVar2(interp, "sqlite_options", "subquery", "0", TCL_GLOBAL_ONLY);
#else
@@ -3250,6 +3288,9 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
#endif
{ "sqlite3_test_errstr", test_errstr, 0 },
{ "tcl_variable_type", tcl_variable_type, 0 },
#ifndef SQLITE_OMIT_SHARED_CACHE
{ "sqlite3_enable_shared_cache", test_enable_shared_cache, 0},
#endif
};
static int bitmask_size = sizeof(Bitmask)*8;
int i;
+2 -2
View File
@@ -13,7 +13,7 @@
** is not included in the SQLite library. It is used for automated
** testing of the SQLite library.
**
** $Id: test3.c,v 1.63 2005/12/09 20:21:59 drh Exp $
** $Id: test3.c,v 1.64 2005/12/30 16:28:02 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include "pager.h"
@@ -69,7 +69,7 @@ static int btree_open(
}
if( Tcl_GetInt(interp, argv[2], &nCache) ) return TCL_ERROR;
if( Tcl_GetInt(interp, argv[3], &flags) ) return TCL_ERROR;
rc = sqlite3BtreeOpen(argv[1], &pBt, flags);
rc = sqlite3BtreeOpen(argv[1], 0, &pBt, flags);
if( rc!=SQLITE_OK ){
Tcl_AppendResult(interp, errorName(rc), 0);
return TCL_ERROR;
-1
View File
@@ -926,7 +926,6 @@ static int vdbeCommit(sqlite3 *db){
/* If there are any write-transactions at all, invoke the commit hook */
if( needXcommit && db->xCommitCallback ){
int rc;
sqlite3SafetyOff(db);
rc = db->xCommitCallback(db->pCommitArg);
sqlite3SafetyOn(db);