aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/log.c74
-rw-r--r--src/log.h5
-rw-r--r--src/main.c18
-rw-r--r--src/pager.c6
-rw-r--r--src/pager.h2
-rw-r--r--src/sqlite.h.in34
-rw-r--r--src/sqliteInt.h2
-rw-r--r--src/tclsqlite.c59
-rw-r--r--src/vdbeapi.c25
-rw-r--r--src/vdbeaux.c2
10 files changed, 189 insertions, 38 deletions
diff --git a/src/log.c b/src/log.c
index 81f808aa6..67029d3f8 100644
--- a/src/log.c
+++ b/src/log.c
@@ -203,6 +203,14 @@ struct LogSummary {
#define LOG_REGION_D 0x08
/*
+** Values for the third parameter to logLockRegion().
+*/
+#define LOG_UNLOCK 0 /* Unlock a range of bytes */
+#define LOG_RDLOCK 1 /* Put a SHARED lock on a range of bytes */
+#define LOG_WRLOCK 2 /* Put an EXCLUSIVE lock on a byte-range */
+#define LOG_WRLOCKW 3 /* Block on EXCLUSIVE lock on a byte-range */
+
+/*
** A single instance of this structure is allocated as part of each
** connection to a database log. All structures associated with the
** same log file are linked together into a list using LogLock.pNext
@@ -225,6 +233,7 @@ struct Log {
sqlite3_file *pFd; /* File handle for log file */
int isLocked; /* Non-zero if a snapshot is held open */
int isWriteLocked; /* True if this is the writer connection */
+ u32 iCallback; /* Value to pass to log callback (or 0) */
LogSummaryHdr hdr; /* Log summary header for current snapshot */
LogLock lock; /* Lock held by this connection (if any) */
};
@@ -647,29 +656,29 @@ finished:
}
/*
-** Values for the third parameter to logLockRegion().
+** Place, modify or remove a lock on the log-summary file associated
+** with pSummary.
*/
-#define LOG_UNLOCK 0
-#define LOG_RDLOCK 1
-#define LOG_WRLOCK 2
-#define LOG_WRLOCKW 3
-
-static int logLockFd(LogSummary *pSummary, int iStart, int nByte, int op){
+static int logLockFd(
+ LogSummary *pSummary, /* The log-summary object to lock */
+ int iStart, /* First byte to lock */
+ int nByte, /* Number of bytes to lock */
+ int op /* LOG_UNLOCK, RDLOCK, WRLOCK or WRLOCKW */
+){
int aType[4] = {
- F_UNLCK, /* LOG_UNLOCK */
- F_RDLCK, /* LOG_RDLOCK */
- F_WRLCK, /* LOG_WRLOCK */
- F_WRLCK /* LOG_WRLOCKW */
+ F_UNLCK, /* LOG_UNLOCK */
+ F_RDLCK, /* LOG_RDLOCK */
+ F_WRLCK, /* LOG_WRLOCK */
+ F_WRLCK /* LOG_WRLOCKW */
};
int aOp[4] = {
- F_SETLK, /* LOG_UNLOCK */
- F_SETLK, /* LOG_RDLOCK */
- F_SETLK, /* LOG_WRLOCK */
- F_SETLKW /* LOG_WRLOCKW */
+ F_SETLK, /* LOG_UNLOCK */
+ F_SETLK, /* LOG_RDLOCK */
+ F_SETLK, /* LOG_WRLOCK */
+ F_SETLKW /* LOG_WRLOCKW */
};
-
- struct flock f; /* Locking operation */
- int rc; /* Value returned by fcntl() */
+ struct flock f; /* Locking operation */
+ int rc; /* Value returned by fcntl() */
assert( ArraySize(aType)==ArraySize(aOp) );
assert( op>=0 && op<ArraySize(aType) );
@@ -816,19 +825,28 @@ static int logLockRegion(Log *pLog, u32 mRegion, int op){
return SQLITE_OK;
}
+/*
+** Lock the DMH region, either with an EXCLUSIVE or SHARED lock. This
+** function is never called with LOG_UNLOCK - the only way the DMH region
+** is every completely unlocked is by by closing the file descriptor.
+*/
static int logLockDMH(LogSummary *pSummary, int eLock){
+ assert( sqlite3_mutex_held(pSummary->mutex) );
assert( eLock==LOG_RDLOCK || eLock==LOG_WRLOCK );
return logLockFd(pSummary, LOG_LOCK_DMH, 1, eLock);
}
+/*
+** Lock (or unlock) the MUTEX region. It is always locked using an
+** EXCLUSIVE, blocking lock.
+*/
static int logLockMutex(LogSummary *pSummary, int eLock){
+ assert( sqlite3_mutex_held(pSummary->mutex) );
assert( eLock==LOG_WRLOCKW || eLock==LOG_UNLOCK );
logLockFd(pSummary, LOG_LOCK_MUTEX, 1, eLock);
return SQLITE_OK;
}
-
-
/*
** This function intializes the connection to the log-summary identified
** by struct pSummary.
@@ -880,7 +898,7 @@ static int logSummaryInit(
}
rc = logLockDMH(pSummary, LOG_RDLOCK);
if( rc!=SQLITE_OK ){
- return SQLITE_IOERR;
+ rc = SQLITE_IOERR;
}
out:
@@ -1461,7 +1479,7 @@ int sqlite3LogRead(Log *pLog, Pgno pgno, int *pInLog, u8 *pOut){
/*
** Set *pPgno to the size of the database file (or zero, if unknown).
*/
-void sqlite3LogMaxpgno(Log *pLog, Pgno *pPgno){
+void sqlite3LogDbsize(Log *pLog, Pgno *pPgno){
assert( pLog->isLocked );
*pPgno = pLog->hdr.nPage;
}
@@ -1646,9 +1664,10 @@ int sqlite3LogFrames(
if( isCommit && SQLITE_OK==(rc = logEnterMutex(pLog)) ){
logSummaryWriteHdr(pLog->pSummary, &pLog->hdr);
logLeaveMutex(pLog);
+ pLog->iCallback = iFrame;
}
- return SQLITE_OK;
+ return rc;
}
/*
@@ -1698,3 +1717,12 @@ int sqlite3LogCheckpoint(
return rc;
}
+int sqlite3LogCallback(Log *pLog){
+ u32 ret = 0;
+ if( pLog ){
+ ret = pLog->iCallback;
+ pLog->iCallback = 0;
+ }
+ return (int)ret;
+}
+
diff --git a/src/log.h b/src/log.h
index 5da5fb105..af698ebf5 100644
--- a/src/log.h
+++ b/src/log.h
@@ -32,7 +32,7 @@ void sqlite3LogCloseSnapshot(Log *pLog);
/* Read a page from the log, if it is present. */
int sqlite3LogRead(Log *pLog, Pgno pgno, int *pInLog, u8 *pOut);
-void sqlite3LogMaxpgno(Log *pLog, Pgno *pPgno);
+void sqlite3LogDbsize(Log *pLog, Pgno *pPgno);
/* Obtain or release the WRITER lock. */
int sqlite3LogWriteLock(Log *pLog, int op);
@@ -50,4 +50,7 @@ int sqlite3LogCheckpoint(
void *pBusyHandlerArg /* Argument to pass to xBusyHandler */
);
+/* Return the value to pass to a log callback. Or 0 for no callback. */
+int sqlite3LogCallback(Log *pLog);
+
#endif /* _LOG_H_ */
diff --git a/src/main.c b/src/main.c
index cc03d50ed..80c15963a 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1187,6 +1187,24 @@ void *sqlite3_rollback_hook(
}
/*
+** Register a callback to be invoked each time a transaction is written
+** into the write-ahead-log by this database connection.
+*/
+void *sqlite3_log_hook(
+ sqlite3 *db, /* Attach the hook to this db handle */
+ int(*xCallback)(void *, sqlite3*, const char*, int),
+ void *pArg /* First argument passed to xCallback() */
+){
+ void *pRet;
+ sqlite3_mutex_enter(db->mutex);
+ pRet = db->pLogArg;
+ db->xLogCallback = xCallback;
+ db->pLogArg = pArg;
+ sqlite3_mutex_leave(db->mutex);
+ return pRet;
+}
+
+/*
** This function returns true if main-memory should be used instead of
** a temporary file for transient pager files and statement journals.
** The value returned depends on the value of db->temp_store (runtime
diff --git a/src/pager.c b/src/pager.c
index 5fb259ff7..d46350afd 100644
--- a/src/pager.c
+++ b/src/pager.c
@@ -2686,7 +2686,7 @@ int sqlite3PagerPagecount(Pager *pPager, int *pnPage){
i64 n = 0; /* File size in bytes returned by OsFileSize() */
if( pagerUseLog(pPager) ){
- sqlite3LogMaxpgno(pPager->pLog, &nPage);
+ sqlite3LogDbsize(pPager->pLog, &nPage);
}
if( nPage==0 ){
@@ -5709,4 +5709,8 @@ int sqlite3PagerCheckpoint(Pager *pPager){
return rc;
}
+int sqlite3PagerLogCallback(Pager *pPager){
+ return sqlite3LogCallback(pPager->pLog);
+}
+
#endif /* SQLITE_OMIT_DISKIO */
diff --git a/src/pager.h b/src/pager.h
index 1e14d2ea6..17d61a500 100644
--- a/src/pager.h
+++ b/src/pager.h
@@ -133,7 +133,9 @@ int sqlite3PagerRollback(Pager*);
int sqlite3PagerOpenSavepoint(Pager *pPager, int n);
int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint);
int sqlite3PagerSharedLock(Pager *pPager);
+
int sqlite3PagerCheckpoint(Pager *pPager);
+int sqlite3PagerLogCallback(Pager *pPager);
/* Functions used to query pager state and configuration. */
u8 sqlite3PagerIsreadonly(Pager*);
diff --git a/src/sqlite.h.in b/src/sqlite.h.in
index c1363031e..97c97aab0 100644
--- a/src/sqlite.h.in
+++ b/src/sqlite.h.in
@@ -5727,6 +5727,40 @@ int sqlite3_strnicmp(const char *, const char *, int);
void sqlite3_log(int iErrCode, const char *zFormat, ...);
/*
+** Experimental WAL callback interface.
+**
+** The [sqlite3_log_hook()] function is used to register a callback that
+** will be invoked each time a database connection commits data to a
+** write-ahead-log (i.e. whenever a transaction is committed in
+** journal_mode=WAL mode).
+**
+** The callback is invoked by SQLite after the commit has taken place and
+** the associated write-lock on the database released, so the implementation
+** may read, write or checkpoint the database as required.
+**
+** The first parameter passed to the callback function when it is invoked
+** is a copy of the third parameter passed to sqlite3_log_hook() when
+** registering the callback. The second is a copy of the database handle.
+** The third parameter is the name of the database that was written to -
+** either "main" or the name of an ATTACHed database. The fourth parameter
+** is the number of pages currently in the log file, including those that
+** were just committed.
+**
+** If an invocation of the callback function returns non-zero, then a
+** checkpoint is automatically run on the database. If zero is returned,
+** no special action is taken.
+**
+** A single database handle may have at most a single log callback
+** registered at one time. Calling [sqlite3_log_hook()] replaces any
+** previously registered log callback.
+*/
+void *sqlite3_log_hook(
+ sqlite3*,
+ int(*)(void *,sqlite3*,const char*,int),
+ void*
+);
+
+/*
** Undo the hack that converts floating point types to integer for
** builds on processors without floating point support.
*/
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 6007b8af9..1dfc86da2 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -823,6 +823,8 @@ struct sqlite3 {
void (*xRollbackCallback)(void*); /* Invoked at every commit. */
void *pUpdateArg;
void (*xUpdateCallback)(void*,int, const char*,const char*,sqlite_int64);
+ int (*xLogCallback)(void *, sqlite3 *, const char *, u32);
+ void *pLogArg;
void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*);
void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*);
void *pCollNeededArg;
diff --git a/src/tclsqlite.c b/src/tclsqlite.c
index 02d67c479..8582ce34e 100644
--- a/src/tclsqlite.c
+++ b/src/tclsqlite.c
@@ -123,6 +123,7 @@ struct SqliteDb {
SqlFunc *pFunc; /* List of SQL functions */
Tcl_Obj *pUpdateHook; /* Update hook script (if any) */
Tcl_Obj *pRollbackHook; /* Rollback hook script (if any) */
+ Tcl_Obj *pLogHook; /* WAL hook script (if any) */
Tcl_Obj *pUnlockNotify; /* Unlock notify script (if any) */
SqlCollate *pCollate; /* List of SQL collation functions */
int rc; /* Return code of most recent sqlite3_exec() */
@@ -485,6 +486,9 @@ static void DbDeleteCmd(void *db){
if( pDb->pRollbackHook ){
Tcl_DecrRefCount(pDb->pRollbackHook);
}
+ if( pDb->pLogHook ){
+ Tcl_DecrRefCount(pDb->pLogHook);
+ }
if( pDb->pCollateNeeded ){
Tcl_DecrRefCount(pDb->pCollateNeeded);
}
@@ -589,6 +593,32 @@ static void DbRollbackHandler(void *clientData){
}
}
+static int DbLogHandler(
+ void *clientData,
+ sqlite3 *db,
+ const char *zDb,
+ int nEntry
+){
+ int ret = 0;
+ Tcl_Obj *p;
+ SqliteDb *pDb = (SqliteDb*)clientData;
+ Tcl_Interp *interp = pDb->interp;
+ assert(pDb->pLogHook);
+
+ p = Tcl_DuplicateObj(pDb->pLogHook);
+ Tcl_IncrRefCount(p);
+ Tcl_ListObjAppendElement(interp, p, Tcl_NewStringObj(zDb, -1));
+ Tcl_ListObjAppendElement(interp, p, Tcl_NewIntObj(nEntry));
+ if( TCL_OK!=Tcl_EvalObjEx(interp, p, 0)
+ || TCL_OK!=Tcl_GetIntFromObj(interp, Tcl_GetObjResult(interp), &ret)
+ ){
+ Tcl_BackgroundError(interp);
+ }
+ Tcl_DecrRefCount(p);
+
+ return ret;
+}
+
#if defined(SQLITE_TEST) && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
static void setTestUnlockNotifyVars(Tcl_Interp *interp, int iArg, int nArg){
char zBuf[64];
@@ -1540,12 +1570,12 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
"complete", "copy", "enable_load_extension",
"errorcode", "eval", "exists",
"function", "incrblob", "interrupt",
- "last_insert_rowid", "nullvalue", "onecolumn",
- "profile", "progress", "rekey",
- "restore", "rollback_hook", "status",
- "timeout", "total_changes", "trace",
- "transaction", "unlock_notify", "update_hook",
- "version", 0
+ "last_insert_rowid", "log_hook", "nullvalue",
+ "onecolumn", "profile", "progress",
+ "rekey", "restore", "rollback_hook",
+ "status", "timeout", "total_changes",
+ "trace", "transaction", "unlock_notify",
+ "update_hook", "version", 0
};
enum DB_enum {
DB_AUTHORIZER, DB_BACKUP, DB_BUSY,
@@ -1554,12 +1584,12 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
DB_COMPLETE, DB_COPY, DB_ENABLE_LOAD_EXTENSION,
DB_ERRORCODE, DB_EVAL, DB_EXISTS,
DB_FUNCTION, DB_INCRBLOB, DB_INTERRUPT,
- DB_LAST_INSERT_ROWID, DB_NULLVALUE, DB_ONECOLUMN,
- DB_PROFILE, DB_PROGRESS, DB_REKEY,
- DB_RESTORE, DB_ROLLBACK_HOOK, DB_STATUS,
- DB_TIMEOUT, DB_TOTAL_CHANGES, DB_TRACE,
- DB_TRANSACTION, DB_UNLOCK_NOTIFY, DB_UPDATE_HOOK,
- DB_VERSION,
+ DB_LAST_INSERT_ROWID, DB_LOG_HOOK, DB_NULLVALUE,
+ DB_ONECOLUMN, DB_PROFILE, DB_PROGRESS,
+ DB_REKEY, DB_RESTORE, DB_ROLLBACK_HOOK,
+ DB_STATUS, DB_TIMEOUT, DB_TOTAL_CHANGES,
+ DB_TRACE, DB_TRANSACTION, DB_UNLOCK_NOTIFY,
+ DB_UPDATE_HOOK, DB_VERSION
};
/* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */
@@ -2730,9 +2760,11 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
}
/*
+ ** $db log_hook ?script?
** $db update_hook ?script?
** $db rollback_hook ?script?
*/
+ case DB_LOG_HOOK:
case DB_UPDATE_HOOK:
case DB_ROLLBACK_HOOK: {
@@ -2742,6 +2774,8 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
Tcl_Obj **ppHook;
if( choice==DB_UPDATE_HOOK ){
ppHook = &pDb->pUpdateHook;
+ }else if( choice==DB_LOG_HOOK ){
+ ppHook = &pDb->pLogHook;
}else{
ppHook = &pDb->pRollbackHook;
}
@@ -2767,6 +2801,7 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
sqlite3_update_hook(pDb->db, (pDb->pUpdateHook?DbUpdateHandler:0), pDb);
sqlite3_rollback_hook(pDb->db,(pDb->pRollbackHook?DbRollbackHandler:0),pDb);
+ sqlite3_log_hook(pDb->db,(pDb->pLogHook?DbLogHandler:0),pDb);
break;
}
diff --git a/src/vdbeapi.c b/src/vdbeapi.c
index 29a2a429b..1c938ee5d 100644
--- a/src/vdbeapi.c
+++ b/src/vdbeapi.c
@@ -306,6 +306,23 @@ void sqlite3_result_error_nomem(sqlite3_context *pCtx){
pCtx->s.db->mallocFailed = 1;
}
+static int doLogCallbacks(sqlite3 *db){
+ int i;
+ int rc = SQLITE_OK;
+ for(i=0; i<db->nDb; i++){
+ Btree *pBt = db->aDb[i].pBt;
+ if( pBt ){
+ int nEntry = sqlite3PagerLogCallback(sqlite3BtreePager(pBt));
+ if( db->xLogCallback && nEntry>0 && rc==SQLITE_OK
+ && db->xLogCallback(db->pLogArg, db, db->aDb[i].zName, nEntry)
+ ){
+ rc = sqlite3PagerCheckpoint(sqlite3BtreePager(pBt));
+ }
+ }
+ }
+ return rc;
+}
+
/*
** Execute the statement pStmt, either until a row of data is ready, the
** statement is completely executed or an error occurs.
@@ -387,6 +404,14 @@ static int sqlite3Step(Vdbe *p){
}
#endif
+ if( rc==SQLITE_DONE ){
+ assert( p->rc==SQLITE_OK );
+ p->rc = doLogCallbacks(db);
+ if( p->rc!=SQLITE_OK ){
+ rc = SQLITE_ERROR;
+ }
+ }
+
db->errCode = rc;
if( SQLITE_NOMEM==sqlite3ApiExit(p->db, p->rc) ){
p->rc = SQLITE_NOMEM;
diff --git a/src/vdbeaux.c b/src/vdbeaux.c
index a5746f869..7f00f397a 100644
--- a/src/vdbeaux.c
+++ b/src/vdbeaux.c
@@ -1651,7 +1651,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
** one database file has an open write transaction, a master journal
** file is required for an atomic commit.
*/
- for(i=0; i<db->nDb; i++){
+ for(i=0; i<db->nDb; i++){
Btree *pBt = db->aDb[i].pBt;
if( sqlite3BtreeIsInTrans(pBt) ){
needXcommit = 1;