Rework recover extension code for readability.
FossilOrigin-Name: 1a2540960e40e3c8c622448fd3862e249bd463c29ae4ce5e39942e942533f60a
This commit is contained in:
parent
bc2e7fc228
commit
65660916dc
@ -17,6 +17,16 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
** Declaration for public API function in file dbdata.c. This may be called
|
||||
** with NULL as the final two arguments to register the sqlite_dbptr and
|
||||
** sqlite_dbdata virtual tables with a database handle.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
int sqlite3_dbdata_init(sqlite3*, char**, const sqlite3_api_routines*);
|
||||
|
||||
typedef unsigned int u32;
|
||||
typedef sqlite3_int64 i64;
|
||||
|
||||
@ -131,29 +141,32 @@ struct RecoverBitmap {
|
||||
};
|
||||
|
||||
/*
|
||||
**
|
||||
** Main recover handle structure.
|
||||
*/
|
||||
struct sqlite3_recover {
|
||||
/* Copies of sqlite3_recover_init[_sql]() parameters */
|
||||
sqlite3 *dbIn; /* Input database */
|
||||
sqlite3 *dbOut; /* Output database */
|
||||
|
||||
sqlite3_stmt *pGetPage; /* SELECT against input db sqlite_dbdata */
|
||||
|
||||
char *zDb; /* Name of input db ("main" etc.) */
|
||||
char *zUri; /* URI for output database */
|
||||
RecoverTable *pTblList; /* List of tables recovered from schem */
|
||||
RecoverBitmap *pUsed; /* Used by recoverLostAndFound() */
|
||||
|
||||
int errCode; /* For sqlite3_recover_errcode() */
|
||||
char *zErrMsg; /* For sqlite3_recover_errmsg() */
|
||||
void *pSqlCtx; /* SQL callback context */
|
||||
int (*xSql)(void*,const char*); /* Pointer to SQL callback function */
|
||||
|
||||
/* Values configured by sqlite3_recover_config() */
|
||||
char *zStateDb; /* State database to use (or NULL) */
|
||||
char *zLostAndFound; /* Name of lost-and-found table (or NULL) */
|
||||
int bFreelistCorrupt; /* SQLITE_RECOVER_FREELIST_CORRUPT setting */
|
||||
int bRecoverRowid; /* SQLITE_RECOVER_ROWIDS setting */
|
||||
|
||||
void *pSqlCtx; /* SQL callback context */
|
||||
int (*xSql)(void*,const char*); /* Pointer to SQL callback function */
|
||||
/* Error code and error message */
|
||||
int errCode; /* For sqlite3_recover_errcode() */
|
||||
char *zErrMsg; /* For sqlite3_recover_errmsg() */
|
||||
|
||||
/* Fields used within sqlite3_recover_run() */
|
||||
int bRun; /* True once _recover_run() has been called */
|
||||
sqlite3 *dbOut; /* Output database */
|
||||
sqlite3_stmt *pGetPage; /* SELECT against input db sqlite_dbdata */
|
||||
RecoverTable *pTblList; /* List of tables recovered from schem */
|
||||
RecoverBitmap *pUsed; /* Used by recoverLostAndFound() */
|
||||
};
|
||||
|
||||
/*
|
||||
@ -172,6 +185,15 @@ static int recoverStrlen(const char *zStr){
|
||||
return nRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is a no-op if the recover handle passed as the first
|
||||
** argument already contains an error (if p->errCode!=SQLITE_OK).
|
||||
**
|
||||
** Otherwise, an attempt is made to allocate, zero and return a buffer nByte
|
||||
** bytes in size. If successful, a pointer to the new buffer is returned. Or,
|
||||
** if an OOM error occurs, NULL is returned and the handle error code
|
||||
** (p->errCode) set to SQLITE_NOMEM.
|
||||
*/
|
||||
static void *recoverMalloc(sqlite3_recover *p, i64 nByte){
|
||||
void *pRet = 0;
|
||||
assert( nByte>0 );
|
||||
@ -186,6 +208,19 @@ static void *recoverMalloc(sqlite3_recover *p, i64 nByte){
|
||||
return pRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** Set the error code and error message for the recover handle passed as
|
||||
** the first argument. The error code is set to the value of parameter
|
||||
** errCode.
|
||||
**
|
||||
** Parameter zFmt must be a printf() style formatting string. The handle
|
||||
** error message is set to the result of using any trailing arguments for
|
||||
** parameter substitutions in the formatting string.
|
||||
**
|
||||
** For example:
|
||||
**
|
||||
** recoverError(p, SQLITE_ERROR, "no such table: %s", zTablename);
|
||||
*/
|
||||
static int recoverError(
|
||||
sqlite3_recover *p,
|
||||
int errCode,
|
||||
@ -204,6 +239,14 @@ static int recoverError(
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** This function is a no-op if p->errCode is initially other than SQLITE_OK.
|
||||
** In this case it returns NULL.
|
||||
**
|
||||
** Otherwise, an attempt is made to allocate and return a bitmap object
|
||||
** large enough to store a bit for all page numbers between 1 and nPg,
|
||||
** inclusive. The bitmap is initially zeroed.
|
||||
*/
|
||||
static RecoverBitmap *recoverBitmapAlloc(sqlite3_recover *p, i64 nPg){
|
||||
int nElem = (nPg+1+31) / 32;
|
||||
int nByte = sizeof(RecoverBitmap) + nElem*sizeof(u32);
|
||||
@ -215,10 +258,16 @@ static RecoverBitmap *recoverBitmapAlloc(sqlite3_recover *p, i64 nPg){
|
||||
return pRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** Free a bitmap object allocated by recoverBitmapAlloc().
|
||||
*/
|
||||
static void recoverBitmapFree(RecoverBitmap *pMap){
|
||||
sqlite3_free(pMap);
|
||||
}
|
||||
|
||||
/*
|
||||
** Set the bit associated with page iPg in bitvec pMap.
|
||||
*/
|
||||
static void recoverBitmapSet(RecoverBitmap *pMap, i64 iPg){
|
||||
if( iPg<=pMap->nPg ){
|
||||
int iElem = (iPg / 32);
|
||||
@ -227,6 +276,10 @@ static void recoverBitmapSet(RecoverBitmap *pMap, i64 iPg){
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Query bitmap object pMap for the state of the bit associated with page
|
||||
** iPg. Return 1 if it is set, or 0 otherwise.
|
||||
*/
|
||||
static int recoverBitmapQuery(RecoverBitmap *pMap, i64 iPg){
|
||||
int ret = 1;
|
||||
if( iPg<=pMap->nPg ){
|
||||
@ -237,11 +290,24 @@ static int recoverBitmapQuery(RecoverBitmap *pMap, i64 iPg){
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Set the recover handle error to the error code and message returned by
|
||||
** calling sqlite3_errcode() and sqlite3_errmsg(), respectively, on database
|
||||
** handle db.
|
||||
*/
|
||||
static int recoverDbError(sqlite3_recover *p, sqlite3 *db){
|
||||
return recoverError(p, sqlite3_errcode(db), "%s", sqlite3_errmsg(db));
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is a no-op if recover handle p already contains an error
|
||||
** (if p->errCode!=SQLITE_OK).
|
||||
**
|
||||
** Otherwise, it attempts to prepare the SQL statement in zSql against
|
||||
** database handle db. If successful, the statement handle is returned.
|
||||
** Or, if an error occurs, NULL is returned and an error left in the
|
||||
** recover handle.
|
||||
*/
|
||||
static sqlite3_stmt *recoverPrepare(
|
||||
sqlite3_recover *p,
|
||||
sqlite3 *db,
|
||||
@ -257,7 +323,15 @@ static sqlite3_stmt *recoverPrepare(
|
||||
}
|
||||
|
||||
/*
|
||||
** Create a prepared statement using printf-style arguments for the SQL.
|
||||
** This function is a no-op if recover handle p already contains an error
|
||||
** (if p->errCode!=SQLITE_OK).
|
||||
**
|
||||
** Otherwise, argument zFmt is used as a printf() style format string,
|
||||
** along with any trailing arguments, to create an SQL statement. This
|
||||
** SQL statement is prepared against database handle db and, if successful,
|
||||
** the statment handle returned. Or, if an error occurs - either during
|
||||
** the printf() formatting or when preparing the resulting SQL - an
|
||||
** error code and message are left in the recover handle.
|
||||
*/
|
||||
static sqlite3_stmt *recoverPreparePrintf(
|
||||
sqlite3_recover *p,
|
||||
@ -281,7 +355,15 @@ static sqlite3_stmt *recoverPreparePrintf(
|
||||
return pStmt;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Reset SQLite statement handle pStmt. If the call to sqlite3_reset()
|
||||
** indicates that an error occurred, and there is not already an error
|
||||
** in the recover handle passed as the first argument, set the error
|
||||
** code and error message appropriately.
|
||||
**
|
||||
** This function returns a copy of the statement handle pointer passed
|
||||
** as the second argument.
|
||||
*/
|
||||
static sqlite3_stmt *recoverReset(sqlite3_recover *p, sqlite3_stmt *pStmt){
|
||||
int rc = sqlite3_reset(pStmt);
|
||||
if( rc!=SQLITE_OK && p->errCode==SQLITE_OK ){
|
||||
@ -290,6 +372,12 @@ static sqlite3_stmt *recoverReset(sqlite3_recover *p, sqlite3_stmt *pStmt){
|
||||
return pStmt;
|
||||
}
|
||||
|
||||
/*
|
||||
** Finalize SQLite statement handle pStmt. If the call to sqlite3_reset()
|
||||
** indicates that an error occurred, and there is not already an error
|
||||
** in the recover handle passed as the first argument, set the error
|
||||
** code and error message appropriately.
|
||||
*/
|
||||
static void recoverFinalize(sqlite3_recover *p, sqlite3_stmt *pStmt){
|
||||
sqlite3 *db = sqlite3_db_handle(pStmt);
|
||||
int rc = sqlite3_finalize(pStmt);
|
||||
@ -298,6 +386,15 @@ static void recoverFinalize(sqlite3_recover *p, sqlite3_stmt *pStmt){
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is a no-op if recover handle p already contains an error
|
||||
** (if p->errCode!=SQLITE_OK). A copy of p->errCode is returned in this
|
||||
** case.
|
||||
**
|
||||
** Otherwise, execute SQL script zSql. If successful, return SQLITE_OK.
|
||||
** Or, if an error occurs, leave an error code and message in the recover
|
||||
** handle and return a copy of the error code.
|
||||
*/
|
||||
static int recoverExec(sqlite3_recover *p, sqlite3 *db, const char *zSql){
|
||||
if( p->errCode==SQLITE_OK ){
|
||||
int rc = sqlite3_exec(db, zSql, 0, 0, 0);
|
||||
@ -308,6 +405,20 @@ static int recoverExec(sqlite3_recover *p, sqlite3 *db, const char *zSql){
|
||||
return p->errCode;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is a no-op if recover handle p already contains an error
|
||||
** (if p->errCode!=SQLITE_OK). NULL is returned in this case.
|
||||
**
|
||||
** Otherwise, an attempt is made to interpret zFmt as a printf() style
|
||||
** formatting string and the result of using the trailing arguments for
|
||||
** parameter substitution with it written into a buffer obtained from
|
||||
** sqlite3_malloc(). If successful, a pointer to the buffer is returned.
|
||||
** It is the responsibility of the caller to eventually free the buffer
|
||||
** using sqlite3_free().
|
||||
**
|
||||
** Or, if an error occurs, an error code and message is left in the recover
|
||||
** handle and NULL returned.
|
||||
*/
|
||||
static char *recoverMPrintf(sqlite3_recover *p, const char *zFmt, ...){
|
||||
va_list ap;
|
||||
char *z;
|
||||
@ -324,9 +435,13 @@ static char *recoverMPrintf(sqlite3_recover *p, const char *zFmt, ...){
|
||||
}
|
||||
|
||||
/*
|
||||
** Execute "PRAGMA page_count" against the input database. If successful,
|
||||
** return the integer result. Or, if an error occurs, leave an error code
|
||||
** and error message in the sqlite3_recover handle.
|
||||
** This function is a no-op if recover handle p already contains an error
|
||||
** (if p->errCode!=SQLITE_OK). Zero is returned in this case.
|
||||
**
|
||||
** Otherwise, execute "PRAGMA page_count" against the input database. If
|
||||
** successful, return the integer result. Or, if an error occurs, leave an
|
||||
** error code and error message in the sqlite3_recover handle and return
|
||||
** zero.
|
||||
*/
|
||||
static i64 recoverPageCount(sqlite3_recover *p){
|
||||
i64 nPg = 0;
|
||||
@ -342,10 +457,12 @@ static i64 recoverPageCount(sqlite3_recover *p){
|
||||
}
|
||||
|
||||
/*
|
||||
** Scalar function "read_i32". The first argument to this function
|
||||
** must be a blob. The second a non-negative integer. This function
|
||||
** reads and returns a 32-bit big-endian integer from byte
|
||||
** Implementation of SQL scalar function "read_i32". The first argument to
|
||||
** this function must be a blob. The second a non-negative integer. This
|
||||
** function reads and returns a 32-bit big-endian integer from byte
|
||||
** offset (4*<arg2>) of the blob.
|
||||
**
|
||||
** SELECT read_i32(<blob>, <idx>)
|
||||
*/
|
||||
static void recoverReadI32(
|
||||
sqlite3_context *context,
|
||||
@ -372,7 +489,16 @@ static void recoverReadI32(
|
||||
}
|
||||
|
||||
/*
|
||||
** SELECT page_is_used(pgno);
|
||||
** Implementation of SQL scalar function "page_is_used". This function
|
||||
** is used as part of the procedure for locating orphan rows for the
|
||||
** lost-and-found table, and it depends on those routines having populated
|
||||
** the sqlite3_recover.pUsed variable.
|
||||
**
|
||||
** The only argument to this function is a page-number. It returns true
|
||||
** if the page has already been used somehow during data recovery, or false
|
||||
** otherwise.
|
||||
**
|
||||
** SELECT page_is_used(<pgno>);
|
||||
*/
|
||||
static void recoverPageIsUsed(
|
||||
sqlite3_context *pCtx,
|
||||
@ -394,7 +520,7 @@ static void recoverPageIsUsed(
|
||||
** sqlite_dbdata and sqlite_dbptr virtual table modules to access pages
|
||||
** of the database being recovered.
|
||||
**
|
||||
** This function always takes a single integer argument. If the arguement
|
||||
** This function always takes a single integer argument. If the argument
|
||||
** is zero, then the value returned is the number of pages in the db being
|
||||
** recovered. If the argument is greater than zero, it is a page number.
|
||||
** The value returned in this case is an SQL blob containing the data for
|
||||
@ -462,18 +588,17 @@ static const char *recoverUnusedString(
|
||||
return zBuf;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Scalar function "escape_crnl". The argument passed to this function is the
|
||||
** output of built-in function quote(). If the first character of the input is
|
||||
** "'", indicating that the value passed to quote() was a text value, then this
|
||||
** function searches the input for "\n" and "\r" characters and adds a wrapper
|
||||
** similar to the following:
|
||||
** Implementation of scalar SQL function "escape_crnl". The argument passed to
|
||||
** this function is the output of built-in function quote(). If the first
|
||||
** character of the input is "'", indicating that the value passed to quote()
|
||||
** was a text value, then this function searches the input for "\n" and "\r"
|
||||
** characters and adds a wrapper similar to the following:
|
||||
**
|
||||
** replace(replace(<input>, '\n', char(10), '\r', char(13));
|
||||
**
|
||||
** Or, if the first character of the input is not "'", then a copy
|
||||
** of the input is returned.
|
||||
** Or, if the first character of the input is not "'", then a copy of the input
|
||||
** is returned.
|
||||
*/
|
||||
static void recoverEscapeCrnl(
|
||||
sqlite3_context *context,
|
||||
@ -552,12 +677,20 @@ static void recoverEscapeCrnl(
|
||||
sqlite3_result_value(context, argv[0]);
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
int sqlite3_dbdata_init(sqlite3*, char**, const sqlite3_api_routines*);
|
||||
|
||||
/*
|
||||
** This function is a no-op if recover handle p already contains an error
|
||||
** (if p->errCode!=SQLITE_OK). A copy of the error code is returned in
|
||||
** this case.
|
||||
**
|
||||
** Otherwise, an attempt is made to open the output database, attach
|
||||
** and create the schema of the temporary database used to store
|
||||
** intermediate data, and to register all required user functions and
|
||||
** virtual table modules with the output handle.
|
||||
**
|
||||
** If no error occurs, SQLITE_OK is returned. Otherwise, an error code
|
||||
** and error message are left in the recover handle and a copy of the
|
||||
** error code returned.
|
||||
*/
|
||||
static int recoverOpenOutput(sqlite3_recover *p){
|
||||
struct Func {
|
||||
const char *zName;
|
||||
@ -576,17 +709,19 @@ static int recoverOpenOutput(sqlite3_recover *p){
|
||||
|
||||
assert( p->dbOut==0 );
|
||||
|
||||
if( sqlite3_open_v2(p->zUri, &db, flags, 0) ){
|
||||
recoverDbError(p, db);
|
||||
}else{
|
||||
char *zSql = recoverMPrintf(p, "ATTACH %Q AS recovery;", p->zStateDb);
|
||||
recoverExec(p, db, zSql);
|
||||
recoverExec(p, db,
|
||||
"PRAGMA writable_schema = 1;"
|
||||
"CREATE TABLE recovery.map(pgno INTEGER PRIMARY KEY, parent INT);"
|
||||
"CREATE TABLE recovery.schema(type, name, tbl_name, rootpage, sql);"
|
||||
);
|
||||
sqlite3_free(zSql);
|
||||
if( p->errCode==SQLITE_OK ){
|
||||
if( sqlite3_open_v2(p->zUri, &db, flags, 0) ){
|
||||
recoverDbError(p, db);
|
||||
}else{
|
||||
char *zSql = recoverMPrintf(p, "ATTACH %Q AS recovery;", p->zStateDb);
|
||||
recoverExec(p, db, zSql);
|
||||
recoverExec(p, db,
|
||||
"PRAGMA writable_schema = 1;"
|
||||
"CREATE TABLE recovery.map(pgno INTEGER PRIMARY KEY, parent INT);"
|
||||
"CREATE TABLE recovery.schema(type, name, tbl_name, rootpage, sql);"
|
||||
);
|
||||
sqlite3_free(zSql);
|
||||
}
|
||||
}
|
||||
|
||||
/* Register the sqlite_dbdata and sqlite_dbptr virtual table modules.
|
||||
@ -624,6 +759,19 @@ static int recoverOpenOutput(sqlite3_recover *p){
|
||||
return p->errCode;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is a no-op if recover handle p already contains an error
|
||||
** (if p->errCode!=SQLITE_OK). A copy of the error code is returned in
|
||||
** this case.
|
||||
**
|
||||
** Otherwise, attempt to populate temporary table "recovery.schema" with the
|
||||
** parts of the database schema that can be extracted from the input database.
|
||||
**
|
||||
** If no error occurs, SQLITE_OK is returned. Otherwise, an error code
|
||||
** and error message are left in the recover handle and a copy of the
|
||||
** error code returned. It is not considered an error if part of all of
|
||||
** the database schema cannot be recovered due to corruption.
|
||||
*/
|
||||
static int recoverCacheSchema(sqlite3_recover *p){
|
||||
return recoverExec(p, p->dbOut,
|
||||
"WITH RECURSIVE pages(p) AS ("
|
||||
@ -643,7 +791,24 @@ static int recoverCacheSchema(sqlite3_recover *p){
|
||||
);
|
||||
}
|
||||
|
||||
static void recoverAddTable(sqlite3_recover *p, const char *zName, i64 iRoot){
|
||||
/*
|
||||
** This function is a no-op if recover handle p already contains an error
|
||||
** (if p->errCode!=SQLITE_OK).
|
||||
**
|
||||
** Otherwise, argument zName must be the name of a table that has just been
|
||||
** created in the output database. This function queries the output db
|
||||
** for the schema of said table, and creates a RecoverTable object to
|
||||
** store the schema in memory. The new RecoverTable object is linked into
|
||||
** the list at sqlite3_recover.pTblList.
|
||||
**
|
||||
** Parameter iRoot must be the root page of table zName in the INPUT
|
||||
** database.
|
||||
*/
|
||||
static void recoverAddTable(
|
||||
sqlite3_recover *p,
|
||||
const char *zName, /* Name of table created in output db */
|
||||
i64 iRoot /* Root page of same table in INPUT db */
|
||||
){
|
||||
sqlite3_stmt *pStmt = recoverPreparePrintf(p, p->dbOut,
|
||||
"PRAGMA table_xinfo(%Q)", zName
|
||||
);
|
||||
@ -727,6 +892,15 @@ static void recoverAddTable(sqlite3_recover *p, const char *zName, i64 iRoot){
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** If this recover handle is not in SQL callback mode (i.e. was not created
|
||||
** using sqlite3_recover_init_sql()) of if an error has already occurred,
|
||||
** this function is a no-op. Otherwise, issue a callback with SQL statement
|
||||
** zSql as the parameter.
|
||||
**
|
||||
** If the callback returns non-zero, set the recover handle error code to
|
||||
** the value returned (so that the caller will abandon processing).
|
||||
*/
|
||||
static void recoverSqlCallback(sqlite3_recover *p, const char *zSql){
|
||||
if( p->errCode==SQLITE_OK && p->xSql ){
|
||||
int res = p->xSql(p->pSqlCtx, zSql);
|
||||
@ -737,7 +911,23 @@ static void recoverSqlCallback(sqlite3_recover *p, const char *zSql){
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is called after recoverCacheSchema() has cached those parts
|
||||
** of the input database schema that could be recovered in temporary table
|
||||
** "recovery.schema". This function creates in the output database copies
|
||||
** of all parts of that schema that must be created before the tables can
|
||||
** be populated. Specifically, this means:
|
||||
**
|
||||
** * all tables that are not VIRTUAL, and
|
||||
** * UNIQUE indexes.
|
||||
**
|
||||
** If the recovery handle uses SQL callbacks, then callbacks containing
|
||||
** the associated "CREATE TABLE" and "CREATE INDEX" statements are made.
|
||||
**
|
||||
** Additionally, records are added to the sqlite_schema table of the
|
||||
** output database for any VIRTUAL tables. The CREATE VIRTUAL TABLE
|
||||
** records are written directly to sqlite_schema, not actually executed.
|
||||
** If the handle is in SQL callback mode, then callbacks are invoked
|
||||
** with equivalent SQL statements.
|
||||
*/
|
||||
static int recoverWriteSchema1(sqlite3_recover *p){
|
||||
sqlite3_stmt *pSelect = 0;
|
||||
@ -800,6 +990,19 @@ static int recoverWriteSchema1(sqlite3_recover *p){
|
||||
return p->errCode;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is called after the output database has been populated. It
|
||||
** adds all recovered schema elements that were not created in the output
|
||||
** database by recoverWriteSchema1() - everything except for tables and
|
||||
** UNIQUE indexes. Specifically:
|
||||
**
|
||||
** * views,
|
||||
** * triggers,
|
||||
** * non-UNIQUE indexes.
|
||||
**
|
||||
** If the recover handle is in SQL callback mode, then equivalent callbacks
|
||||
** are issued to create the schema elements.
|
||||
*/
|
||||
static int recoverWriteSchema2(sqlite3_recover *p){
|
||||
sqlite3_stmt *pSelect = 0;
|
||||
|
||||
@ -825,6 +1028,41 @@ static int recoverWriteSchema2(sqlite3_recover *p){
|
||||
return p->errCode;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is a no-op if recover handle p already contains an error
|
||||
** (if p->errCode!=SQLITE_OK). In this case it returns NULL.
|
||||
**
|
||||
** Otherwise, if the recover handle is configured to create an output
|
||||
** database (was created by sqlite3_recover_init()), then this function
|
||||
** prepares and returns an SQL statement to INSERT a new record into table
|
||||
** pTab, assuming the first nField fields of a record extracted from disk
|
||||
** are valid.
|
||||
**
|
||||
** For example, if table pTab is:
|
||||
**
|
||||
** CREATE TABLE name(a, b GENERATED ALWAYS AS (a+1) STORED, c, d, e);
|
||||
**
|
||||
** And nField is 4, then the SQL statement prepared and returned is:
|
||||
**
|
||||
** INSERT INTO (a, c, d) VALUES (?1, ?2, ?3);
|
||||
**
|
||||
** In this case even though 4 values were extracted from the input db,
|
||||
** only 3 are written to the output, as the generated STORED column
|
||||
** cannot be written.
|
||||
**
|
||||
** If the recover handle is in SQL callback mode, then the SQL statement
|
||||
** prepared is such that evaluating it returns a single row containing
|
||||
** a single text value - itself an SQL statement similar to the above,
|
||||
** except with SQL literals in place of the variables. For example:
|
||||
**
|
||||
** SELECT 'INSERT INTO (a, c, d) VALUES ('
|
||||
** || quote(?1) || ', '
|
||||
** || quote(?2) || ', '
|
||||
** || quote(?3) || ')';
|
||||
**
|
||||
** In either case, it is the responsibility of the caller to eventually
|
||||
** free the statement handle using sqlite3_finalize().
|
||||
*/
|
||||
static sqlite3_stmt *recoverInsertStmt(
|
||||
sqlite3_recover *p,
|
||||
RecoverTable *pTab,
|
||||
@ -855,7 +1093,6 @@ static sqlite3_stmt *recoverInsertStmt(
|
||||
zSep = ", ";
|
||||
}
|
||||
|
||||
|
||||
for(ii=0; ii<nField; ii++){
|
||||
int eHidden = pTab->aCol[ii].eHidden;
|
||||
if( eHidden!=RECOVER_EHIDDEN_VIRTUAL
|
||||
@ -893,6 +1130,11 @@ static sqlite3_stmt *recoverInsertStmt(
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Search the list of RecoverTable objects at p->pTblList for one that
|
||||
** has root page iRoot in the input database. If such an object is found,
|
||||
** return a pointer to it. Otherwise, return NULL.
|
||||
*/
|
||||
static RecoverTable *recoverFindTable(sqlite3_recover *p, u32 iRoot){
|
||||
RecoverTable *pRet = 0;
|
||||
for(pRet=p->pTblList; pRet && pRet->iRoot!=iRoot; pRet=pRet->pNext);
|
||||
@ -1007,6 +1249,9 @@ static sqlite3_stmt *recoverLostAndFoundInsert(
|
||||
return pRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** Helper function for recoverLostAndFound().
|
||||
*/
|
||||
static void recoverLostAndFoundPopulate(
|
||||
sqlite3_recover *p,
|
||||
sqlite3_stmt *pInsert,
|
||||
@ -1096,6 +1341,12 @@ static void recoverLostAndFoundPopulate(
|
||||
sqlite3_free(apVal);
|
||||
}
|
||||
|
||||
/*
|
||||
** This function searches for orphaned rows in the input database. If
|
||||
** any are found, it creates the lost-and-found table in the output
|
||||
** db and writes all orphaned rows to it. Or, if the recover handle is
|
||||
** in SQL callback mode, issues equivalent callbacks.
|
||||
*/
|
||||
static int recoverLostAndFound(sqlite3_recover *p){
|
||||
i64 nPg = 0;
|
||||
RecoverBitmap *pMap = 0;
|
||||
@ -1198,12 +1449,18 @@ static int recoverLostAndFound(sqlite3_recover *p){
|
||||
recoverFinalize(p, pInsert);
|
||||
sqlite3_free(zTab);
|
||||
}
|
||||
|
||||
recoverBitmapFree(pMap);
|
||||
p->pUsed = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** For each table in the recovered schema, this function extracts as much
|
||||
** data as possible from the output database and writes it to the input
|
||||
** database. Or, if the recover handle is in SQL callback mode, issues
|
||||
** equivalent callbacks.
|
||||
**
|
||||
** It does not recover "orphaned" data into the lost-and-found table.
|
||||
** See recoverLostAndFound() for that.
|
||||
*/
|
||||
static int recoverWriteData(sqlite3_recover *p){
|
||||
RecoverTable *pTbl;
|
||||
int nMax = 0;
|
||||
@ -1349,12 +1606,79 @@ static int recoverWriteData(sqlite3_recover *p){
|
||||
return p->errCode;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function does the work of sqlite3_recover_run(). It is assumed that
|
||||
** no error has occurred when this is called. If an error occurs during
|
||||
** the recovery operation, an error code and error message are left in
|
||||
** the recovery handle.
|
||||
*/
|
||||
static void recoverRun(sqlite3_recover *p){
|
||||
RecoverTable *pTab = 0;
|
||||
RecoverTable *pNext = 0;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
assert( p->errCode==SQLITE_OK );
|
||||
assert( p->bRun==0 );
|
||||
p->bRun = 1;
|
||||
|
||||
recoverSqlCallback(p, "BEGIN");
|
||||
recoverSqlCallback(p, "PRAGMA writable_schema = on");
|
||||
|
||||
/* Open the output database. And register required virtual tables and
|
||||
** user functions with the new handle. */
|
||||
recoverOpenOutput(p);
|
||||
|
||||
/* Open transactions on both the input and output databases. */
|
||||
recoverExec(p, p->dbIn, "BEGIN");
|
||||
recoverExec(p, p->dbOut, "BEGIN");
|
||||
|
||||
recoverCacheSchema(p);
|
||||
recoverWriteSchema1(p);
|
||||
recoverWriteData(p);
|
||||
if( p->zLostAndFound ) recoverLostAndFound(p);
|
||||
recoverWriteSchema2(p);
|
||||
|
||||
/* If no error has occurred, commit the write transaction on the output
|
||||
** database. Then end the read transaction on the input database, regardless
|
||||
** of whether or not prior errors have occurred. */
|
||||
recoverExec(p, p->dbOut, "COMMIT");
|
||||
rc = sqlite3_exec(p->dbIn, "END", 0, 0, 0);
|
||||
if( p->errCode==SQLITE_OK ) p->errCode = rc;
|
||||
|
||||
recoverSqlCallback(p, "PRAGMA writable_schema = off");
|
||||
recoverSqlCallback(p, "COMMIT");
|
||||
|
||||
/* Clean up various resources allocated by this function. */
|
||||
for(pTab=p->pTblList; pTab; pTab=pNext){
|
||||
pNext = pTab->pNext;
|
||||
sqlite3_free(pTab);
|
||||
}
|
||||
p->pTblList = 0;
|
||||
sqlite3_finalize(p->pGetPage);
|
||||
p->pGetPage = 0;
|
||||
recoverBitmapFree(p->pUsed);
|
||||
p->pUsed = 0;
|
||||
sqlite3_close(p->dbOut);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** This is a worker function that does the heavy lifting for both init
|
||||
** functions:
|
||||
**
|
||||
** sqlite3_recover_init()
|
||||
** sqlite3_recover_init_sql()
|
||||
**
|
||||
** All this function does is allocate space for the recover handle and
|
||||
** take copies of the input parameters. All the real work is done within
|
||||
** sqlite3_recover_run().
|
||||
*/
|
||||
sqlite3_recover *recoverInit(
|
||||
sqlite3* db,
|
||||
const char *zDb,
|
||||
const char *zUri,
|
||||
int (*xSql)(void*, const char*),
|
||||
void *pSqlCtx
|
||||
const char *zUri, /* Output URI for _recover_init() */
|
||||
int (*xSql)(void*, const char*),/* SQL callback for _recover_init_sql() */
|
||||
void *pSqlCtx /* Context arg for _recover_init_sql() */
|
||||
){
|
||||
sqlite3_recover *pRet = 0;
|
||||
int nDb = 0;
|
||||
@ -1384,6 +1708,10 @@ sqlite3_recover *recoverInit(
|
||||
return pRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** Initialize a recovery handle that creates a new database containing
|
||||
** the recovered data.
|
||||
*/
|
||||
sqlite3_recover *sqlite3_recover_init(
|
||||
sqlite3* db,
|
||||
const char *zDb,
|
||||
@ -1392,6 +1720,10 @@ sqlite3_recover *sqlite3_recover_init(
|
||||
return recoverInit(db, zDb, zUri, 0, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
** Initialize a recovery handle that returns recovered data in the
|
||||
** form of SQL statements via a callback.
|
||||
*/
|
||||
sqlite3_recover *sqlite3_recover_init_sql(
|
||||
sqlite3* db,
|
||||
const char *zDb,
|
||||
@ -1401,13 +1733,23 @@ sqlite3_recover *sqlite3_recover_init_sql(
|
||||
return recoverInit(db, zDb, "", xSql, pSqlCtx);
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the handle error message, if any.
|
||||
*/
|
||||
const char *sqlite3_recover_errmsg(sqlite3_recover *p){
|
||||
return p ? p->zErrMsg : "not an error";
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the handle error code.
|
||||
*/
|
||||
int sqlite3_recover_errcode(sqlite3_recover *p){
|
||||
return p ? p->errCode : SQLITE_NOMEM;
|
||||
}
|
||||
|
||||
/*
|
||||
** Configure the handle.
|
||||
*/
|
||||
int sqlite3_recover_config(sqlite3_recover *p, int op, void *pArg){
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
@ -1443,57 +1785,27 @@ int sqlite3_recover_config(sqlite3_recover *p, int op, void *pArg){
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void recoverStep(sqlite3_recover *p){
|
||||
RecoverTable *pTab = 0;
|
||||
RecoverTable *pNext = 0;
|
||||
int rc = SQLITE_OK;
|
||||
assert( p->errCode==SQLITE_OK );
|
||||
|
||||
recoverSqlCallback(p, "BEGIN");
|
||||
recoverSqlCallback(p, "PRAGMA writable_schema = on");
|
||||
|
||||
/* Open the output database. And register required virtual tables and
|
||||
** user functions with the new handle. */
|
||||
recoverOpenOutput(p);
|
||||
|
||||
/* Open transactions on both the input and output databases. */
|
||||
recoverExec(p, p->dbIn, "BEGIN");
|
||||
recoverExec(p, p->dbOut, "BEGIN");
|
||||
|
||||
recoverCacheSchema(p);
|
||||
recoverWriteSchema1(p);
|
||||
recoverWriteData(p);
|
||||
if( p->zLostAndFound ) recoverLostAndFound(p);
|
||||
recoverWriteSchema2(p);
|
||||
|
||||
/* If no error has occurred, commit the write transaction on the output
|
||||
** database. Then end the read transaction on the input database, regardless
|
||||
** of whether or not prior errors have occurred. */
|
||||
recoverExec(p, p->dbOut, "COMMIT");
|
||||
rc = sqlite3_exec(p->dbIn, "END", 0, 0, 0);
|
||||
if( p->errCode==SQLITE_OK ) p->errCode = rc;
|
||||
|
||||
recoverSqlCallback(p, "PRAGMA writable_schema = off");
|
||||
recoverSqlCallback(p, "COMMIT");
|
||||
|
||||
for(pTab=p->pTblList; pTab; pTab=pNext){
|
||||
pNext = pTab->pNext;
|
||||
sqlite3_free(pTab);
|
||||
}
|
||||
p->pTblList = 0;
|
||||
|
||||
sqlite3_finalize(p->pGetPage);
|
||||
sqlite3_close(p->dbOut);
|
||||
p->pGetPage = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Do the configured recovery operation. Return SQLITE_OK if successful, or
|
||||
** else an SQLite error code.
|
||||
*/
|
||||
int sqlite3_recover_run(sqlite3_recover *p){
|
||||
if( p && p->errCode==SQLITE_OK ){
|
||||
recoverStep(p);
|
||||
if( p ){
|
||||
if( p->bRun ) return SQLITE_MISUSE; /* Has already run */
|
||||
if( p->errCode==SQLITE_OK ) recoverRun(p);
|
||||
}
|
||||
return p ? p->errCode : SQLITE_NOMEM;
|
||||
}
|
||||
|
||||
/*
|
||||
** Free all resources associated with the recover handle passed as the only
|
||||
** argument. The results of using a handle with any sqlite3_recover_**
|
||||
** API function after it has been passed to this function are undefined.
|
||||
**
|
||||
** A copy of the value returned by the first call made to sqlite3_recover_run()
|
||||
** on this handle is returned, or SQLITE_OK if sqlite3_recover_run() has
|
||||
** not been called on this handle.
|
||||
*/
|
||||
int sqlite3_recover_finish(sqlite3_recover *p){
|
||||
int rc = p->errCode;
|
||||
sqlite3_free(p->zErrMsg);
|
||||
|
12
manifest
12
manifest
@ -1,5 +1,5 @@
|
||||
C Further\sfixes\sto\scomments\sin\ssqlite3recover.h.\sAlso\srework\ssome\sdata\sstructures\sin\ssqlite3recover.c.
|
||||
D 2022-09-09T20:44:56.135
|
||||
C Rework\srecover\sextension\scode\sfor\sreadability.
|
||||
D 2022-09-10T20:01:49.308
|
||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
|
||||
@ -392,7 +392,7 @@ F ext/recover/recover_common.tcl 6679af7dffc858e345053a91c9b0a897595b4a13007acef
|
||||
F ext/recover/recoverclobber.test e6537ebf99f57bfff6cca59550b5f4278319b57a89865abb98d755a8fd561d84
|
||||
F ext/recover/recoverold.test f368a6ae2db12b6017257b332a19ab5df527f4061e43f12f5c85d8e2b236f074
|
||||
F ext/recover/recoverrowid.test ec4436cd69e6cdacb48dd2963ff6dd9dbd5fe648376de5e7c0c2f4f6cbacb417
|
||||
F ext/recover/sqlite3recover.c e8d0eae7da7ba24e733f6247082c12d7a030ce794eb5c42d64993f55c12882f5
|
||||
F ext/recover/sqlite3recover.c 4ed53fd33639ede83505f4397b8e1e46b6c0c5a5188c42f28f8372487141170a
|
||||
F ext/recover/sqlite3recover.h 81108efb8c4618d3d9c6da4df785212b0e4501aa0d25edfc463405fe839a6640
|
||||
F ext/recover/test_recover.c ed8d0cc8703ab29cf562f793623b045de109b7937f254108ff4132f35abb37fb
|
||||
F ext/repair/README.md 92f5e8aae749a4dae14f02eea8e1bb42d4db2b6ce5e83dbcdd6b1446997e0c15
|
||||
@ -2007,8 +2007,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
|
||||
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||
P 5f2d5ccd56c06c3468377126acfd4be39b79b05bb6fb09b674b2e185df143aa3
|
||||
R d4558eaeefd2a718275b323dc4307f9b
|
||||
P 599d1f8ec2f9e24924a6f9e66c85664360c7b95531b07a4efe1dd8c096b3fc99
|
||||
R cd56b05532a91c08b9e673fffba829f5
|
||||
U dan
|
||||
Z 6e64e7331e56e0c44a533586bb618920
|
||||
Z 624cfda4b7c658530a41a87671068e55
|
||||
# Remove this line to create a well-formed Fossil manifest.
|
||||
|
@ -1 +1 @@
|
||||
599d1f8ec2f9e24924a6f9e66c85664360c7b95531b07a4efe1dd8c096b3fc99
|
||||
1a2540960e40e3c8c622448fd3862e249bd463c29ae4ce5e39942e942533f60a
|
Loading…
x
Reference in New Issue
Block a user