Index: third_party/sqlite/src/ext/rbu/sqlite3rbu.c |
diff --git a/third_party/sqlite/src/ext/rbu/sqlite3rbu.c b/third_party/sqlite/src/ext/rbu/sqlite3rbu.c |
index 4c38e14c9e917f097cd9c6980c6caf77ec96af59..48c69115ee5a5dd8858acadc65b04e0594ccf90b 100644 |
--- a/third_party/sqlite/src/ext/rbu/sqlite3rbu.c |
+++ b/third_party/sqlite/src/ext/rbu/sqlite3rbu.c |
@@ -147,14 +147,15 @@ |
** RBU_STATE_OALSZ: |
** Valid if STAGE==1. The size in bytes of the *-oal file. |
*/ |
-#define RBU_STATE_STAGE 1 |
-#define RBU_STATE_TBL 2 |
-#define RBU_STATE_IDX 3 |
-#define RBU_STATE_ROW 4 |
-#define RBU_STATE_PROGRESS 5 |
-#define RBU_STATE_CKPT 6 |
-#define RBU_STATE_COOKIE 7 |
-#define RBU_STATE_OALSZ 8 |
+#define RBU_STATE_STAGE 1 |
+#define RBU_STATE_TBL 2 |
+#define RBU_STATE_IDX 3 |
+#define RBU_STATE_ROW 4 |
+#define RBU_STATE_PROGRESS 5 |
+#define RBU_STATE_CKPT 6 |
+#define RBU_STATE_COOKIE 7 |
+#define RBU_STATE_OALSZ 8 |
+#define RBU_STATE_PHASEONESTEP 9 |
#define RBU_STAGE_OAL 1 |
#define RBU_STAGE_MOVE 2 |
@@ -175,6 +176,7 @@ typedef struct RbuUpdateStmt RbuUpdateStmt; |
#if !defined(SQLITE_AMALGAMATION) |
typedef unsigned int u32; |
+typedef unsigned short u16; |
typedef unsigned char u8; |
typedef sqlite3_int64 i64; |
#endif |
@@ -188,6 +190,8 @@ typedef sqlite3_int64 i64; |
#define WAL_LOCK_CKPT 1 |
#define WAL_LOCK_READ0 3 |
+#define SQLITE_FCNTL_RBUCNT 5149216 |
+ |
/* |
** A structure to store values read from the rbu_state table in memory. |
*/ |
@@ -200,6 +204,7 @@ struct RbuState { |
i64 nProgress; |
u32 iCookie; |
i64 iOalSz; |
+ i64 nPhaseOneStep; |
}; |
struct RbuUpdateStmt { |
@@ -244,6 +249,7 @@ struct RbuObjIter { |
int iTnum; /* Root page of current object */ |
int iPkTnum; /* If eType==EXTERNAL, root of PK index */ |
int bUnique; /* Current index is unique */ |
+ int nIndex; /* Number of aux. indexes on table zTbl */ |
/* Statements created by rbuObjIterPrepareAll() */ |
int nCol; /* Number of columns in current object */ |
@@ -280,10 +286,11 @@ struct RbuObjIter { |
*/ |
#define RBU_INSERT 1 /* Insert on a main table b-tree */ |
#define RBU_DELETE 2 /* Delete a row from a main table b-tree */ |
-#define RBU_IDX_DELETE 3 /* Delete a row from an aux. index b-tree */ |
-#define RBU_IDX_INSERT 4 /* Insert on an aux. index b-tree */ |
-#define RBU_UPDATE 5 /* Update a row in a main table b-tree */ |
+#define RBU_REPLACE 3 /* Delete and then insert a row */ |
+#define RBU_IDX_DELETE 4 /* Delete a row from an aux. index b-tree */ |
+#define RBU_IDX_INSERT 5 /* Insert on an aux. index b-tree */ |
+#define RBU_UPDATE 6 /* Update a row in a main table b-tree */ |
/* |
** A single step of an incremental checkpoint - frame iWalFrame of the wal |
@@ -296,6 +303,43 @@ struct RbuFrame { |
/* |
** RBU handle. |
+** |
+** nPhaseOneStep: |
+** If the RBU database contains an rbu_count table, this value is set to |
+** a running estimate of the number of b-tree operations required to |
+** finish populating the *-oal file. This allows the sqlite3_bp_progress() |
+** API to calculate the permyriadage progress of populating the *-oal file |
+** using the formula: |
+** |
+** permyriadage = (10000 * nProgress) / nPhaseOneStep |
+** |
+** nPhaseOneStep is initialized to the sum of: |
+** |
+** nRow * (nIndex + 1) |
+** |
+** for all source tables in the RBU database, where nRow is the number |
+** of rows in the source table and nIndex the number of indexes on the |
+** corresponding target database table. |
+** |
+** This estimate is accurate if the RBU update consists entirely of |
+** INSERT operations. However, it is inaccurate if: |
+** |
+** * the RBU update contains any UPDATE operations. If the PK specified |
+** for an UPDATE operation does not exist in the target table, then |
+** no b-tree operations are required on index b-trees. Or if the |
+** specified PK does exist, then (nIndex*2) such operations are |
+** required (one delete and one insert on each index b-tree). |
+** |
+** * the RBU update contains any DELETE operations for which the specified |
+** PK does not exist. In this case no operations are required on index |
+** b-trees. |
+** |
+** * the RBU update contains REPLACE operations. These are similar to |
+** UPDATE operations. |
+** |
+** nPhaseOneStep is updated to account for the conditions above during the |
+** first pass of each source table. The updated nPhaseOneStep value is |
+** stored in the rbu_state table if the RBU update is suspended. |
*/ |
struct sqlite3rbu { |
int eStage; /* Value of RBU_STATE_STAGE field */ |
@@ -313,6 +357,7 @@ struct sqlite3rbu { |
const char *zVfsName; /* Name of automatically created rbu vfs */ |
rbu_file *pTargetFd; /* File handle open on target db */ |
i64 iOalSz; |
+ i64 nPhaseOneStep; |
/* The following state variables are used as part of the incremental |
** checkpoint stage (eStage==RBU_STAGE_CKPT). See comments surrounding |
@@ -325,6 +370,10 @@ struct sqlite3rbu { |
int pgsz; |
u8 *aBuf; |
i64 iWalCksum; |
+ |
+ /* Used in RBU vacuum mode only */ |
+ int nRbu; /* Number of RBU VFS in the stack */ |
+ rbu_file *pRbuFd; /* Fd for main db of dbRbu */ |
}; |
/* |
@@ -350,6 +399,7 @@ struct rbu_file { |
int openFlags; /* Flags this file was opened with */ |
u32 iCookie; /* Cookie value for main db files */ |
u8 iWriteVer; /* "write-version" value for main db files */ |
+ u8 bNolock; /* True to fail EXCLUSIVE locks */ |
int nShm; /* Number of entries in apShm[] array */ |
char **apShm; /* Array of mmap'd *-shm regions */ |
@@ -360,6 +410,11 @@ struct rbu_file { |
rbu_file *pMainNext; /* Next MAIN_DB file */ |
}; |
+/* |
+** True for an RBU vacuum handle, or false otherwise. |
+*/ |
+#define rbuIsVacuum(p) ((p)->zTarget==0) |
+ |
/************************************************************************* |
** The following three functions, found below: |
@@ -808,8 +863,11 @@ static int rbuObjIterNext(sqlite3rbu *p, RbuObjIter *pIter){ |
/* |
** The implementation of the rbu_target_name() SQL function. This function |
-** accepts one argument - the name of a table in the RBU database. If the |
-** table name matches the pattern: |
+** accepts one or two arguments. The first argument is the name of a table - |
+** the name of a table in the RBU database. The second, if it is present, is 1 |
+** for a view or 0 for a table. |
+** |
+** For a non-vacuum RBU handle, if the table name matches the pattern: |
** |
** data[0-9]_<name> |
** |
@@ -820,21 +878,33 @@ static int rbuObjIterNext(sqlite3rbu *p, RbuObjIter *pIter){ |
** "data_t1" -> "t1" |
** "data0123_t2" -> "t2" |
** "dataAB_t3" -> NULL |
+** |
+** For an rbu vacuum handle, a copy of the first argument is returned if |
+** the second argument is either missing or 0 (not a view). |
*/ |
static void rbuTargetNameFunc( |
- sqlite3_context *context, |
+ sqlite3_context *pCtx, |
int argc, |
sqlite3_value **argv |
){ |
+ sqlite3rbu *p = sqlite3_user_data(pCtx); |
const char *zIn; |
- assert( argc==1 ); |
+ assert( argc==1 || argc==2 ); |
zIn = (const char*)sqlite3_value_text(argv[0]); |
- if( zIn && strlen(zIn)>4 && memcmp("data", zIn, 4)==0 ){ |
- int i; |
- for(i=4; zIn[i]>='0' && zIn[i]<='9'; i++); |
- if( zIn[i]=='_' && zIn[i+1] ){ |
- sqlite3_result_text(context, &zIn[i+1], -1, SQLITE_STATIC); |
+ if( zIn ){ |
+ if( rbuIsVacuum(p) ){ |
+ if( argc==1 || 0==sqlite3_value_int(argv[1]) ){ |
+ sqlite3_result_text(pCtx, zIn, -1, SQLITE_STATIC); |
+ } |
+ }else{ |
+ if( strlen(zIn)>4 && memcmp("data", zIn, 4)==0 ){ |
+ int i; |
+ for(i=4; zIn[i]>='0' && zIn[i]<='9'; i++); |
+ if( zIn[i]=='_' && zIn[i+1] ){ |
+ sqlite3_result_text(pCtx, &zIn[i+1], -1, SQLITE_STATIC); |
+ } |
+ } |
} |
} |
} |
@@ -851,11 +921,14 @@ static int rbuObjIterFirst(sqlite3rbu *p, RbuObjIter *pIter){ |
int rc; |
memset(pIter, 0, sizeof(RbuObjIter)); |
- rc = prepareAndCollectError(p->dbRbu, &pIter->pTblIter, &p->zErrmsg, |
- "SELECT rbu_target_name(name) AS target, name FROM sqlite_master " |
+ rc = prepareFreeAndCollectError(p->dbRbu, &pIter->pTblIter, &p->zErrmsg, |
+ sqlite3_mprintf( |
+ "SELECT rbu_target_name(name, type='view') AS target, name " |
+ "FROM sqlite_master " |
"WHERE type IN ('table', 'view') AND target IS NOT NULL " |
+ " %s " |
"ORDER BY name" |
- ); |
+ , rbuIsVacuum(p) ? "AND rootpage!=0 AND rootpage IS NOT NULL" : "")); |
if( rc==SQLITE_OK ){ |
rc = prepareAndCollectError(p->dbMain, &pIter->pIdxIter, &p->zErrmsg, |
@@ -935,7 +1008,7 @@ static void *rbuMalloc(sqlite3rbu *p, int nByte){ |
void *pRet = 0; |
if( p->rc==SQLITE_OK ){ |
assert( nByte>0 ); |
- pRet = sqlite3_malloc(nByte); |
+ pRet = sqlite3_malloc64(nByte); |
if( pRet==0 ){ |
p->rc = SQLITE_NOMEM; |
}else{ |
@@ -981,8 +1054,8 @@ static char *rbuStrndup(const char *zStr, int *pRc){ |
assert( *pRc==SQLITE_OK ); |
if( zStr ){ |
- int nCopy = strlen(zStr) + 1; |
- zRet = (char*)sqlite3_malloc(nCopy); |
+ size_t nCopy = strlen(zStr) + 1; |
+ zRet = (char*)sqlite3_malloc64(nCopy); |
if( zRet ){ |
memcpy(zRet, zStr, nCopy); |
}else{ |
@@ -1143,6 +1216,7 @@ static void rbuObjIterCacheIndexedCols(sqlite3rbu *p, RbuObjIter *pIter){ |
); |
} |
+ pIter->nIndex = 0; |
while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pList) ){ |
const char *zIdx = (const char*)sqlite3_column_text(pList, 1); |
sqlite3_stmt *pXInfo = 0; |
@@ -1156,6 +1230,12 @@ static void rbuObjIterCacheIndexedCols(sqlite3rbu *p, RbuObjIter *pIter){ |
} |
rbuFinalize(p, pXInfo); |
bIndex = 1; |
+ pIter->nIndex++; |
+ } |
+ |
+ if( pIter->eType==RBU_PK_WITHOUT_ROWID ){ |
+ /* "PRAGMA index_list" includes the main PK b-tree */ |
+ pIter->nIndex--; |
} |
rbuFinalize(p, pList); |
@@ -1221,6 +1301,7 @@ static int rbuObjIterCacheTableInfo(sqlite3rbu *p, RbuObjIter *pIter){ |
pStmt = 0; |
if( p->rc==SQLITE_OK |
+ && rbuIsVacuum(p)==0 |
&& bRbuRowid!=(pIter->eType==RBU_PK_VTAB || pIter->eType==RBU_PK_NONE) |
){ |
p->rc = SQLITE_ERROR; |
@@ -1269,6 +1350,7 @@ static int rbuObjIterCacheTableInfo(sqlite3rbu *p, RbuObjIter *pIter){ |
rbuFinalize(p, pStmt); |
rbuObjIterCacheIndexedCols(p, pIter); |
assert( pIter->eType!=RBU_PK_VTAB || pIter->abIndexed==0 ); |
+ assert( pIter->eType!=RBU_PK_VTAB || pIter->nIndex==0 ); |
} |
return p->rc; |
@@ -1359,6 +1441,8 @@ static char *rbuObjIterGetIndexCols( |
for(i=0; pIter->abTblPk[i]==0; i++); |
assert( i<pIter->nTblCol ); |
zCol = pIter->azTblCol[i]; |
+ }else if( rbuIsVacuum(p) ){ |
+ zCol = "_rowid_"; |
}else{ |
zCol = "rbu_rowid"; |
} |
@@ -1822,6 +1906,14 @@ static void rbuTmpInsertFunc( |
int rc = SQLITE_OK; |
int i; |
+ assert( sqlite3_value_int(apVal[0])!=0 |
+ || p->objiter.eType==RBU_PK_EXTERNAL |
+ || p->objiter.eType==RBU_PK_NONE |
+ ); |
+ if( sqlite3_value_int(apVal[0])!=0 ){ |
+ p->nPhaseOneStep += p->objiter.nIndex; |
+ } |
+ |
for(i=0; rc==SQLITE_OK && i<nVal; i++){ |
rc = sqlite3_bind_value(p->objiter.pTmpInsert, i+1, apVal[i]); |
} |
@@ -1891,7 +1983,7 @@ static int rbuObjIterPrepareAll( |
} |
/* And to delete index entries */ |
- if( p->rc==SQLITE_OK ){ |
+ if( rbuIsVacuum(p)==0 && p->rc==SQLITE_OK ){ |
p->rc = prepareFreeAndCollectError( |
p->dbMain, &pIter->pDelete, &p->zErrmsg, |
sqlite3_mprintf("DELETE FROM \"rbu_imp_%w\" WHERE %s", zTbl, zWhere) |
@@ -1901,6 +1993,15 @@ static int rbuObjIterPrepareAll( |
/* Create the SELECT statement to read keys in sorted order */ |
if( p->rc==SQLITE_OK ){ |
char *zSql; |
+ if( rbuIsVacuum(p) ){ |
+ zSql = sqlite3_mprintf( |
+ "SELECT %s, 0 AS rbu_control FROM '%q' ORDER BY %s%s", |
+ zCollist, |
+ pIter->zDataTbl, |
+ zCollist, zLimit |
+ ); |
+ }else |
+ |
if( pIter->eType==RBU_PK_EXTERNAL || pIter->eType==RBU_PK_NONE ){ |
zSql = sqlite3_mprintf( |
"SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' ORDER BY %s%s", |
@@ -1909,13 +2010,13 @@ static int rbuObjIterPrepareAll( |
); |
}else{ |
zSql = sqlite3_mprintf( |
+ "SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' " |
+ "UNION ALL " |
"SELECT %s, rbu_control FROM '%q' " |
"WHERE typeof(rbu_control)='integer' AND rbu_control!=1 " |
- "UNION ALL " |
- "SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' " |
"ORDER BY %s%s", |
- zCollist, pIter->zDataTbl, |
zCollist, p->zStateDb, pIter->zDataTbl, |
+ zCollist, pIter->zDataTbl, |
zCollist, zLimit |
); |
} |
@@ -1927,7 +2028,9 @@ static int rbuObjIterPrepareAll( |
sqlite3_free(zWhere); |
sqlite3_free(zBind); |
}else{ |
- int bRbuRowid = (pIter->eType==RBU_PK_VTAB || pIter->eType==RBU_PK_NONE); |
+ int bRbuRowid = (pIter->eType==RBU_PK_VTAB) |
+ ||(pIter->eType==RBU_PK_NONE) |
+ ||(pIter->eType==RBU_PK_EXTERNAL && rbuIsVacuum(p)); |
const char *zTbl = pIter->zTbl; /* Table this step applies to */ |
const char *zWrite; /* Imposter table name */ |
@@ -1954,8 +2057,10 @@ static int rbuObjIterPrepareAll( |
); |
} |
- /* Create the DELETE statement to write to the target PK b-tree */ |
- if( p->rc==SQLITE_OK ){ |
+ /* Create the DELETE statement to write to the target PK b-tree. |
+ ** Because it only performs INSERT operations, this is not required for |
+ ** an rbu vacuum handle. */ |
+ if( rbuIsVacuum(p)==0 && p->rc==SQLITE_OK ){ |
p->rc = prepareFreeAndCollectError(p->dbMain, &pIter->pDelete, pz, |
sqlite3_mprintf( |
"DELETE FROM \"%s%w\" WHERE %s", zWrite, zTbl, zWhere |
@@ -1963,7 +2068,7 @@ static int rbuObjIterPrepareAll( |
); |
} |
- if( pIter->abIndexed ){ |
+ if( rbuIsVacuum(p)==0 && pIter->abIndexed ){ |
const char *zRbuRowid = ""; |
if( pIter->eType==RBU_PK_EXTERNAL || pIter->eType==RBU_PK_NONE ){ |
zRbuRowid = ", rbu_rowid"; |
@@ -1981,17 +2086,17 @@ static int rbuObjIterPrepareAll( |
rbuMPrintfExec(p, p->dbMain, |
"CREATE TEMP TRIGGER rbu_delete_tr BEFORE DELETE ON \"%s%w\" " |
"BEGIN " |
- " SELECT rbu_tmp_insert(2, %s);" |
+ " SELECT rbu_tmp_insert(3, %s);" |
"END;" |
"CREATE TEMP TRIGGER rbu_update1_tr BEFORE UPDATE ON \"%s%w\" " |
"BEGIN " |
- " SELECT rbu_tmp_insert(2, %s);" |
+ " SELECT rbu_tmp_insert(3, %s);" |
"END;" |
"CREATE TEMP TRIGGER rbu_update2_tr AFTER UPDATE ON \"%s%w\" " |
"BEGIN " |
- " SELECT rbu_tmp_insert(3, %s);" |
+ " SELECT rbu_tmp_insert(4, %s);" |
"END;", |
zWrite, zTbl, zOldlist, |
zWrite, zTbl, zOldlist, |
@@ -2013,10 +2118,16 @@ static int rbuObjIterPrepareAll( |
/* Create the SELECT statement to read keys from data_xxx */ |
if( p->rc==SQLITE_OK ){ |
+ const char *zRbuRowid = ""; |
+ if( bRbuRowid ){ |
+ zRbuRowid = rbuIsVacuum(p) ? ",_rowid_ " : ",rbu_rowid"; |
+ } |
p->rc = prepareFreeAndCollectError(p->dbRbu, &pIter->pSelect, pz, |
sqlite3_mprintf( |
- "SELECT %s, rbu_control%s FROM '%q'%s", |
- zCollist, (bRbuRowid ? ", rbu_rowid" : ""), |
+ "SELECT %s,%s rbu_control%s FROM '%q'%s", |
+ zCollist, |
+ (rbuIsVacuum(p) ? "0 AS " : ""), |
+ zRbuRowid, |
pIter->zDataTbl, zLimit |
) |
); |
@@ -2111,11 +2222,15 @@ static int rbuGetUpdateStmt( |
return p->rc; |
} |
-static sqlite3 *rbuOpenDbhandle(sqlite3rbu *p, const char *zName){ |
+static sqlite3 *rbuOpenDbhandle( |
+ sqlite3rbu *p, |
+ const char *zName, |
+ int bUseVfs |
+){ |
sqlite3 *db = 0; |
if( p->rc==SQLITE_OK ){ |
const int flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_URI; |
- p->rc = sqlite3_open_v2(zName, &db, flags, p->zVfsName); |
+ p->rc = sqlite3_open_v2(zName, &db, flags, bUseVfs ? p->zVfsName : 0); |
if( p->rc ){ |
p->zErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(db)); |
sqlite3_close(db); |
@@ -2126,16 +2241,112 @@ static sqlite3 *rbuOpenDbhandle(sqlite3rbu *p, const char *zName){ |
} |
/* |
+** Free an RbuState object allocated by rbuLoadState(). |
+*/ |
+static void rbuFreeState(RbuState *p){ |
+ if( p ){ |
+ sqlite3_free(p->zTbl); |
+ sqlite3_free(p->zIdx); |
+ sqlite3_free(p); |
+ } |
+} |
+ |
+/* |
+** Allocate an RbuState object and load the contents of the rbu_state |
+** table into it. Return a pointer to the new object. It is the |
+** responsibility of the caller to eventually free the object using |
+** sqlite3_free(). |
+** |
+** If an error occurs, leave an error code and message in the rbu handle |
+** and return NULL. |
+*/ |
+static RbuState *rbuLoadState(sqlite3rbu *p){ |
+ RbuState *pRet = 0; |
+ sqlite3_stmt *pStmt = 0; |
+ int rc; |
+ int rc2; |
+ |
+ pRet = (RbuState*)rbuMalloc(p, sizeof(RbuState)); |
+ if( pRet==0 ) return 0; |
+ |
+ rc = prepareFreeAndCollectError(p->dbRbu, &pStmt, &p->zErrmsg, |
+ sqlite3_mprintf("SELECT k, v FROM %s.rbu_state", p->zStateDb) |
+ ); |
+ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ |
+ switch( sqlite3_column_int(pStmt, 0) ){ |
+ case RBU_STATE_STAGE: |
+ pRet->eStage = sqlite3_column_int(pStmt, 1); |
+ if( pRet->eStage!=RBU_STAGE_OAL |
+ && pRet->eStage!=RBU_STAGE_MOVE |
+ && pRet->eStage!=RBU_STAGE_CKPT |
+ ){ |
+ p->rc = SQLITE_CORRUPT; |
+ } |
+ break; |
+ |
+ case RBU_STATE_TBL: |
+ pRet->zTbl = rbuStrndup((char*)sqlite3_column_text(pStmt, 1), &rc); |
+ break; |
+ |
+ case RBU_STATE_IDX: |
+ pRet->zIdx = rbuStrndup((char*)sqlite3_column_text(pStmt, 1), &rc); |
+ break; |
+ |
+ case RBU_STATE_ROW: |
+ pRet->nRow = sqlite3_column_int(pStmt, 1); |
+ break; |
+ |
+ case RBU_STATE_PROGRESS: |
+ pRet->nProgress = sqlite3_column_int64(pStmt, 1); |
+ break; |
+ |
+ case RBU_STATE_CKPT: |
+ pRet->iWalCksum = sqlite3_column_int64(pStmt, 1); |
+ break; |
+ |
+ case RBU_STATE_COOKIE: |
+ pRet->iCookie = (u32)sqlite3_column_int64(pStmt, 1); |
+ break; |
+ |
+ case RBU_STATE_OALSZ: |
+ pRet->iOalSz = (u32)sqlite3_column_int64(pStmt, 1); |
+ break; |
+ |
+ case RBU_STATE_PHASEONESTEP: |
+ pRet->nPhaseOneStep = sqlite3_column_int64(pStmt, 1); |
+ break; |
+ |
+ default: |
+ rc = SQLITE_CORRUPT; |
+ break; |
+ } |
+ } |
+ rc2 = sqlite3_finalize(pStmt); |
+ if( rc==SQLITE_OK ) rc = rc2; |
+ |
+ p->rc = rc; |
+ return pRet; |
+} |
+ |
+ |
+/* |
** Open the database handle and attach the RBU database as "rbu". If an |
** error occurs, leave an error code and message in the RBU handle. |
*/ |
-static void rbuOpenDatabase(sqlite3rbu *p){ |
- assert( p->rc==SQLITE_OK ); |
- assert( p->dbMain==0 && p->dbRbu==0 ); |
- |
- p->eStage = 0; |
- p->dbMain = rbuOpenDbhandle(p, p->zTarget); |
- p->dbRbu = rbuOpenDbhandle(p, p->zRbu); |
+static void rbuOpenDatabase(sqlite3rbu *p, int *pbRetry){ |
+ assert( p->rc || (p->dbMain==0 && p->dbRbu==0) ); |
+ assert( p->rc || rbuIsVacuum(p) || p->zTarget!=0 ); |
+ |
+ /* Open the RBU database */ |
+ p->dbRbu = rbuOpenDbhandle(p, p->zRbu, 1); |
+ |
+ if( p->rc==SQLITE_OK && rbuIsVacuum(p) ){ |
+ sqlite3_file_control(p->dbRbu, "main", SQLITE_FCNTL_RBUCNT, (void*)p); |
+ if( p->zState==0 ){ |
+ const char *zFile = sqlite3_db_filename(p->dbRbu, "main"); |
+ p->zState = rbuMPrintf(p, "file://%s-vacuum?modeof=%s", zFile, zFile); |
+ } |
+ } |
/* If using separate RBU and state databases, attach the state database to |
** the RBU db handle now. */ |
@@ -2146,6 +2357,105 @@ static void rbuOpenDatabase(sqlite3rbu *p){ |
memcpy(p->zStateDb, "main", 4); |
} |
+#if 0 |
+ if( p->rc==SQLITE_OK && rbuIsVacuum(p) ){ |
+ p->rc = sqlite3_exec(p->dbRbu, "BEGIN", 0, 0, 0); |
+ } |
+#endif |
+ |
+ /* If it has not already been created, create the rbu_state table */ |
+ rbuMPrintfExec(p, p->dbRbu, RBU_CREATE_STATE, p->zStateDb); |
+ |
+#if 0 |
+ if( rbuIsVacuum(p) ){ |
+ if( p->rc==SQLITE_OK ){ |
+ int rc2; |
+ int bOk = 0; |
+ sqlite3_stmt *pCnt = 0; |
+ p->rc = prepareAndCollectError(p->dbRbu, &pCnt, &p->zErrmsg, |
+ "SELECT count(*) FROM stat.sqlite_master" |
+ ); |
+ if( p->rc==SQLITE_OK |
+ && sqlite3_step(pCnt)==SQLITE_ROW |
+ && 1==sqlite3_column_int(pCnt, 0) |
+ ){ |
+ bOk = 1; |
+ } |
+ rc2 = sqlite3_finalize(pCnt); |
+ if( p->rc==SQLITE_OK ) p->rc = rc2; |
+ |
+ if( p->rc==SQLITE_OK && bOk==0 ){ |
+ p->rc = SQLITE_ERROR; |
+ p->zErrmsg = sqlite3_mprintf("invalid state database"); |
+ } |
+ |
+ if( p->rc==SQLITE_OK ){ |
+ p->rc = sqlite3_exec(p->dbRbu, "COMMIT", 0, 0, 0); |
+ } |
+ } |
+ } |
+#endif |
+ |
+ if( p->rc==SQLITE_OK && rbuIsVacuum(p) ){ |
+ int bOpen = 0; |
+ int rc; |
+ p->nRbu = 0; |
+ p->pRbuFd = 0; |
+ rc = sqlite3_file_control(p->dbRbu, "main", SQLITE_FCNTL_RBUCNT, (void*)p); |
+ if( rc!=SQLITE_NOTFOUND ) p->rc = rc; |
+ if( p->eStage>=RBU_STAGE_MOVE ){ |
+ bOpen = 1; |
+ }else{ |
+ RbuState *pState = rbuLoadState(p); |
+ if( pState ){ |
+ bOpen = (pState->eStage>=RBU_STAGE_MOVE); |
+ rbuFreeState(pState); |
+ } |
+ } |
+ if( bOpen ) p->dbMain = rbuOpenDbhandle(p, p->zRbu, p->nRbu<=1); |
+ } |
+ |
+ p->eStage = 0; |
+ if( p->rc==SQLITE_OK && p->dbMain==0 ){ |
+ if( !rbuIsVacuum(p) ){ |
+ p->dbMain = rbuOpenDbhandle(p, p->zTarget, 1); |
+ }else if( p->pRbuFd->pWalFd ){ |
+ if( pbRetry ){ |
+ p->pRbuFd->bNolock = 0; |
+ sqlite3_close(p->dbRbu); |
+ sqlite3_close(p->dbMain); |
+ p->dbMain = 0; |
+ p->dbRbu = 0; |
+ *pbRetry = 1; |
+ return; |
+ } |
+ p->rc = SQLITE_ERROR; |
+ p->zErrmsg = sqlite3_mprintf("cannot vacuum wal mode database"); |
+ }else{ |
+ char *zTarget; |
+ char *zExtra = 0; |
+ if( strlen(p->zRbu)>=5 && 0==memcmp("file:", p->zRbu, 5) ){ |
+ zExtra = &p->zRbu[5]; |
+ while( *zExtra ){ |
+ if( *zExtra++=='?' ) break; |
+ } |
+ if( *zExtra=='\0' ) zExtra = 0; |
+ } |
+ |
+ zTarget = sqlite3_mprintf("file:%s-vacuum?rbu_memory=1%s%s", |
+ sqlite3_db_filename(p->dbRbu, "main"), |
+ (zExtra==0 ? "" : "&"), (zExtra==0 ? "" : zExtra) |
+ ); |
+ |
+ if( zTarget==0 ){ |
+ p->rc = SQLITE_NOMEM; |
+ return; |
+ } |
+ p->dbMain = rbuOpenDbhandle(p, zTarget, p->nRbu<=1); |
+ sqlite3_free(zTarget); |
+ } |
+ } |
+ |
if( p->rc==SQLITE_OK ){ |
p->rc = sqlite3_create_function(p->dbMain, |
"rbu_tmp_insert", -1, SQLITE_UTF8, (void*)p, rbuTmpInsertFunc, 0, 0 |
@@ -2160,7 +2470,7 @@ static void rbuOpenDatabase(sqlite3rbu *p){ |
if( p->rc==SQLITE_OK ){ |
p->rc = sqlite3_create_function(p->dbRbu, |
- "rbu_target_name", 1, SQLITE_UTF8, (void*)p, rbuTargetNameFunc, 0, 0 |
+ "rbu_target_name", -1, SQLITE_UTF8, (void*)p, rbuTargetNameFunc, 0, 0 |
); |
} |
@@ -2209,9 +2519,9 @@ static void rbuFileSuffix3(const char *zBase, char *z){ |
#endif |
{ |
int i, sz; |
- sz = sqlite3Strlen30(z); |
+ sz = (int)strlen(z)&0xffffff; |
for(i=sz-1; i>0 && z[i]!='/' && z[i]!='.'; i--){} |
- if( z[i]=='.' && ALWAYS(sz>i+4) ) memmove(&z[i+1], &z[sz-3], 4); |
+ if( z[i]=='.' && sz>i+4 ) memmove(&z[i+1], &z[sz-3], 4); |
} |
#endif |
} |
@@ -2299,16 +2609,18 @@ static void rbuSetupCheckpoint(sqlite3rbu *p, RbuState *pState){ |
if( rc2!=SQLITE_INTERNAL ) p->rc = rc2; |
} |
- if( p->rc==SQLITE_OK ){ |
+ if( p->rc==SQLITE_OK && p->nFrame>0 ){ |
p->eStage = RBU_STAGE_CKPT; |
p->nStep = (pState ? pState->nRow : 0); |
p->aBuf = rbuMalloc(p, p->pgsz); |
p->iWalCksum = rbuShmChecksum(p); |
} |
- if( p->rc==SQLITE_OK && pState && pState->iWalCksum!=p->iWalCksum ){ |
- p->rc = SQLITE_DONE; |
- p->eStage = RBU_STAGE_DONE; |
+ if( p->rc==SQLITE_OK ){ |
+ if( p->nFrame==0 || (pState && pState->iWalCksum!=p->iWalCksum) ){ |
+ p->rc = SQLITE_DONE; |
+ p->eStage = RBU_STAGE_DONE; |
+ } |
} |
} |
@@ -2330,7 +2642,7 @@ static int rbuCaptureWalRead(sqlite3rbu *pRbu, i64 iOff, int iAmt){ |
if( pRbu->nFrame==pRbu->nFrameAlloc ){ |
int nNew = (pRbu->nFrameAlloc ? pRbu->nFrameAlloc : 64) * 2; |
RbuFrame *aNew; |
- aNew = (RbuFrame*)sqlite3_realloc(pRbu->aFrame, nNew * sizeof(RbuFrame)); |
+ aNew = (RbuFrame*)sqlite3_realloc64(pRbu->aFrame, nNew * sizeof(RbuFrame)); |
if( aNew==0 ) return SQLITE_NOMEM; |
pRbu->aFrame = aNew; |
pRbu->nFrameAlloc = nNew; |
@@ -2395,7 +2707,7 @@ static LPWSTR rbuWinUtf8ToUnicode(const char *zFilename){ |
if( nChar==0 ){ |
return 0; |
} |
- zWideFilename = sqlite3_malloc( nChar*sizeof(zWideFilename[0]) ); |
+ zWideFilename = sqlite3_malloc64( nChar*sizeof(zWideFilename[0]) ); |
if( zWideFilename==0 ){ |
return 0; |
} |
@@ -2419,9 +2731,15 @@ static LPWSTR rbuWinUtf8ToUnicode(const char *zFilename){ |
*/ |
static void rbuMoveOalFile(sqlite3rbu *p){ |
const char *zBase = sqlite3_db_filename(p->dbMain, "main"); |
+ const char *zMove = zBase; |
+ char *zOal; |
+ char *zWal; |
- char *zWal = sqlite3_mprintf("%s-wal", zBase); |
- char *zOal = sqlite3_mprintf("%s-oal", zBase); |
+ if( rbuIsVacuum(p) ){ |
+ zMove = sqlite3_db_filename(p->dbRbu, "main"); |
+ } |
+ zOal = sqlite3_mprintf("%s-oal", zMove); |
+ zWal = sqlite3_mprintf("%s-wal", zMove); |
assert( p->eStage==RBU_STAGE_MOVE ); |
assert( p->rc==SQLITE_OK && p->zErrmsg==0 ); |
@@ -2442,8 +2760,8 @@ static void rbuMoveOalFile(sqlite3rbu *p){ |
/* Re-open the databases. */ |
rbuObjIterFinalize(&p->objiter); |
- sqlite3_close(p->dbMain); |
sqlite3_close(p->dbRbu); |
+ sqlite3_close(p->dbMain); |
p->dbMain = 0; |
p->dbRbu = 0; |
@@ -2475,7 +2793,7 @@ static void rbuMoveOalFile(sqlite3rbu *p){ |
#endif |
if( p->rc==SQLITE_OK ){ |
- rbuOpenDatabase(p); |
+ rbuOpenDatabase(p, 0); |
rbuSetupCheckpoint(p, 0); |
} |
} |
@@ -2509,14 +2827,12 @@ static int rbuStepType(sqlite3rbu *p, const char **pzMask){ |
switch( sqlite3_column_type(p->objiter.pSelect, iCol) ){ |
case SQLITE_INTEGER: { |
int iVal = sqlite3_column_int(p->objiter.pSelect, iCol); |
- if( iVal==0 ){ |
- res = RBU_INSERT; |
- }else if( iVal==1 ){ |
- res = RBU_DELETE; |
- }else if( iVal==2 ){ |
- res = RBU_IDX_DELETE; |
- }else if( iVal==3 ){ |
- res = RBU_IDX_INSERT; |
+ switch( iVal ){ |
+ case 0: res = RBU_INSERT; break; |
+ case 1: res = RBU_DELETE; break; |
+ case 2: res = RBU_REPLACE; break; |
+ case 3: res = RBU_IDX_DELETE; break; |
+ case 4: res = RBU_IDX_INSERT; break; |
} |
break; |
} |
@@ -2556,6 +2872,83 @@ static void assertColumnName(sqlite3_stmt *pStmt, int iCol, const char *zName){ |
#endif |
/* |
+** Argument eType must be one of RBU_INSERT, RBU_DELETE, RBU_IDX_INSERT or |
+** RBU_IDX_DELETE. This function performs the work of a single |
+** sqlite3rbu_step() call for the type of operation specified by eType. |
+*/ |
+static void rbuStepOneOp(sqlite3rbu *p, int eType){ |
+ RbuObjIter *pIter = &p->objiter; |
+ sqlite3_value *pVal; |
+ sqlite3_stmt *pWriter; |
+ int i; |
+ |
+ assert( p->rc==SQLITE_OK ); |
+ assert( eType!=RBU_DELETE || pIter->zIdx==0 ); |
+ assert( eType==RBU_DELETE || eType==RBU_IDX_DELETE |
+ || eType==RBU_INSERT || eType==RBU_IDX_INSERT |
+ ); |
+ |
+ /* If this is a delete, decrement nPhaseOneStep by nIndex. If the DELETE |
+ ** statement below does actually delete a row, nPhaseOneStep will be |
+ ** incremented by the same amount when SQL function rbu_tmp_insert() |
+ ** is invoked by the trigger. */ |
+ if( eType==RBU_DELETE ){ |
+ p->nPhaseOneStep -= p->objiter.nIndex; |
+ } |
+ |
+ if( eType==RBU_IDX_DELETE || eType==RBU_DELETE ){ |
+ pWriter = pIter->pDelete; |
+ }else{ |
+ pWriter = pIter->pInsert; |
+ } |
+ |
+ for(i=0; i<pIter->nCol; i++){ |
+ /* If this is an INSERT into a table b-tree and the table has an |
+ ** explicit INTEGER PRIMARY KEY, check that this is not an attempt |
+ ** to write a NULL into the IPK column. That is not permitted. */ |
+ if( eType==RBU_INSERT |
+ && pIter->zIdx==0 && pIter->eType==RBU_PK_IPK && pIter->abTblPk[i] |
+ && sqlite3_column_type(pIter->pSelect, i)==SQLITE_NULL |
+ ){ |
+ p->rc = SQLITE_MISMATCH; |
+ p->zErrmsg = sqlite3_mprintf("datatype mismatch"); |
+ return; |
+ } |
+ |
+ if( eType==RBU_DELETE && pIter->abTblPk[i]==0 ){ |
+ continue; |
+ } |
+ |
+ pVal = sqlite3_column_value(pIter->pSelect, i); |
+ p->rc = sqlite3_bind_value(pWriter, i+1, pVal); |
+ if( p->rc ) return; |
+ } |
+ if( pIter->zIdx==0 ){ |
+ if( pIter->eType==RBU_PK_VTAB |
+ || pIter->eType==RBU_PK_NONE |
+ || (pIter->eType==RBU_PK_EXTERNAL && rbuIsVacuum(p)) |
+ ){ |
+ /* For a virtual table, or a table with no primary key, the |
+ ** SELECT statement is: |
+ ** |
+ ** SELECT <cols>, rbu_control, rbu_rowid FROM .... |
+ ** |
+ ** Hence column_value(pIter->nCol+1). |
+ */ |
+ assertColumnName(pIter->pSelect, pIter->nCol+1, |
+ rbuIsVacuum(p) ? "rowid" : "rbu_rowid" |
+ ); |
+ pVal = sqlite3_column_value(pIter->pSelect, pIter->nCol+1); |
+ p->rc = sqlite3_bind_value(pWriter, pIter->nCol+1, pVal); |
+ } |
+ } |
+ if( p->rc==SQLITE_OK ){ |
+ sqlite3_step(pWriter); |
+ p->rc = resetAndCollectError(pWriter, &p->zErrmsg); |
+ } |
+} |
+ |
+/* |
** This function does the work for an sqlite3rbu_step() call. |
** |
** The object-iterator (p->objiter) currently points to a valid object, |
@@ -2569,78 +2962,36 @@ static void assertColumnName(sqlite3_stmt *pStmt, int iCol, const char *zName){ |
static int rbuStep(sqlite3rbu *p){ |
RbuObjIter *pIter = &p->objiter; |
const char *zMask = 0; |
- int i; |
int eType = rbuStepType(p, &zMask); |
if( eType ){ |
+ assert( eType==RBU_INSERT || eType==RBU_DELETE |
+ || eType==RBU_REPLACE || eType==RBU_IDX_DELETE |
+ || eType==RBU_IDX_INSERT || eType==RBU_UPDATE |
+ ); |
assert( eType!=RBU_UPDATE || pIter->zIdx==0 ); |
- if( pIter->zIdx==0 && eType==RBU_IDX_DELETE ){ |
+ if( pIter->zIdx==0 && (eType==RBU_IDX_DELETE || eType==RBU_IDX_INSERT) ){ |
rbuBadControlError(p); |
} |
- else if( |
- eType==RBU_INSERT |
- || eType==RBU_DELETE |
- || eType==RBU_IDX_DELETE |
- || eType==RBU_IDX_INSERT |
- ){ |
- sqlite3_value *pVal; |
- sqlite3_stmt *pWriter; |
- |
- assert( eType!=RBU_UPDATE ); |
- assert( eType!=RBU_DELETE || pIter->zIdx==0 ); |
- |
- if( eType==RBU_IDX_DELETE || eType==RBU_DELETE ){ |
- pWriter = pIter->pDelete; |
- }else{ |
- pWriter = pIter->pInsert; |
- } |
- |
- for(i=0; i<pIter->nCol; i++){ |
- /* If this is an INSERT into a table b-tree and the table has an |
- ** explicit INTEGER PRIMARY KEY, check that this is not an attempt |
- ** to write a NULL into the IPK column. That is not permitted. */ |
- if( eType==RBU_INSERT |
- && pIter->zIdx==0 && pIter->eType==RBU_PK_IPK && pIter->abTblPk[i] |
- && sqlite3_column_type(pIter->pSelect, i)==SQLITE_NULL |
- ){ |
- p->rc = SQLITE_MISMATCH; |
- p->zErrmsg = sqlite3_mprintf("datatype mismatch"); |
- goto step_out; |
- } |
- |
- if( eType==RBU_DELETE && pIter->abTblPk[i]==0 ){ |
- continue; |
- } |
- |
- pVal = sqlite3_column_value(pIter->pSelect, i); |
- p->rc = sqlite3_bind_value(pWriter, i+1, pVal); |
- if( p->rc ) goto step_out; |
- } |
- if( pIter->zIdx==0 |
- && (pIter->eType==RBU_PK_VTAB || pIter->eType==RBU_PK_NONE) |
- ){ |
- /* For a virtual table, or a table with no primary key, the |
- ** SELECT statement is: |
- ** |
- ** SELECT <cols>, rbu_control, rbu_rowid FROM .... |
- ** |
- ** Hence column_value(pIter->nCol+1). |
- */ |
- assertColumnName(pIter->pSelect, pIter->nCol+1, "rbu_rowid"); |
- pVal = sqlite3_column_value(pIter->pSelect, pIter->nCol+1); |
- p->rc = sqlite3_bind_value(pWriter, pIter->nCol+1, pVal); |
- } |
- if( p->rc==SQLITE_OK ){ |
- sqlite3_step(pWriter); |
- p->rc = resetAndCollectError(pWriter, &p->zErrmsg); |
+ else if( eType==RBU_REPLACE ){ |
+ if( pIter->zIdx==0 ){ |
+ p->nPhaseOneStep += p->objiter.nIndex; |
+ rbuStepOneOp(p, RBU_DELETE); |
} |
- }else{ |
+ if( p->rc==SQLITE_OK ) rbuStepOneOp(p, RBU_INSERT); |
+ } |
+ else if( eType!=RBU_UPDATE ){ |
+ rbuStepOneOp(p, eType); |
+ } |
+ else{ |
sqlite3_value *pVal; |
sqlite3_stmt *pUpdate = 0; |
assert( eType==RBU_UPDATE ); |
+ p->nPhaseOneStep -= p->objiter.nIndex; |
rbuGetUpdateStmt(p, pIter, zMask, &pUpdate); |
if( pUpdate ){ |
+ int i; |
for(i=0; p->rc==SQLITE_OK && i<pIter->nCol; i++){ |
char c = zMask[pIter->aiSrcOrder[i]]; |
pVal = sqlite3_column_value(pIter->pSelect, i); |
@@ -2663,20 +3014,23 @@ static int rbuStep(sqlite3rbu *p){ |
} |
} |
} |
- |
- step_out: |
return p->rc; |
} |
/* |
** Increment the schema cookie of the main database opened by p->dbMain. |
+** |
+** Or, if this is an RBU vacuum, set the schema cookie of the main db |
+** opened by p->dbMain to one more than the schema cookie of the main |
+** db opened by p->dbRbu. |
*/ |
static void rbuIncrSchemaCookie(sqlite3rbu *p){ |
if( p->rc==SQLITE_OK ){ |
+ sqlite3 *dbread = (rbuIsVacuum(p) ? p->dbRbu : p->dbMain); |
int iCookie = 1000000; |
sqlite3_stmt *pStmt; |
- p->rc = prepareAndCollectError(p->dbMain, &pStmt, &p->zErrmsg, |
+ p->rc = prepareAndCollectError(dbread, &pStmt, &p->zErrmsg, |
"PRAGMA schema_version" |
); |
if( p->rc==SQLITE_OK ){ |
@@ -2704,6 +3058,7 @@ static void rbuIncrSchemaCookie(sqlite3rbu *p){ |
static void rbuSaveState(sqlite3rbu *p, int eStage){ |
if( p->rc==SQLITE_OK || p->rc==SQLITE_DONE ){ |
sqlite3_stmt *pInsert = 0; |
+ rbu_file *pFd = (rbuIsVacuum(p) ? p->pRbuFd : p->pTargetFd); |
int rc; |
assert( p->zErrmsg==0 ); |
@@ -2717,6 +3072,7 @@ static void rbuSaveState(sqlite3rbu *p, int eStage){ |
"(%d, %d), " |
"(%d, %lld), " |
"(%d, %lld), " |
+ "(%d, %lld), " |
"(%d, %lld) ", |
p->zStateDb, |
RBU_STATE_STAGE, eStage, |
@@ -2725,8 +3081,9 @@ static void rbuSaveState(sqlite3rbu *p, int eStage){ |
RBU_STATE_ROW, p->nStep, |
RBU_STATE_PROGRESS, p->nProgress, |
RBU_STATE_CKPT, p->iWalCksum, |
- RBU_STATE_COOKIE, (i64)p->pTargetFd->iCookie, |
- RBU_STATE_OALSZ, p->iOalSz |
+ RBU_STATE_COOKIE, (i64)pFd->iCookie, |
+ RBU_STATE_OALSZ, p->iOalSz, |
+ RBU_STATE_PHASEONESTEP, p->nPhaseOneStep |
) |
); |
assert( pInsert==0 || rc==SQLITE_OK ); |
@@ -2741,20 +3098,115 @@ static void rbuSaveState(sqlite3rbu *p, int eStage){ |
/* |
-** Step the RBU object. |
+** The second argument passed to this function is the name of a PRAGMA |
+** setting - "page_size", "auto_vacuum", "user_version" or "application_id". |
+** This function executes the following on sqlite3rbu.dbRbu: |
+** |
+** "PRAGMA main.$zPragma" |
+** |
+** where $zPragma is the string passed as the second argument, then |
+** on sqlite3rbu.dbMain: |
+** |
+** "PRAGMA main.$zPragma = $val" |
+** |
+** where $val is the value returned by the first PRAGMA invocation. |
+** |
+** In short, it copies the value of the specified PRAGMA setting from |
+** dbRbu to dbMain. |
*/ |
-int sqlite3rbu_step(sqlite3rbu *p){ |
- if( p ){ |
- switch( p->eStage ){ |
- case RBU_STAGE_OAL: { |
- RbuObjIter *pIter = &p->objiter; |
- while( p->rc==SQLITE_OK && pIter->zTbl ){ |
+static void rbuCopyPragma(sqlite3rbu *p, const char *zPragma){ |
+ if( p->rc==SQLITE_OK ){ |
+ sqlite3_stmt *pPragma = 0; |
+ p->rc = prepareFreeAndCollectError(p->dbRbu, &pPragma, &p->zErrmsg, |
+ sqlite3_mprintf("PRAGMA main.%s", zPragma) |
+ ); |
+ if( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPragma) ){ |
+ p->rc = rbuMPrintfExec(p, p->dbMain, "PRAGMA main.%s = %d", |
+ zPragma, sqlite3_column_int(pPragma, 0) |
+ ); |
+ } |
+ rbuFinalize(p, pPragma); |
+ } |
+} |
+ |
+/* |
+** The RBU handle passed as the only argument has just been opened and |
+** the state database is empty. If this RBU handle was opened for an |
+** RBU vacuum operation, create the schema in the target db. |
+*/ |
+static void rbuCreateTargetSchema(sqlite3rbu *p){ |
+ sqlite3_stmt *pSql = 0; |
+ sqlite3_stmt *pInsert = 0; |
+ |
+ assert( rbuIsVacuum(p) ); |
+ p->rc = sqlite3_exec(p->dbMain, "PRAGMA writable_schema=1", 0,0, &p->zErrmsg); |
+ if( p->rc==SQLITE_OK ){ |
+ p->rc = prepareAndCollectError(p->dbRbu, &pSql, &p->zErrmsg, |
+ "SELECT sql FROM sqlite_master WHERE sql!='' AND rootpage!=0" |
+ " AND name!='sqlite_sequence' " |
+ " ORDER BY type DESC" |
+ ); |
+ } |
+ |
+ while( p->rc==SQLITE_OK && sqlite3_step(pSql)==SQLITE_ROW ){ |
+ const char *zSql = (const char*)sqlite3_column_text(pSql, 0); |
+ p->rc = sqlite3_exec(p->dbMain, zSql, 0, 0, &p->zErrmsg); |
+ } |
+ rbuFinalize(p, pSql); |
+ if( p->rc!=SQLITE_OK ) return; |
+ |
+ if( p->rc==SQLITE_OK ){ |
+ p->rc = prepareAndCollectError(p->dbRbu, &pSql, &p->zErrmsg, |
+ "SELECT * FROM sqlite_master WHERE rootpage=0 OR rootpage IS NULL" |
+ ); |
+ } |
+ |
+ if( p->rc==SQLITE_OK ){ |
+ p->rc = prepareAndCollectError(p->dbMain, &pInsert, &p->zErrmsg, |
+ "INSERT INTO sqlite_master VALUES(?,?,?,?,?)" |
+ ); |
+ } |
+ |
+ while( p->rc==SQLITE_OK && sqlite3_step(pSql)==SQLITE_ROW ){ |
+ int i; |
+ for(i=0; i<5; i++){ |
+ sqlite3_bind_value(pInsert, i+1, sqlite3_column_value(pSql, i)); |
+ } |
+ sqlite3_step(pInsert); |
+ p->rc = sqlite3_reset(pInsert); |
+ } |
+ if( p->rc==SQLITE_OK ){ |
+ p->rc = sqlite3_exec(p->dbMain, "PRAGMA writable_schema=0",0,0,&p->zErrmsg); |
+ } |
+ |
+ rbuFinalize(p, pSql); |
+ rbuFinalize(p, pInsert); |
+} |
+ |
+/* |
+** Step the RBU object. |
+*/ |
+int sqlite3rbu_step(sqlite3rbu *p){ |
+ if( p ){ |
+ switch( p->eStage ){ |
+ case RBU_STAGE_OAL: { |
+ RbuObjIter *pIter = &p->objiter; |
+ |
+ /* If this is an RBU vacuum operation and the state table was empty |
+ ** when this handle was opened, create the target database schema. */ |
+ if( rbuIsVacuum(p) && p->nProgress==0 && p->rc==SQLITE_OK ){ |
+ rbuCreateTargetSchema(p); |
+ rbuCopyPragma(p, "user_version"); |
+ rbuCopyPragma(p, "application_id"); |
+ } |
+ |
+ while( p->rc==SQLITE_OK && pIter->zTbl ){ |
if( pIter->bCleanup ){ |
/* Clean up the rbu_tmp_xxx table for the previous table. It |
** cannot be dropped as there are currently active SQL statements. |
** But the contents can be deleted. */ |
- if( pIter->abIndexed ){ |
+ if( rbuIsVacuum(p)==0 && pIter->abIndexed ){ |
rbuMPrintfExec(p, p->dbRbu, |
"DELETE FROM %s.'rbu_tmp_%q'", p->zStateDb, pIter->zDataTbl |
); |
@@ -2842,90 +3294,6 @@ int sqlite3rbu_step(sqlite3rbu *p){ |
} |
/* |
-** Free an RbuState object allocated by rbuLoadState(). |
-*/ |
-static void rbuFreeState(RbuState *p){ |
- if( p ){ |
- sqlite3_free(p->zTbl); |
- sqlite3_free(p->zIdx); |
- sqlite3_free(p); |
- } |
-} |
- |
-/* |
-** Allocate an RbuState object and load the contents of the rbu_state |
-** table into it. Return a pointer to the new object. It is the |
-** responsibility of the caller to eventually free the object using |
-** sqlite3_free(). |
-** |
-** If an error occurs, leave an error code and message in the rbu handle |
-** and return NULL. |
-*/ |
-static RbuState *rbuLoadState(sqlite3rbu *p){ |
- RbuState *pRet = 0; |
- sqlite3_stmt *pStmt = 0; |
- int rc; |
- int rc2; |
- |
- pRet = (RbuState*)rbuMalloc(p, sizeof(RbuState)); |
- if( pRet==0 ) return 0; |
- |
- rc = prepareFreeAndCollectError(p->dbRbu, &pStmt, &p->zErrmsg, |
- sqlite3_mprintf("SELECT k, v FROM %s.rbu_state", p->zStateDb) |
- ); |
- while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ |
- switch( sqlite3_column_int(pStmt, 0) ){ |
- case RBU_STATE_STAGE: |
- pRet->eStage = sqlite3_column_int(pStmt, 1); |
- if( pRet->eStage!=RBU_STAGE_OAL |
- && pRet->eStage!=RBU_STAGE_MOVE |
- && pRet->eStage!=RBU_STAGE_CKPT |
- ){ |
- p->rc = SQLITE_CORRUPT; |
- } |
- break; |
- |
- case RBU_STATE_TBL: |
- pRet->zTbl = rbuStrndup((char*)sqlite3_column_text(pStmt, 1), &rc); |
- break; |
- |
- case RBU_STATE_IDX: |
- pRet->zIdx = rbuStrndup((char*)sqlite3_column_text(pStmt, 1), &rc); |
- break; |
- |
- case RBU_STATE_ROW: |
- pRet->nRow = sqlite3_column_int(pStmt, 1); |
- break; |
- |
- case RBU_STATE_PROGRESS: |
- pRet->nProgress = sqlite3_column_int64(pStmt, 1); |
- break; |
- |
- case RBU_STATE_CKPT: |
- pRet->iWalCksum = sqlite3_column_int64(pStmt, 1); |
- break; |
- |
- case RBU_STATE_COOKIE: |
- pRet->iCookie = (u32)sqlite3_column_int64(pStmt, 1); |
- break; |
- |
- case RBU_STATE_OALSZ: |
- pRet->iOalSz = (u32)sqlite3_column_int64(pStmt, 1); |
- break; |
- |
- default: |
- rc = SQLITE_CORRUPT; |
- break; |
- } |
- } |
- rc2 = sqlite3_finalize(pStmt); |
- if( rc==SQLITE_OK ) rc = rc2; |
- |
- p->rc = rc; |
- return pRet; |
-} |
- |
-/* |
** Compare strings z1 and z2, returning 0 if they are identical, or non-zero |
** otherwise. Either or both argument may be NULL. Two NULL values are |
** considered equal, and NULL is considered distinct from all other values. |
@@ -3021,19 +3389,111 @@ static void rbuDeleteVfs(sqlite3rbu *p){ |
} |
/* |
-** Open and return a new RBU handle. |
+** This user-defined SQL function is invoked with a single argument - the |
+** name of a table expected to appear in the target database. It returns |
+** the number of auxilliary indexes on the table. |
*/ |
-sqlite3rbu *sqlite3rbu_open( |
+static void rbuIndexCntFunc( |
+ sqlite3_context *pCtx, |
+ int nVal, |
+ sqlite3_value **apVal |
+){ |
+ sqlite3rbu *p = (sqlite3rbu*)sqlite3_user_data(pCtx); |
+ sqlite3_stmt *pStmt = 0; |
+ char *zErrmsg = 0; |
+ int rc; |
+ |
+ assert( nVal==1 ); |
+ |
+ rc = prepareFreeAndCollectError(p->dbMain, &pStmt, &zErrmsg, |
+ sqlite3_mprintf("SELECT count(*) FROM sqlite_master " |
+ "WHERE type='index' AND tbl_name = %Q", sqlite3_value_text(apVal[0])) |
+ ); |
+ if( rc!=SQLITE_OK ){ |
+ sqlite3_result_error(pCtx, zErrmsg, -1); |
+ }else{ |
+ int nIndex = 0; |
+ if( SQLITE_ROW==sqlite3_step(pStmt) ){ |
+ nIndex = sqlite3_column_int(pStmt, 0); |
+ } |
+ rc = sqlite3_finalize(pStmt); |
+ if( rc==SQLITE_OK ){ |
+ sqlite3_result_int(pCtx, nIndex); |
+ }else{ |
+ sqlite3_result_error(pCtx, sqlite3_errmsg(p->dbMain), -1); |
+ } |
+ } |
+ |
+ sqlite3_free(zErrmsg); |
+} |
+ |
+/* |
+** If the RBU database contains the rbu_count table, use it to initialize |
+** the sqlite3rbu.nPhaseOneStep variable. The schema of the rbu_count table |
+** is assumed to contain the same columns as: |
+** |
+** CREATE TABLE rbu_count(tbl TEXT PRIMARY KEY, cnt INTEGER) WITHOUT ROWID; |
+** |
+** There should be one row in the table for each data_xxx table in the |
+** database. The 'tbl' column should contain the name of a data_xxx table, |
+** and the cnt column the number of rows it contains. |
+** |
+** sqlite3rbu.nPhaseOneStep is initialized to the sum of (1 + nIndex) * cnt |
+** for all rows in the rbu_count table, where nIndex is the number of |
+** indexes on the corresponding target database table. |
+*/ |
+static void rbuInitPhaseOneSteps(sqlite3rbu *p){ |
+ if( p->rc==SQLITE_OK ){ |
+ sqlite3_stmt *pStmt = 0; |
+ int bExists = 0; /* True if rbu_count exists */ |
+ |
+ p->nPhaseOneStep = -1; |
+ |
+ p->rc = sqlite3_create_function(p->dbRbu, |
+ "rbu_index_cnt", 1, SQLITE_UTF8, (void*)p, rbuIndexCntFunc, 0, 0 |
+ ); |
+ |
+ /* Check for the rbu_count table. If it does not exist, or if an error |
+ ** occurs, nPhaseOneStep will be left set to -1. */ |
+ if( p->rc==SQLITE_OK ){ |
+ p->rc = prepareAndCollectError(p->dbRbu, &pStmt, &p->zErrmsg, |
+ "SELECT 1 FROM sqlite_master WHERE tbl_name = 'rbu_count'" |
+ ); |
+ } |
+ if( p->rc==SQLITE_OK ){ |
+ if( SQLITE_ROW==sqlite3_step(pStmt) ){ |
+ bExists = 1; |
+ } |
+ p->rc = sqlite3_finalize(pStmt); |
+ } |
+ |
+ if( p->rc==SQLITE_OK && bExists ){ |
+ p->rc = prepareAndCollectError(p->dbRbu, &pStmt, &p->zErrmsg, |
+ "SELECT sum(cnt * (1 + rbu_index_cnt(rbu_target_name(tbl))))" |
+ "FROM rbu_count" |
+ ); |
+ if( p->rc==SQLITE_OK ){ |
+ if( SQLITE_ROW==sqlite3_step(pStmt) ){ |
+ p->nPhaseOneStep = sqlite3_column_int64(pStmt, 0); |
+ } |
+ p->rc = sqlite3_finalize(pStmt); |
+ } |
+ } |
+ } |
+} |
+ |
+ |
+static sqlite3rbu *openRbuHandle( |
const char *zTarget, |
const char *zRbu, |
const char *zState |
){ |
sqlite3rbu *p; |
- int nTarget = strlen(zTarget); |
- int nRbu = strlen(zRbu); |
- int nState = zState ? strlen(zState) : 0; |
+ size_t nTarget = zTarget ? strlen(zTarget) : 0; |
+ size_t nRbu = strlen(zRbu); |
+ size_t nByte = sizeof(sqlite3rbu) + nTarget+1 + nRbu+1; |
- p = (sqlite3rbu*)sqlite3_malloc(sizeof(sqlite3rbu)+nTarget+1+nRbu+1+nState+1); |
+ p = (sqlite3rbu*)sqlite3_malloc64(nByte); |
if( p ){ |
RbuState *pState = 0; |
@@ -3041,21 +3501,34 @@ sqlite3rbu *sqlite3rbu_open( |
memset(p, 0, sizeof(sqlite3rbu)); |
rbuCreateVfs(p); |
- /* Open the target database */ |
+ /* Open the target, RBU and state databases */ |
if( p->rc==SQLITE_OK ){ |
- p->zTarget = (char*)&p[1]; |
- memcpy(p->zTarget, zTarget, nTarget+1); |
- p->zRbu = &p->zTarget[nTarget+1]; |
+ char *pCsr = (char*)&p[1]; |
+ int bRetry = 0; |
+ if( zTarget ){ |
+ p->zTarget = pCsr; |
+ memcpy(p->zTarget, zTarget, nTarget+1); |
+ pCsr += nTarget+1; |
+ } |
+ p->zRbu = pCsr; |
memcpy(p->zRbu, zRbu, nRbu+1); |
+ pCsr += nRbu+1; |
if( zState ){ |
- p->zState = &p->zRbu[nRbu+1]; |
- memcpy(p->zState, zState, nState+1); |
+ p->zState = rbuMPrintf(p, "%s", zState); |
} |
- rbuOpenDatabase(p); |
- } |
- /* If it has not already been created, create the rbu_state table */ |
- rbuMPrintfExec(p, p->dbRbu, RBU_CREATE_STATE, p->zStateDb); |
+ /* If the first attempt to open the database file fails and the bRetry |
+ ** flag it set, this means that the db was not opened because it seemed |
+ ** to be a wal-mode db. But, this may have happened due to an earlier |
+ ** RBU vacuum operation leaving an old wal file in the directory. |
+ ** If this is the case, it will have been checkpointed and deleted |
+ ** when the handle was closed and a second attempt to open the |
+ ** database may succeed. */ |
+ rbuOpenDatabase(p, &bRetry); |
+ if( bRetry ){ |
+ rbuOpenDatabase(p, 0); |
+ } |
+ } |
if( p->rc==SQLITE_OK ){ |
pState = rbuLoadState(p); |
@@ -3064,9 +3537,11 @@ sqlite3rbu *sqlite3rbu_open( |
if( pState->eStage==0 ){ |
rbuDeleteOalFile(p); |
+ rbuInitPhaseOneSteps(p); |
p->eStage = RBU_STAGE_OAL; |
}else{ |
p->eStage = pState->eStage; |
+ p->nPhaseOneStep = pState->nPhaseOneStep; |
} |
p->nProgress = pState->nProgress; |
p->iOalSz = pState->iOalSz; |
@@ -3084,38 +3559,27 @@ sqlite3rbu *sqlite3rbu_open( |
} |
} |
- if( p->rc==SQLITE_OK |
+ if( p->rc==SQLITE_OK |
&& (p->eStage==RBU_STAGE_OAL || p->eStage==RBU_STAGE_MOVE) |
- && pState->eStage!=0 && p->pTargetFd->iCookie!=pState->iCookie |
- ){ |
- /* At this point (pTargetFd->iCookie) contains the value of the |
- ** change-counter cookie (the thing that gets incremented when a |
- ** transaction is committed in rollback mode) currently stored on |
- ** page 1 of the database file. */ |
- p->rc = SQLITE_BUSY; |
- p->zErrmsg = sqlite3_mprintf("database modified during rbu update"); |
+ && pState->eStage!=0 |
+ ){ |
+ rbu_file *pFd = (rbuIsVacuum(p) ? p->pRbuFd : p->pTargetFd); |
+ if( pFd->iCookie!=pState->iCookie ){ |
+ /* At this point (pTargetFd->iCookie) contains the value of the |
+ ** change-counter cookie (the thing that gets incremented when a |
+ ** transaction is committed in rollback mode) currently stored on |
+ ** page 1 of the database file. */ |
+ p->rc = SQLITE_BUSY; |
+ p->zErrmsg = sqlite3_mprintf("database modified during rbu %s", |
+ (rbuIsVacuum(p) ? "vacuum" : "update") |
+ ); |
+ } |
} |
if( p->rc==SQLITE_OK ){ |
if( p->eStage==RBU_STAGE_OAL ){ |
sqlite3 *db = p->dbMain; |
- |
- /* Open transactions both databases. The *-oal file is opened or |
- ** created at this point. */ |
- p->rc = sqlite3_exec(db, "BEGIN IMMEDIATE", 0, 0, &p->zErrmsg); |
- if( p->rc==SQLITE_OK ){ |
- p->rc = sqlite3_exec(p->dbRbu, "BEGIN IMMEDIATE", 0, 0, &p->zErrmsg); |
- } |
- |
- /* Check if the main database is a zipvfs db. If it is, set the upper |
- ** level pager to use "journal_mode=off". This prevents it from |
- ** generating a large journal using a temp file. */ |
- if( p->rc==SQLITE_OK ){ |
- int frc = sqlite3_file_control(db, "main", SQLITE_FCNTL_ZIPVFS, 0); |
- if( frc==SQLITE_OK ){ |
- p->rc = sqlite3_exec(db, "PRAGMA journal_mode=off",0,0,&p->zErrmsg); |
- } |
- } |
+ p->rc = sqlite3_exec(p->dbRbu, "BEGIN", 0, 0, &p->zErrmsg); |
/* Point the object iterator at the first object */ |
if( p->rc==SQLITE_OK ){ |
@@ -3126,12 +3590,34 @@ sqlite3rbu *sqlite3rbu_open( |
** update finished. */ |
if( p->rc==SQLITE_OK && p->objiter.zTbl==0 ){ |
p->rc = SQLITE_DONE; |
- } |
+ p->eStage = RBU_STAGE_DONE; |
+ }else{ |
+ if( p->rc==SQLITE_OK && pState->eStage==0 && rbuIsVacuum(p) ){ |
+ rbuCopyPragma(p, "page_size"); |
+ rbuCopyPragma(p, "auto_vacuum"); |
+ } |
- if( p->rc==SQLITE_OK ){ |
- rbuSetupOal(p, pState); |
- } |
+ /* Open transactions both databases. The *-oal file is opened or |
+ ** created at this point. */ |
+ if( p->rc==SQLITE_OK ){ |
+ p->rc = sqlite3_exec(db, "BEGIN IMMEDIATE", 0, 0, &p->zErrmsg); |
+ } |
+ |
+ /* Check if the main database is a zipvfs db. If it is, set the upper |
+ ** level pager to use "journal_mode=off". This prevents it from |
+ ** generating a large journal using a temp file. */ |
+ if( p->rc==SQLITE_OK ){ |
+ int frc = sqlite3_file_control(db, "main", SQLITE_FCNTL_ZIPVFS, 0); |
+ if( frc==SQLITE_OK ){ |
+ p->rc = sqlite3_exec( |
+ db, "PRAGMA journal_mode=off",0,0,&p->zErrmsg); |
+ } |
+ } |
+ if( p->rc==SQLITE_OK ){ |
+ rbuSetupOal(p, pState); |
+ } |
+ } |
}else if( p->eStage==RBU_STAGE_MOVE ){ |
/* no-op */ |
}else if( p->eStage==RBU_STAGE_CKPT ){ |
@@ -3149,6 +3635,44 @@ sqlite3rbu *sqlite3rbu_open( |
return p; |
} |
+/* |
+** Allocate and return an RBU handle with all fields zeroed except for the |
+** error code, which is set to SQLITE_MISUSE. |
+*/ |
+static sqlite3rbu *rbuMisuseError(void){ |
+ sqlite3rbu *pRet; |
+ pRet = sqlite3_malloc64(sizeof(sqlite3rbu)); |
+ if( pRet ){ |
+ memset(pRet, 0, sizeof(sqlite3rbu)); |
+ pRet->rc = SQLITE_MISUSE; |
+ } |
+ return pRet; |
+} |
+ |
+/* |
+** Open and return a new RBU handle. |
+*/ |
+sqlite3rbu *sqlite3rbu_open( |
+ const char *zTarget, |
+ const char *zRbu, |
+ const char *zState |
+){ |
+ if( zTarget==0 || zRbu==0 ){ return rbuMisuseError(); } |
+ /* TODO: Check that zTarget and zRbu are non-NULL */ |
+ return openRbuHandle(zTarget, zRbu, zState); |
+} |
+ |
+/* |
+** Open a handle to begin or resume an RBU VACUUM operation. |
+*/ |
+sqlite3rbu *sqlite3rbu_vacuum( |
+ const char *zTarget, |
+ const char *zState |
+){ |
+ if( zTarget==0 ){ return rbuMisuseError(); } |
+ /* TODO: Check that both arguments are non-NULL */ |
+ return openRbuHandle(0, zTarget, zState); |
+} |
/* |
** Return the database handle used by pRbu. |
@@ -3169,8 +3693,8 @@ sqlite3 *sqlite3rbu_db(sqlite3rbu *pRbu, int bRbu){ |
*/ |
static void rbuEditErrmsg(sqlite3rbu *p){ |
if( p->rc==SQLITE_CONSTRAINT && p->zErrmsg ){ |
- int i; |
- int nErrmsg = strlen(p->zErrmsg); |
+ unsigned int i; |
+ size_t nErrmsg = strlen(p->zErrmsg); |
for(i=0; i<(nErrmsg-8); i++){ |
if( memcmp(&p->zErrmsg[i], "rbu_imp_", 8)==0 ){ |
int nDel = 8; |
@@ -3203,9 +3727,19 @@ int sqlite3rbu_close(sqlite3rbu *p, char **pzErrmsg){ |
/* Close any open statement handles. */ |
rbuObjIterFinalize(&p->objiter); |
+ /* If this is an RBU vacuum handle and the vacuum has either finished |
+ ** successfully or encountered an error, delete the contents of the |
+ ** state table. This causes the next call to sqlite3rbu_vacuum() |
+ ** specifying the current target and state databases to start a new |
+ ** vacuum from scratch. */ |
+ if( rbuIsVacuum(p) && p->rc!=SQLITE_OK && p->dbRbu ){ |
+ int rc2 = sqlite3_exec(p->dbRbu, "DELETE FROM stat.rbu_state", 0, 0, 0); |
+ if( p->rc==SQLITE_DONE && rc2!=SQLITE_OK ) p->rc = rc2; |
+ } |
+ |
/* Close the open database handle and VFS object. */ |
- sqlite3_close(p->dbMain); |
sqlite3_close(p->dbRbu); |
+ sqlite3_close(p->dbMain); |
rbuDeleteVfs(p); |
sqlite3_free(p->aBuf); |
sqlite3_free(p->aFrame); |
@@ -3213,6 +3747,7 @@ int sqlite3rbu_close(sqlite3rbu *p, char **pzErrmsg){ |
rbuEditErrmsg(p); |
rc = p->rc; |
*pzErrmsg = p->zErrmsg; |
+ sqlite3_free(p->zState); |
sqlite3_free(p); |
}else{ |
rc = SQLITE_NOMEM; |
@@ -3230,9 +3765,75 @@ sqlite3_int64 sqlite3rbu_progress(sqlite3rbu *pRbu){ |
return pRbu->nProgress; |
} |
+/* |
+** Return permyriadage progress indications for the two main stages of |
+** an RBU update. |
+*/ |
+void sqlite3rbu_bp_progress(sqlite3rbu *p, int *pnOne, int *pnTwo){ |
+ const int MAX_PROGRESS = 10000; |
+ switch( p->eStage ){ |
+ case RBU_STAGE_OAL: |
+ if( p->nPhaseOneStep>0 ){ |
+ *pnOne = (int)(MAX_PROGRESS * (i64)p->nProgress/(i64)p->nPhaseOneStep); |
+ }else{ |
+ *pnOne = -1; |
+ } |
+ *pnTwo = 0; |
+ break; |
+ |
+ case RBU_STAGE_MOVE: |
+ *pnOne = MAX_PROGRESS; |
+ *pnTwo = 0; |
+ break; |
+ |
+ case RBU_STAGE_CKPT: |
+ *pnOne = MAX_PROGRESS; |
+ *pnTwo = (int)(MAX_PROGRESS * (i64)p->nStep / (i64)p->nFrame); |
+ break; |
+ |
+ case RBU_STAGE_DONE: |
+ *pnOne = MAX_PROGRESS; |
+ *pnTwo = MAX_PROGRESS; |
+ break; |
+ |
+ default: |
+ assert( 0 ); |
+ } |
+} |
+ |
+/* |
+** Return the current state of the RBU vacuum or update operation. |
+*/ |
+int sqlite3rbu_state(sqlite3rbu *p){ |
+ int aRes[] = { |
+ 0, SQLITE_RBU_STATE_OAL, SQLITE_RBU_STATE_MOVE, |
+ 0, SQLITE_RBU_STATE_CHECKPOINT, SQLITE_RBU_STATE_DONE |
+ }; |
+ |
+ assert( RBU_STAGE_OAL==1 ); |
+ assert( RBU_STAGE_MOVE==2 ); |
+ assert( RBU_STAGE_CKPT==4 ); |
+ assert( RBU_STAGE_DONE==5 ); |
+ assert( aRes[RBU_STAGE_OAL]==SQLITE_RBU_STATE_OAL ); |
+ assert( aRes[RBU_STAGE_MOVE]==SQLITE_RBU_STATE_MOVE ); |
+ assert( aRes[RBU_STAGE_CKPT]==SQLITE_RBU_STATE_CHECKPOINT ); |
+ assert( aRes[RBU_STAGE_DONE]==SQLITE_RBU_STATE_DONE ); |
+ |
+ if( p->rc!=SQLITE_OK && p->rc!=SQLITE_DONE ){ |
+ return SQLITE_RBU_STATE_ERROR; |
+ }else{ |
+ assert( p->rc!=SQLITE_DONE || p->eStage==RBU_STAGE_DONE ); |
+ assert( p->eStage==RBU_STAGE_OAL |
+ || p->eStage==RBU_STAGE_MOVE |
+ || p->eStage==RBU_STAGE_CKPT |
+ || p->eStage==RBU_STAGE_DONE |
+ ); |
+ return aRes[p->eStage]; |
+ } |
+} |
+ |
int sqlite3rbu_savestate(sqlite3rbu *p){ |
int rc = p->rc; |
- |
if( rc==SQLITE_DONE ) return SQLITE_OK; |
assert( p->eStage>=RBU_STAGE_OAL && p->eStage<=RBU_STAGE_DONE ); |
@@ -3372,6 +3973,22 @@ static u32 rbuGetU32(u8 *aBuf){ |
} |
/* |
+** Write an unsigned 32-bit value in big-endian format to the supplied |
+** buffer. |
+*/ |
+static void rbuPutU32(u8 *aBuf, u32 iVal){ |
+ aBuf[0] = (iVal >> 24) & 0xFF; |
+ aBuf[1] = (iVal >> 16) & 0xFF; |
+ aBuf[2] = (iVal >> 8) & 0xFF; |
+ aBuf[3] = (iVal >> 0) & 0xFF; |
+} |
+ |
+static void rbuPutU16(u8 *aBuf, u16 iVal){ |
+ aBuf[0] = (iVal >> 8) & 0xFF; |
+ aBuf[1] = (iVal >> 0) & 0xFF; |
+} |
+ |
+/* |
** Read data from an rbuVfs-file. |
*/ |
static int rbuVfsRead( |
@@ -3396,6 +4013,35 @@ static int rbuVfsRead( |
memset(zBuf, 0, iAmt); |
}else{ |
rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst); |
+#if 1 |
+ /* If this is being called to read the first page of the target |
+ ** database as part of an rbu vacuum operation, synthesize the |
+ ** contents of the first page if it does not yet exist. Otherwise, |
+ ** SQLite will not check for a *-wal file. */ |
+ if( pRbu && rbuIsVacuum(pRbu) |
+ && rc==SQLITE_IOERR_SHORT_READ && iOfst==0 |
+ && (p->openFlags & SQLITE_OPEN_MAIN_DB) |
+ && pRbu->rc==SQLITE_OK |
+ ){ |
+ sqlite3_file *pFd = (sqlite3_file*)pRbu->pRbuFd; |
+ rc = pFd->pMethods->xRead(pFd, zBuf, iAmt, iOfst); |
+ if( rc==SQLITE_OK ){ |
+ u8 *aBuf = (u8*)zBuf; |
+ u32 iRoot = rbuGetU32(&aBuf[52]) ? 1 : 0; |
+ rbuPutU32(&aBuf[52], iRoot); /* largest root page number */ |
+ rbuPutU32(&aBuf[36], 0); /* number of free pages */ |
+ rbuPutU32(&aBuf[32], 0); /* first page on free list trunk */ |
+ rbuPutU32(&aBuf[28], 1); /* size of db file in pages */ |
+ rbuPutU32(&aBuf[24], pRbu->pRbuFd->iCookie+1); /* Change counter */ |
+ |
+ if( iAmt>100 ){ |
+ memset(&aBuf[100], 0, iAmt-100); |
+ rbuPutU16(&aBuf[105], iAmt & 0xFFFF); |
+ aBuf[100] = 0x0D; |
+ } |
+ } |
+ } |
+#endif |
} |
if( rc==SQLITE_OK && iOfst==0 && (p->openFlags & SQLITE_OPEN_MAIN_DB) ){ |
/* These look like magic numbers. But they are stable, as they are part |
@@ -3470,7 +4116,20 @@ static int rbuVfsSync(sqlite3_file *pFile, int flags){ |
*/ |
static int rbuVfsFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ |
rbu_file *p = (rbu_file *)pFile; |
- return p->pReal->pMethods->xFileSize(p->pReal, pSize); |
+ int rc; |
+ rc = p->pReal->pMethods->xFileSize(p->pReal, pSize); |
+ |
+ /* If this is an RBU vacuum operation and this is the target database, |
+ ** pretend that it has at least one page. Otherwise, SQLite will not |
+ ** check for the existance of a *-wal file. rbuVfsRead() contains |
+ ** similar logic. */ |
+ if( rc==SQLITE_OK && *pSize==0 |
+ && p->pRbu && rbuIsVacuum(p->pRbu) |
+ && (p->openFlags & SQLITE_OPEN_MAIN_DB) |
+ ){ |
+ *pSize = 1024; |
+ } |
+ return rc; |
} |
/* |
@@ -3482,7 +4141,9 @@ static int rbuVfsLock(sqlite3_file *pFile, int eLock){ |
int rc = SQLITE_OK; |
assert( p->openFlags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_TEMP_DB) ); |
- if( pRbu && eLock==SQLITE_LOCK_EXCLUSIVE && pRbu->eStage!=RBU_STAGE_DONE ){ |
+ if( eLock==SQLITE_LOCK_EXCLUSIVE |
+ && (p->bNolock || (pRbu && pRbu->eStage!=RBU_STAGE_DONE)) |
+ ){ |
/* Do not allow EXCLUSIVE locks. Preventing SQLite from taking this |
** prevents it from checkpointing the database from sqlite3_close(). */ |
rc = SQLITE_BUSY; |
@@ -3545,6 +4206,12 @@ static int rbuVfsFileControl(sqlite3_file *pFile, int op, void *pArg){ |
} |
return rc; |
} |
+ else if( op==SQLITE_FCNTL_RBUCNT ){ |
+ sqlite3rbu *pRbu = (sqlite3rbu*)pArg; |
+ pRbu->nRbu++; |
+ pRbu->pRbuFd = p; |
+ p->bNolock = 1; |
+ } |
rc = xControl(p->pReal, op, pArg); |
if( rc==SQLITE_OK && op==SQLITE_FCNTL_VFSNAME ){ |
@@ -3634,7 +4301,7 @@ static int rbuVfsShmMap( |
if( eStage==RBU_STAGE_OAL || eStage==RBU_STAGE_MOVE ){ |
if( iRegion<=p->nShm ){ |
int nByte = (iRegion+1) * sizeof(char*); |
- char **apNew = (char**)sqlite3_realloc(p->apShm, nByte); |
+ char **apNew = (char**)sqlite3_realloc64(p->apShm, nByte); |
if( apNew==0 ){ |
rc = SQLITE_NOMEM; |
}else{ |
@@ -3645,7 +4312,7 @@ static int rbuVfsShmMap( |
} |
if( rc==SQLITE_OK && p->apShm[iRegion]==0 ){ |
- char *pNew = (char*)sqlite3_malloc(szRegion); |
+ char *pNew = (char*)sqlite3_malloc64(szRegion); |
if( pNew==0 ){ |
rc = SQLITE_NOMEM; |
}else{ |
@@ -3703,11 +4370,38 @@ static int rbuVfsShmUnmap(sqlite3_file *pFile, int delFlag){ |
static rbu_file *rbuFindMaindb(rbu_vfs *pRbuVfs, const char *zWal){ |
rbu_file *pDb; |
sqlite3_mutex_enter(pRbuVfs->mutex); |
- for(pDb=pRbuVfs->pMain; pDb && pDb->zWal!=zWal; pDb=pDb->pMainNext); |
+ for(pDb=pRbuVfs->pMain; pDb && pDb->zWal!=zWal; pDb=pDb->pMainNext){} |
sqlite3_mutex_leave(pRbuVfs->mutex); |
return pDb; |
} |
+/* |
+** A main database named zName has just been opened. The following |
+** function returns a pointer to a buffer owned by SQLite that contains |
+** the name of the *-wal file this db connection will use. SQLite |
+** happens to pass a pointer to this buffer when using xAccess() |
+** or xOpen() to operate on the *-wal file. |
+*/ |
+static const char *rbuMainToWal(const char *zName, int flags){ |
+ int n = (int)strlen(zName); |
+ const char *z = &zName[n]; |
+ if( flags & SQLITE_OPEN_URI ){ |
+ int odd = 0; |
+ while( 1 ){ |
+ if( z[0]==0 ){ |
+ odd = 1 - odd; |
+ if( odd && z[1]==0 ) break; |
+ } |
+ z++; |
+ } |
+ z += 2; |
+ }else{ |
+ while( *z==0 ) z++; |
+ } |
+ z += (n + 8 + 1); |
+ return z; |
+} |
+ |
/* |
** Open an rbu file handle. |
*/ |
@@ -3743,6 +4437,7 @@ static int rbuVfsOpen( |
rbu_file *pFd = (rbu_file *)pFile; |
int rc = SQLITE_OK; |
const char *zOpen = zName; |
+ int oflags = flags; |
memset(pFd, 0, sizeof(rbu_file)); |
pFd->pReal = (sqlite3_file*)&pFd[1]; |
@@ -3755,23 +4450,7 @@ static int rbuVfsOpen( |
** the name of the *-wal file this db connection will use. SQLite |
** happens to pass a pointer to this buffer when using xAccess() |
** or xOpen() to operate on the *-wal file. */ |
- int n = strlen(zName); |
- const char *z = &zName[n]; |
- if( flags & SQLITE_OPEN_URI ){ |
- int odd = 0; |
- while( 1 ){ |
- if( z[0]==0 ){ |
- odd = 1 - odd; |
- if( odd && z[1]==0 ) break; |
- } |
- z++; |
- } |
- z += 2; |
- }else{ |
- while( *z==0 ) z++; |
- } |
- z += (n + 8 + 1); |
- pFd->zWal = z; |
+ pFd->zWal = rbuMainToWal(zName, flags); |
} |
else if( flags & SQLITE_OPEN_WAL ){ |
rbu_file *pDb = rbuFindMaindb(pRbuVfs, zName); |
@@ -3781,10 +4460,17 @@ static int rbuVfsOpen( |
** code ensures that the string passed to xOpen() is terminated by a |
** pair of '\0' bytes in case the VFS attempts to extract a URI |
** parameter from it. */ |
- int nCopy = strlen(zName); |
- char *zCopy = sqlite3_malloc(nCopy+2); |
+ const char *zBase = zName; |
+ size_t nCopy; |
+ char *zCopy; |
+ if( rbuIsVacuum(pDb->pRbu) ){ |
+ zBase = sqlite3_db_filename(pDb->pRbu->dbRbu, "main"); |
+ zBase = rbuMainToWal(zBase, SQLITE_OPEN_URI); |
+ } |
+ nCopy = strlen(zBase); |
+ zCopy = sqlite3_malloc64(nCopy+2); |
if( zCopy ){ |
- memcpy(zCopy, zName, nCopy); |
+ memcpy(zCopy, zBase, nCopy); |
zCopy[nCopy-3] = 'o'; |
zCopy[nCopy] = '\0'; |
zCopy[nCopy+1] = '\0'; |
@@ -3799,8 +4485,17 @@ static int rbuVfsOpen( |
} |
} |
+ if( oflags & SQLITE_OPEN_MAIN_DB |
+ && sqlite3_uri_boolean(zName, "rbu_memory", 0) |
+ ){ |
+ assert( oflags & SQLITE_OPEN_MAIN_DB ); |
+ oflags = SQLITE_OPEN_TEMP_DB | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | |
+ SQLITE_OPEN_EXCLUSIVE | SQLITE_OPEN_DELETEONCLOSE; |
+ zOpen = 0; |
+ } |
+ |
if( rc==SQLITE_OK ){ |
- rc = pRealVfs->xOpen(pRealVfs, zOpen, pFd->pReal, flags, pOutFlags); |
+ rc = pRealVfs->xOpen(pRealVfs, zOpen, pFd->pReal, oflags, pOutFlags); |
} |
if( pFd->pReal->pMethods ){ |
/* The xOpen() operation has succeeded. Set the sqlite3_file.pMethods |
@@ -4011,13 +4706,13 @@ int sqlite3rbu_create_vfs(const char *zName, const char *zParent){ |
}; |
rbu_vfs *pNew = 0; /* Newly allocated VFS */ |
- int nName; |
int rc = SQLITE_OK; |
+ size_t nName; |
+ size_t nByte; |
- int nByte; |
nName = strlen(zName); |
nByte = sizeof(rbu_vfs) + nName + 1; |
- pNew = (rbu_vfs*)sqlite3_malloc(nByte); |
+ pNew = (rbu_vfs*)sqlite3_malloc64(nByte); |
if( pNew==0 ){ |
rc = SQLITE_NOMEM; |
}else{ |