Add the "recover" extension in ext/recover/, for salvaging data from corrupt databases.
FossilOrigin-Name: a820792548da596ed0f8ab3fd1c04ec16647abbe51a2a5aac9e17c4f58be6d97
This commit is contained in:
commit
387f468591
@ -434,6 +434,9 @@ TESTSRC = \
|
||||
$(TOP)/ext/fts3/fts3_term.c \
|
||||
$(TOP)/ext/fts3/fts3_test.c \
|
||||
$(TOP)/ext/session/test_session.c \
|
||||
$(TOP)/ext/recover/sqlite3recover.c \
|
||||
$(TOP)/ext/recover/dbdata.c \
|
||||
$(TOP)/ext/recover/test_recover.c \
|
||||
$(TOP)/ext/rbu/test_rbu.c
|
||||
|
||||
# Statically linked extensions
|
||||
@ -1108,6 +1111,9 @@ SHELL_SRC = \
|
||||
$(TOP)/ext/expert/sqlite3expert.h \
|
||||
$(TOP)/ext/misc/zipfile.c \
|
||||
$(TOP)/ext/misc/memtrace.c \
|
||||
$(TOP)/ext/recover/dbdata.c \
|
||||
$(TOP)/ext/recover/sqlite3recover.c \
|
||||
$(TOP)/ext/recover/sqlite3recover.h \
|
||||
$(TOP)/src/test_windirent.c
|
||||
|
||||
shell.c: $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl
|
||||
|
@ -1588,6 +1588,9 @@ TESTEXT = \
|
||||
$(TOP)\ext\misc\unionvtab.c \
|
||||
$(TOP)\ext\misc\wholenumber.c \
|
||||
$(TOP)\ext\rtree\test_rtreedoc.c \
|
||||
$(TOP)\ext\recover\sqlite3recover.c \
|
||||
$(TOP)\ext\recover\test_recover.c \
|
||||
$(TOP)\ext\recover\dbdata.c \
|
||||
fts5.c
|
||||
|
||||
# If use of zlib is enabled, add the "zipfile.c" source file.
|
||||
@ -2228,6 +2231,9 @@ SHELL_SRC = \
|
||||
$(TOP)\ext\expert\sqlite3expert.c \
|
||||
$(TOP)\ext\expert\sqlite3expert.h \
|
||||
$(TOP)\ext\misc\memtrace.c \
|
||||
$(TOP)/ext/recover/dbdata.c \
|
||||
$(TOP)/ext/recover/sqlite3recover.c \
|
||||
$(TOP)/ext/recover/sqlite3recover.h \
|
||||
$(TOP)\src\test_windirent.c
|
||||
|
||||
# If use of zlib is enabled, add the "zipfile.c" source file.
|
||||
|
@ -47,7 +47,7 @@
|
||||
**
|
||||
** sqlite3 *db;
|
||||
** sqlite3_open(":memory:", &db);
|
||||
** sqlite3_load_extention(db, "./cksumvfs");
|
||||
** sqlite3_load_extension(db, "./cksumvfs");
|
||||
** sqlite3_close(db);
|
||||
**
|
||||
** If this extension is compiled with -DSQLITE_CKSUMVFS_STATIC and
|
||||
|
@ -75,6 +75,7 @@
|
||||
#include "sqlite3ext.h"
|
||||
|
||||
typedef unsigned char u8;
|
||||
typedef unsigned int u32;
|
||||
|
||||
#endif
|
||||
SQLITE_EXTENSION_INIT1
|
||||
@ -107,6 +108,7 @@ struct DbdataCursor {
|
||||
int iField; /* Current field number */
|
||||
u8 *pHdrPtr;
|
||||
u8 *pPtr;
|
||||
u32 enc; /* Text encoding */
|
||||
|
||||
sqlite3_int64 iIntkey; /* Integer key value */
|
||||
};
|
||||
@ -299,14 +301,14 @@ static int dbdataClose(sqlite3_vtab_cursor *pCursor){
|
||||
/*
|
||||
** Utility methods to decode 16 and 32-bit big-endian unsigned integers.
|
||||
*/
|
||||
static unsigned int get_uint16(unsigned char *a){
|
||||
static u32 get_uint16(unsigned char *a){
|
||||
return (a[0]<<8)|a[1];
|
||||
}
|
||||
static unsigned int get_uint32(unsigned char *a){
|
||||
return ((unsigned int)a[0]<<24)
|
||||
| ((unsigned int)a[1]<<16)
|
||||
| ((unsigned int)a[2]<<8)
|
||||
| ((unsigned int)a[3]);
|
||||
static u32 get_uint32(unsigned char *a){
|
||||
return ((u32)a[0]<<24)
|
||||
| ((u32)a[1]<<16)
|
||||
| ((u32)a[2]<<8)
|
||||
| ((u32)a[3]);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -321,7 +323,7 @@ static unsigned int get_uint32(unsigned char *a){
|
||||
*/
|
||||
static int dbdataLoadPage(
|
||||
DbdataCursor *pCsr, /* Cursor object */
|
||||
unsigned int pgno, /* Page number of page to load */
|
||||
u32 pgno, /* Page number of page to load */
|
||||
u8 **ppPage, /* OUT: pointer to page buffer */
|
||||
int *pnPage /* OUT: Size of (*ppPage) in bytes */
|
||||
){
|
||||
@ -418,6 +420,7 @@ static int dbdataValueBytes(int eType){
|
||||
*/
|
||||
static void dbdataValue(
|
||||
sqlite3_context *pCtx,
|
||||
u32 enc,
|
||||
int eType,
|
||||
u8 *pData,
|
||||
int nData
|
||||
@ -462,7 +465,17 @@ static void dbdataValue(
|
||||
default: {
|
||||
int n = ((eType-12) / 2);
|
||||
if( eType % 2 ){
|
||||
sqlite3_result_text(pCtx, (const char*)pData, n, SQLITE_TRANSIENT);
|
||||
switch( enc ){
|
||||
case SQLITE_UTF16BE:
|
||||
sqlite3_result_text16be(pCtx, (void*)pData, n, SQLITE_TRANSIENT);
|
||||
break;
|
||||
case SQLITE_UTF16LE:
|
||||
sqlite3_result_text16le(pCtx, (void*)pData, n, SQLITE_TRANSIENT);
|
||||
break;
|
||||
default:
|
||||
sqlite3_result_text(pCtx, (char*)pData, n, SQLITE_TRANSIENT);
|
||||
break;
|
||||
}
|
||||
}else{
|
||||
sqlite3_result_blob(pCtx, pData, n, SQLITE_TRANSIENT);
|
||||
}
|
||||
@ -490,6 +503,7 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){
|
||||
rc = dbdataLoadPage(pCsr, pCsr->iPgno, &pCsr->aPage, &pCsr->nPage);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
if( pCsr->aPage ) break;
|
||||
if( pCsr->bOnePage ) return SQLITE_OK;
|
||||
pCsr->iPgno++;
|
||||
}
|
||||
pCsr->iCell = pTab->bPtr ? -2 : 0;
|
||||
@ -600,7 +614,7 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){
|
||||
/* Load content from overflow pages */
|
||||
if( nPayload>nLocal ){
|
||||
sqlite3_int64 nRem = nPayload - nLocal;
|
||||
unsigned int pgnoOvfl = get_uint32(&pCsr->aPage[iOff]);
|
||||
u32 pgnoOvfl = get_uint32(&pCsr->aPage[iOff]);
|
||||
while( nRem>0 ){
|
||||
u8 *aOvfl = 0;
|
||||
int nOvfl = 0;
|
||||
@ -673,6 +687,18 @@ static int dbdataEof(sqlite3_vtab_cursor *pCursor){
|
||||
return pCsr->aPage==0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return true if nul-terminated string zSchema ends in "()". Or false
|
||||
** otherwise.
|
||||
*/
|
||||
static int dbdataIsFunction(const char *zSchema){
|
||||
int n = strlen(zSchema);
|
||||
if( n>2 && zSchema[n-2]=='(' && zSchema[n-1]==')' ){
|
||||
return n-2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Determine the size in pages of database zSchema (where zSchema is
|
||||
** "main", "temp" or the name of an attached database) and set
|
||||
@ -683,10 +709,16 @@ static int dbdataDbsize(DbdataCursor *pCsr, const char *zSchema){
|
||||
DbdataTable *pTab = (DbdataTable*)pCsr->base.pVtab;
|
||||
char *zSql = 0;
|
||||
int rc, rc2;
|
||||
int nFunc = 0;
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
|
||||
zSql = sqlite3_mprintf("PRAGMA %Q.page_count", zSchema);
|
||||
if( (nFunc = dbdataIsFunction(zSchema))>0 ){
|
||||
zSql = sqlite3_mprintf("SELECT %.*s(0)", nFunc, zSchema);
|
||||
}else{
|
||||
zSql = sqlite3_mprintf("PRAGMA %Q.page_count", zSchema);
|
||||
}
|
||||
if( zSql==0 ) return SQLITE_NOMEM;
|
||||
|
||||
rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pStmt, 0);
|
||||
sqlite3_free(zSql);
|
||||
if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
|
||||
@ -697,6 +729,25 @@ static int dbdataDbsize(DbdataCursor *pCsr, const char *zSchema){
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Attempt to figure out the encoding of the database by retrieving page 1
|
||||
** and inspecting the header field. If successful, set the pCsr->enc variable
|
||||
** and return SQLITE_OK. Otherwise, return an SQLite error code.
|
||||
*/
|
||||
static int dbdataGetEncoding(DbdataCursor *pCsr){
|
||||
int rc = SQLITE_OK;
|
||||
int nPg1 = 0;
|
||||
u8 *aPg1 = 0;
|
||||
rc = dbdataLoadPage(pCsr, 1, &aPg1, &nPg1);
|
||||
assert( rc!=SQLITE_OK || nPg1==0 || nPg1>=512 );
|
||||
if( rc==SQLITE_OK && nPg1>0 ){
|
||||
pCsr->enc = get_uint32(&aPg1[56]);
|
||||
}
|
||||
sqlite3_free(aPg1);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** xFilter method for sqlite_dbdata and sqlite_dbptr.
|
||||
*/
|
||||
@ -719,14 +770,22 @@ static int dbdataFilter(
|
||||
pCsr->iPgno = sqlite3_value_int(argv[(idxNum & 0x01)]);
|
||||
pCsr->bOnePage = 1;
|
||||
}else{
|
||||
pCsr->nPage = dbdataDbsize(pCsr, zSchema);
|
||||
rc = dbdataDbsize(pCsr, zSchema);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
int nFunc = 0;
|
||||
if( pTab->pStmt ){
|
||||
pCsr->pStmt = pTab->pStmt;
|
||||
pTab->pStmt = 0;
|
||||
}else if( (nFunc = dbdataIsFunction(zSchema))>0 ){
|
||||
char *zSql = sqlite3_mprintf("SELECT %.*s(?2)", nFunc, zSchema);
|
||||
if( zSql==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pCsr->pStmt, 0);
|
||||
sqlite3_free(zSql);
|
||||
}
|
||||
}else{
|
||||
rc = sqlite3_prepare_v2(pTab->db,
|
||||
"SELECT data FROM sqlite_dbpage(?) WHERE pgno=?", -1,
|
||||
@ -739,13 +798,20 @@ static int dbdataFilter(
|
||||
}else{
|
||||
pTab->base.zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(pTab->db));
|
||||
}
|
||||
|
||||
/* Try to determine the encoding of the db by inspecting the header
|
||||
** field on page 1. */
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = dbdataGetEncoding(pCsr);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = dbdataNext(pCursor);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
** Return a column for the sqlite_dbdata or sqlite_dbptr table.
|
||||
*/
|
||||
static int dbdataColumn(
|
||||
@ -793,7 +859,8 @@ static int dbdataColumn(
|
||||
sqlite3_int64 iType;
|
||||
dbdataGetVarintU32(pCsr->pHdrPtr, &iType);
|
||||
dbdataValue(
|
||||
ctx, iType, pCsr->pPtr, &pCsr->pRec[pCsr->nRec] - pCsr->pPtr
|
||||
ctx, pCsr->enc, iType, pCsr->pPtr,
|
||||
&pCsr->pRec[pCsr->nRec] - pCsr->pPtr
|
||||
);
|
||||
}
|
||||
break;
|
250
ext/recover/recover1.test
Normal file
250
ext/recover/recover1.test
Normal file
@ -0,0 +1,250 @@
|
||||
# 2022 August 28
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] recover_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
|
||||
set testprefix recover1
|
||||
|
||||
|
||||
|
||||
proc compare_result {db1 db2 sql} {
|
||||
set r1 [$db1 eval $sql]
|
||||
set r2 [$db2 eval $sql]
|
||||
if {$r1 != $r2} {
|
||||
puts "r1: $r1"
|
||||
puts "r2: $r2"
|
||||
error "mismatch for $sql"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
proc compare_dbs {db1 db2} {
|
||||
compare_result $db1 $db2 "SELECT sql FROM sqlite_master ORDER BY 1"
|
||||
foreach tbl [$db1 eval {SELECT name FROM sqlite_master WHERE type='table'}] {
|
||||
compare_result $db1 $db2 "SELECT * FROM $tbl"
|
||||
}
|
||||
|
||||
compare_result $db1 $db2 "PRAGMA page_size"
|
||||
compare_result $db1 $db2 "PRAGMA auto_vacuum"
|
||||
compare_result $db1 $db2 "PRAGMA encoding"
|
||||
compare_result $db1 $db2 "PRAGMA user_version"
|
||||
compare_result $db1 $db2 "PRAGMA application_id"
|
||||
}
|
||||
|
||||
proc do_recover_test {tn} {
|
||||
forcedelete test.db2
|
||||
forcedelete rstate.db
|
||||
|
||||
uplevel [list do_test $tn.1 {
|
||||
set R [sqlite3_recover_init db main test.db2]
|
||||
$R config testdb rstate.db
|
||||
$R run
|
||||
$R finish
|
||||
} {}]
|
||||
|
||||
sqlite3 db2 test.db2
|
||||
uplevel [list do_test $tn.2 [list compare_dbs db db2] {}]
|
||||
db2 close
|
||||
|
||||
forcedelete test.db2
|
||||
forcedelete rstate.db
|
||||
|
||||
uplevel [list do_test $tn.3 {
|
||||
set ::sqlhook [list]
|
||||
set R [sqlite3_recover_init_sql db main my_sql_hook]
|
||||
$R config testdb rstate.db
|
||||
$R config rowids 1
|
||||
$R run
|
||||
$R finish
|
||||
} {}]
|
||||
|
||||
sqlite3 db2 test.db2
|
||||
execsql [join $::sqlhook ";"] db2
|
||||
db2 close
|
||||
sqlite3 db2 test.db2
|
||||
uplevel [list do_test $tn.4 [list compare_dbs db db2] {}]
|
||||
db2 close
|
||||
}
|
||||
|
||||
proc my_sql_hook {sql} {
|
||||
lappend ::sqlhook $sql
|
||||
return 0
|
||||
}
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
|
||||
CREATE TABLE t2(a INTEGER PRIMARY KEY, b) WITHOUT ROWID;
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<10
|
||||
)
|
||||
INSERT INTO t1 SELECT i*2, hex(randomblob(250)) FROM s;
|
||||
INSERT INTO t2 SELECT * FROM t1;
|
||||
}
|
||||
|
||||
do_recover_test 1
|
||||
|
||||
do_execsql_test 2.0 {
|
||||
ALTER TABLE t1 ADD COLUMN c DEFAULT 'xyz'
|
||||
}
|
||||
do_recover_test 2
|
||||
|
||||
do_execsql_test 3.0 {
|
||||
CREATE INDEX i1 ON t1(c);
|
||||
}
|
||||
do_recover_test 3
|
||||
|
||||
do_execsql_test 4.0 {
|
||||
CREATE VIEW v1 AS SELECT * FROM t2;
|
||||
}
|
||||
do_recover_test 4
|
||||
|
||||
do_execsql_test 5.0 {
|
||||
CREATE UNIQUE INDEX i2 ON t1(c, b);
|
||||
}
|
||||
do_recover_test 5
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 6.0 {
|
||||
CREATE TABLE t1(
|
||||
a INTEGER PRIMARY KEY,
|
||||
b INT,
|
||||
c TEXT,
|
||||
d INT GENERATED ALWAYS AS (a*abs(b)) VIRTUAL,
|
||||
e TEXT GENERATED ALWAYS AS (substr(c,b,b+1)) STORED,
|
||||
f TEXT GENERATED ALWAYS AS (substr(c,b,b+1)) STORED
|
||||
);
|
||||
|
||||
INSERT INTO t1 VALUES(1, 2, 'hello world');
|
||||
}
|
||||
do_recover_test 6
|
||||
|
||||
do_execsql_test 7.0 {
|
||||
CREATE TABLE t2(i, j GENERATED ALWAYS AS (i+1) STORED, k);
|
||||
INSERT INTO t2 VALUES(10, 'ten');
|
||||
}
|
||||
do_execsql_test 7.1 {
|
||||
SELECT * FROM t2
|
||||
} {10 11 ten}
|
||||
|
||||
do_recover_test 7.2
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 8.0 {
|
||||
CREATE TABLE x1(a INTEGER PRIMARY KEY AUTOINCREMENT, b, c);
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<2
|
||||
)
|
||||
INSERT INTO x1(b, c) SELECT hex(randomblob(100)), hex(randomblob(100)) FROM s;
|
||||
|
||||
CREATE INDEX x1b ON x1(b);
|
||||
CREATE INDEX x1cb ON x1(c, b);
|
||||
DELETE FROM x1 WHERE a>50;
|
||||
|
||||
ANALYZE;
|
||||
}
|
||||
|
||||
do_recover_test 8
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
ifcapable fts5 {
|
||||
do_execsql_test 9.1 {
|
||||
CREATE VIRTUAL TABLE ft5 USING fts5(a, b);
|
||||
INSERT INTO ft5 VALUES('hello', 'world');
|
||||
}
|
||||
do_recover_test 9
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 10.1 {
|
||||
CREATE TABLE x1(a PRIMARY KEY, str TEXT) WITHOUT ROWID;
|
||||
INSERT INTO x1 VALUES(1, '
|
||||
\nhello\012world(\n0)(\n1)
|
||||
');
|
||||
INSERT INTO x1 VALUES(2, '
|
||||
\nhello
|
||||
');
|
||||
}
|
||||
do_execsql_test 10.2 "
|
||||
INSERT INTO x1 VALUES(3, '\012hello there\015world');
|
||||
INSERT INTO x1 VALUES(4, '\015hello there\015world');
|
||||
"
|
||||
do_recover_test 10
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 11.1 {
|
||||
PRAGMA page_size = 4096;
|
||||
PRAGMA encoding='utf16';
|
||||
PRAGMA auto_vacuum = 2;
|
||||
PRAGMA user_version = 45;
|
||||
PRAGMA application_id = 22;
|
||||
|
||||
CREATE TABLE u1(u, v);
|
||||
INSERT INTO u1 VALUES('edvin marton', 'bond');
|
||||
INSERT INTO u1 VALUES(1, 4.0);
|
||||
}
|
||||
do_execsql_test 11.1a {
|
||||
PRAGMA auto_vacuum;
|
||||
} {2}
|
||||
|
||||
do_recover_test 11
|
||||
|
||||
do_test 12.1 {
|
||||
set R [sqlite3_recover_init db "" test.db2]
|
||||
$R config lostandfound ""
|
||||
$R config invalid xyz
|
||||
} {12}
|
||||
do_test 12.2 {
|
||||
$R run
|
||||
$R run
|
||||
} {0}
|
||||
|
||||
do_test 12.3 {
|
||||
$R finish
|
||||
} {}
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
file_control_reservebytes db 16
|
||||
do_execsql_test 12.1 {
|
||||
PRAGMA auto_vacuum = 2;
|
||||
PRAGMA user_version = 45;
|
||||
PRAGMA application_id = 22;
|
||||
|
||||
CREATE TABLE u1(u, v);
|
||||
CREATE UNIQUE INDEX i1 ON u1(u, v);
|
||||
INSERT INTO u1 VALUES(1, 2), (3, 4);
|
||||
|
||||
CREATE TABLE u2(u, v);
|
||||
CREATE UNIQUE INDEX i2 ON u1(u, v);
|
||||
INSERT INTO u2 VALUES(hex(randomblob(500)), hex(randomblob(1000)));
|
||||
INSERT INTO u2 VALUES(hex(randomblob(500)), hex(randomblob(1000)));
|
||||
INSERT INTO u2 VALUES(hex(randomblob(500)), hex(randomblob(1000)));
|
||||
INSERT INTO u2 VALUES(hex(randomblob(50000)), hex(randomblob(20000)));
|
||||
}
|
||||
|
||||
do_recover_test 12
|
||||
|
||||
finish_test
|
||||
|
5
ext/recover/recover_common.tcl
Normal file
5
ext/recover/recover_common.tcl
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
58
ext/recover/recoverclobber.test
Normal file
58
ext/recover/recoverclobber.test
Normal file
@ -0,0 +1,58 @@
|
||||
# 2019 April 23
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# Tests for the SQLITE_RECOVER_ROWIDS option.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] recover_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
set testprefix recoverclobber
|
||||
|
||||
ifcapable !vtab {
|
||||
finish_test; return
|
||||
}
|
||||
|
||||
proc recover {db output} {
|
||||
set R [sqlite3_recover_init db main test.db2]
|
||||
$R run
|
||||
$R finish
|
||||
}
|
||||
|
||||
forcedelete test.db2
|
||||
do_execsql_test 1.0 {
|
||||
ATTACH 'test.db2' AS aux;
|
||||
CREATE TABLE aux.x1(x, one);
|
||||
INSERT INTO x1 VALUES(1, 'one'), (2, 'two'), (3, 'three');
|
||||
|
||||
CREATE TABLE t1(a, b);
|
||||
INSERT INTO t1 VALUES(1, 1), (2, 2), (3, 3), (4, 4);
|
||||
|
||||
DETACH aux;
|
||||
}
|
||||
|
||||
breakpoint
|
||||
do_test 1.1 {
|
||||
recover db test.db2
|
||||
} {}
|
||||
|
||||
do_execsql_test 1.2 {
|
||||
ATTACH 'test.db2' AS aux;
|
||||
SELECT * FROM aux.t1;
|
||||
} {1 1 2 2 3 3 4 4}
|
||||
|
||||
do_catchsql_test 1.3 {
|
||||
SELECT * FROM aux.x1;
|
||||
} {1 {no such table: aux.x1}}
|
||||
|
||||
finish_test
|
72
ext/recover/recovercorrupt.test
Normal file
72
ext/recover/recovercorrupt.test
Normal file
@ -0,0 +1,72 @@
|
||||
# 2022 August 28
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] recover_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
|
||||
set testprefix recovercorrupt
|
||||
|
||||
database_may_be_corrupt
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
PRAGMA page_size = 512;
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
|
||||
INSERT INTO t1 VALUES(1, 2, 3);
|
||||
INSERT INTO t1 VALUES(2, hex(randomblob(100)), randomblob(200));
|
||||
CREATE INDEX i1 ON t1(b, c);
|
||||
CREATE TABLE t2(a PRIMARY KEY, b, c) WITHOUT ROWID;
|
||||
INSERT INTO t2 VALUES(1, 2, 3);
|
||||
INSERT INTO t2 VALUES(2, hex(randomblob(100)), randomblob(200));
|
||||
ANALYZE;
|
||||
PRAGMA writable_schema = 1;
|
||||
DELETE FROM sqlite_schema WHERE name='t2';
|
||||
}
|
||||
|
||||
do_test 1.1 {
|
||||
expr [file size test.db]>3072
|
||||
} {1}
|
||||
|
||||
proc toggle_bit {blob bit} {
|
||||
set byte [expr {$bit / 8}]
|
||||
set bit [expr {$bit & 0x0F}]
|
||||
binary scan $blob a${byte}ca* A x B
|
||||
set x [expr {$x ^ (1 << $bit)}]
|
||||
binary format a*ca* $A $x $B
|
||||
}
|
||||
|
||||
|
||||
db_save_and_close
|
||||
for {set ii 0} {$ii < 10000} {incr ii} {
|
||||
db_restore_and_reopen
|
||||
db func toggle_bit toggle_bit
|
||||
set bitsperpage [expr 512*8]
|
||||
|
||||
set pg [expr {($ii / $bitsperpage)+1}]
|
||||
set byte [expr {$ii % $bitsperpage}]
|
||||
db eval {
|
||||
UPDATE sqlite_dbpage SET data = toggle_bit(data, $byte) WHERE pgno=$pg
|
||||
}
|
||||
|
||||
set R [sqlite3_recover_init db main test.db2]
|
||||
$R config lostandfound lost_and_found
|
||||
$R run
|
||||
do_test 1.2.$ii {
|
||||
$R finish
|
||||
} {}
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
117
ext/recover/recovercorrupt2.test
Normal file
117
ext/recover/recovercorrupt2.test
Normal file
@ -0,0 +1,117 @@
|
||||
# 2022 August 28
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] recover_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
|
||||
set testprefix recovercorrupt2
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
PRAGMA page_size = 512;
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
|
||||
INSERT INTO t1 VALUES(1, 2, 3);
|
||||
INSERT INTO t1 VALUES(2, hex(randomblob(100)), randomblob(200));
|
||||
CREATE INDEX i1 ON t1(b, c);
|
||||
CREATE TABLE t2(a PRIMARY KEY, b, c) WITHOUT ROWID;
|
||||
INSERT INTO t2 VALUES(1, 2, 3);
|
||||
INSERT INTO t2 VALUES(2, hex(randomblob(100)), randomblob(200));
|
||||
ANALYZE;
|
||||
PRAGMA writable_schema = 1;
|
||||
UPDATE sqlite_schema SET sql = 'CREATE INDEX i1 ON o(world)' WHERE name='i1';
|
||||
DELETE FROM sqlite_schema WHERE name='sqlite_stat4';
|
||||
}
|
||||
|
||||
do_test 1.1 {
|
||||
set R [sqlite3_recover_init db main test.db2]
|
||||
$R run
|
||||
$R finish
|
||||
} {}
|
||||
|
||||
sqlite3 db2 test.db2
|
||||
do_execsql_test -db db2 1.2 {
|
||||
SELECT sql FROM sqlite_schema
|
||||
} {
|
||||
{CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c)}
|
||||
{CREATE TABLE t2(a PRIMARY KEY, b, c) WITHOUT ROWID}
|
||||
{CREATE TABLE sqlite_stat1(tbl,idx,stat)}
|
||||
}
|
||||
db2 close
|
||||
|
||||
do_execsql_test 1.3 {
|
||||
PRAGMA writable_schema = 1;
|
||||
UPDATE sqlite_schema SET sql = 'CREATE TABLE t2 syntax error!' WHERE name='t2';
|
||||
}
|
||||
|
||||
do_test 1.4 {
|
||||
set R [sqlite3_recover_init db main test.db2]
|
||||
$R run
|
||||
$R finish
|
||||
} {}
|
||||
|
||||
sqlite3 db2 test.db2
|
||||
do_execsql_test -db db2 1.5 {
|
||||
SELECT sql FROM sqlite_schema
|
||||
} {
|
||||
{CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c)}
|
||||
{CREATE TABLE sqlite_stat1(tbl,idx,stat)}
|
||||
}
|
||||
db2 close
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
do_test 2.0 {
|
||||
sqlite3 db {}
|
||||
db deserialize [decode_hexdb {
|
||||
| size 8192 pagesize 4096 filename x3.db
|
||||
| page 1 offset 0
|
||||
| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3.
|
||||
| 16: 10 00 01 01 00 40 20 20 00 00 00 02 00 00 00 02 .....@ ........
|
||||
| 32: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 04 ................
|
||||
| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................
|
||||
| 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 ................
|
||||
| 96: 00 2e 63 00 0d 00 00 00 01 0f d8 00 0f d8 00 00 ..c.............
|
||||
| 4048: 00 00 00 00 00 00 00 00 26 01 06 17 11 11 01 39 ........&......9
|
||||
| 4064: 74 61 62 6c 65 74 31 74 31 02 43 52 45 41 54 45 tablet1t1.CREATE
|
||||
| 4080: 20 54 41 42 4c 45 20 74 31 28 61 2c 62 2c 63 29 TABLE t1(a,b,c)
|
||||
| page 2 offset 4096
|
||||
| 0: 0d 00 00 00 01 0f ce 00 0f ce 00 00 00 00 00 00 ................
|
||||
| 4032: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff ff ..............(.
|
||||
| 4048: ff ff ff ff ff ff ff 28 04 27 25 23 61 61 61 61 .........'%#aaaa
|
||||
| 4064: 61 61 61 61 61 61 61 61 61 62 62 62 62 62 62 62 aaaaaaaaabbbbbbb
|
||||
| 4080: 62 62 62 62 62 63 63 63 63 63 63 63 63 63 63 63 bbbbbccccccccccc
|
||||
| end x3.db
|
||||
}]} {}
|
||||
|
||||
do_test 2.1 {
|
||||
set R [sqlite3_recover_init db main test.db2]
|
||||
$R run
|
||||
$R finish
|
||||
} {}
|
||||
|
||||
sqlite3 db2 test.db2
|
||||
do_execsql_test -db db2 2.2 {
|
||||
SELECT sql FROM sqlite_schema
|
||||
} {
|
||||
{CREATE TABLE t1(a,b,c)}
|
||||
}
|
||||
do_execsql_test -db db2 2.3 {
|
||||
SELECT * FROM t1
|
||||
} {}
|
||||
db2 close
|
||||
|
||||
|
||||
finish_test
|
||||
|
89
ext/recover/recoverfault.test
Normal file
89
ext/recover/recoverfault.test
Normal file
@ -0,0 +1,89 @@
|
||||
# 2022 August 28
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] recover_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
|
||||
set testprefix recoverfault
|
||||
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
proc compare_result {db1 db2 sql} {
|
||||
set r1 [$db1 eval $sql]
|
||||
set r2 [$db2 eval $sql]
|
||||
if {$r1 != $r2} {
|
||||
puts "r1: $r1"
|
||||
puts "r2: $r2"
|
||||
error "mismatch for $sql"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
proc compare_dbs {db1 db2} {
|
||||
compare_result $db1 $db2 "SELECT sql FROM sqlite_master ORDER BY 1"
|
||||
foreach tbl [$db1 eval {SELECT name FROM sqlite_master WHERE type='table'}] {
|
||||
compare_result $db1 $db2 "SELECT * FROM $tbl"
|
||||
}
|
||||
}
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
|
||||
INSERT INTO t1 VALUES(1, 2, 3);
|
||||
INSERT INTO t1 VALUES(2, hex(randomblob(1000)), randomblob(2000));
|
||||
CREATE INDEX i1 ON t1(b, c);
|
||||
ANALYZE;
|
||||
}
|
||||
faultsim_save_and_close
|
||||
|
||||
do_faultsim_test 1 -faults oom* -prep {
|
||||
catch { db2 close }
|
||||
faultsim_restore_and_reopen
|
||||
} -body {
|
||||
set R [sqlite3_recover_init db main test.db2]
|
||||
$R run
|
||||
$R finish
|
||||
} -test {
|
||||
faultsim_test_result {0 {}} {1 {}}
|
||||
if {$testrc==0} {
|
||||
sqlite3 db2 test.db2
|
||||
compare_dbs db db2
|
||||
db2 close
|
||||
}
|
||||
}
|
||||
|
||||
faultsim_restore_and_reopen
|
||||
do_execsql_test 2.0 {
|
||||
CREATE TABLE t2(a INTEGER PRIMARY KEY, b, c);
|
||||
INSERT INTO t2 VALUES(1, 2, 3);
|
||||
INSERT INTO t2 VALUES(2, hex(randomblob(1000)), hex(randomblob(2000)));
|
||||
PRAGMA writable_schema = 1;
|
||||
DELETE FROM sqlite_schema WHERE name='t2';
|
||||
}
|
||||
faultsim_save_and_close
|
||||
|
||||
do_faultsim_test 2 -faults oom* -prep {
|
||||
faultsim_restore_and_reopen
|
||||
} -body {
|
||||
set R [sqlite3_recover_init db main test.db2]
|
||||
$R config lostandfound lost_and_found
|
||||
$R run
|
||||
$R finish
|
||||
} -test {
|
||||
faultsim_test_result {0 {}} {1 {}}
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
107
ext/recover/recoverfault2.test
Normal file
107
ext/recover/recoverfault2.test
Normal file
@ -0,0 +1,107 @@
|
||||
# 2022 August 28
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] recover_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
|
||||
set testprefix recoverfault2
|
||||
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
proc compare_result {db1 db2 sql} {
|
||||
set r1 [$db1 eval $sql]
|
||||
set r2 [$db2 eval $sql]
|
||||
if {$r1 != $r2} {
|
||||
puts "r1: $r1"
|
||||
puts "r2: $r2"
|
||||
error "mismatch for $sql"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
proc compare_dbs {db1 db2} {
|
||||
compare_result $db1 $db2 "SELECT sql FROM sqlite_master ORDER BY 1"
|
||||
foreach tbl [$db1 eval {SELECT name FROM sqlite_master WHERE type='table'}] {
|
||||
compare_result $db1 $db2 "SELECT * FROM $tbl"
|
||||
}
|
||||
}
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
do_execsql_test 1.0 "
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
|
||||
INSERT INTO t1 VALUES(2, '\012hello\015world\012today\n');
|
||||
"
|
||||
faultsim_save_and_close
|
||||
|
||||
proc my_sql_hook {sql} {
|
||||
lappend ::lSql $sql
|
||||
return 0
|
||||
}
|
||||
|
||||
do_faultsim_test 1 -faults oom* -prep {
|
||||
catch { db2 close }
|
||||
faultsim_restore_and_reopen
|
||||
set ::lSql [list]
|
||||
} -body {
|
||||
set R [sqlite3_recover_init_sql db main my_sql_hook]
|
||||
$R run
|
||||
$R finish
|
||||
} -test {
|
||||
faultsim_test_result {0 {}} {1 {}}
|
||||
if {$testrc==0} {
|
||||
sqlite3 db2 ""
|
||||
db2 eval [join $::lSql ";"]
|
||||
compare_dbs db db2
|
||||
db2 close
|
||||
}
|
||||
}
|
||||
|
||||
ifcapable utf16 {
|
||||
reset_db
|
||||
do_execsql_test 2.0 "
|
||||
PRAGMA encoding='utf-16';
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
|
||||
INSERT INTO t1 VALUES(2, '\012hello\015world\012today\n');
|
||||
"
|
||||
faultsim_save_and_close
|
||||
|
||||
proc my_sql_hook {sql} {
|
||||
lappend ::lSql $sql
|
||||
return 0
|
||||
}
|
||||
|
||||
do_faultsim_test 2 -faults oom-t* -prep {
|
||||
catch { db2 close }
|
||||
faultsim_restore_and_reopen
|
||||
set ::lSql [list]
|
||||
} -body {
|
||||
set R [sqlite3_recover_init_sql db main my_sql_hook]
|
||||
$R run
|
||||
$R finish
|
||||
} -test {
|
||||
faultsim_test_result {0 {}} {1 {}}
|
||||
if {$testrc==0} {
|
||||
sqlite3 db2 ""
|
||||
db2 eval [join $::lSql ";"]
|
||||
compare_dbs db db2
|
||||
db2 close
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
|
193
ext/recover/recoverold.test
Normal file
193
ext/recover/recoverold.test
Normal file
@ -0,0 +1,193 @@
|
||||
# 2019 April 23
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] recover_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
set testprefix recoverold
|
||||
|
||||
ifcapable !vtab {
|
||||
finish_test; return
|
||||
}
|
||||
|
||||
proc compare_result {db1 db2 sql} {
|
||||
set r1 [$db1 eval $sql]
|
||||
set r2 [$db2 eval $sql]
|
||||
if {$r1 != $r2} {
|
||||
puts "sql: $sql"
|
||||
puts "r1: $r1"
|
||||
puts "r2: $r2"
|
||||
error "mismatch for $sql"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
proc compare_dbs {db1 db2} {
|
||||
compare_result $db1 $db2 "SELECT sql FROM sqlite_master ORDER BY 1"
|
||||
foreach tbl [$db1 eval {SELECT name FROM sqlite_master WHERE type='table'}] {
|
||||
compare_result $db1 $db2 "SELECT * FROM $tbl"
|
||||
}
|
||||
}
|
||||
|
||||
proc do_recover_test {tn {tsql {}} {res {}}} {
|
||||
forcedelete test.db2
|
||||
forcedelete rstate.db
|
||||
|
||||
set R [sqlite3_recover_init db main test.db2]
|
||||
$R config lostandfound lost_and_found
|
||||
$R run
|
||||
$R finish
|
||||
|
||||
sqlite3 db2 test.db2
|
||||
|
||||
if {$tsql==""} {
|
||||
uplevel [list do_test $tn.1 [list compare_dbs db db2] {}]
|
||||
} else {
|
||||
uplevel [list do_execsql_test -db db2 $tn.1 $tsql $res]
|
||||
}
|
||||
db2 close
|
||||
|
||||
forcedelete test.db2
|
||||
forcedelete rstate.db
|
||||
|
||||
set ::sqlhook [list]
|
||||
set R [sqlite3_recover_init_sql db main my_sql_hook]
|
||||
$R config lostandfound lost_and_found
|
||||
$R run
|
||||
$R finish
|
||||
|
||||
sqlite3 db2 test.db2
|
||||
db2 eval [join $::sqlhook ";"]
|
||||
|
||||
if {$tsql==""} {
|
||||
uplevel [list do_test $tn.sql [list compare_dbs db db2] {}]
|
||||
} else {
|
||||
uplevel [list do_execsql_test -db db2 $tn.sql $tsql $res]
|
||||
}
|
||||
db2 close
|
||||
}
|
||||
|
||||
proc my_sql_hook {sql} {
|
||||
lappend ::sqlhook $sql
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
set doc {
|
||||
hello
|
||||
world
|
||||
}
|
||||
do_execsql_test 1.1.1 {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
|
||||
INSERT INTO t1 VALUES(1, 4, X'1234567800');
|
||||
INSERT INTO t1 VALUES(2, 'test', 8.1);
|
||||
INSERT INTO t1 VALUES(3, $doc, 8.4);
|
||||
}
|
||||
do_recover_test 1.1.2
|
||||
|
||||
do_execsql_test 1.2.1 "
|
||||
DELETE FROM t1;
|
||||
INSERT INTO t1 VALUES(13, 'hello\r\nworld', 13);
|
||||
"
|
||||
do_recover_test 1.2.2
|
||||
|
||||
do_execsql_test 1.3.1 "
|
||||
CREATE TABLE t2(i INTEGER PRIMARY KEY AUTOINCREMENT, b, c);
|
||||
INSERT INTO t2 VALUES(NULL, 1, 2);
|
||||
INSERT INTO t2 VALUES(NULL, 3, 4);
|
||||
INSERT INTO t2 VALUES(NULL, 5, 6);
|
||||
CREATE TABLE t3(i INTEGER PRIMARY KEY AUTOINCREMENT, b, c);
|
||||
INSERT INTO t3 VALUES(NULL, 1, 2);
|
||||
INSERT INTO t3 VALUES(NULL, 3, 4);
|
||||
INSERT INTO t3 VALUES(NULL, 5, 6);
|
||||
DELETE FROM t2;
|
||||
"
|
||||
do_recover_test 1.3.2
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 2.1.0 {
|
||||
PRAGMA auto_vacuum = 0;
|
||||
CREATE TABLE t1(a, b, c, PRIMARY KEY(b, c)) WITHOUT ROWID;
|
||||
INSERT INTO t1 VALUES(1, 2, 3);
|
||||
INSERT INTO t1 VALUES(4, 5, 6);
|
||||
INSERT INTO t1 VALUES(7, 8, 9);
|
||||
}
|
||||
|
||||
do_recover_test 2.1.1
|
||||
|
||||
do_execsql_test 2.2.0 {
|
||||
PRAGMA writable_schema = 1;
|
||||
DELETE FROM sqlite_master WHERE name='t1';
|
||||
}
|
||||
do_recover_test 2.2.1 {
|
||||
SELECT name FROM sqlite_master
|
||||
} {lost_and_found}
|
||||
|
||||
do_execsql_test 2.3.0 {
|
||||
CREATE TABLE lost_and_found(a, b, c);
|
||||
}
|
||||
do_recover_test 2.3.1 {
|
||||
SELECT name FROM sqlite_master
|
||||
} {lost_and_found lost_and_found_0}
|
||||
|
||||
do_execsql_test 2.4.0 {
|
||||
CREATE TABLE lost_and_found_0(a, b, c);
|
||||
}
|
||||
do_recover_test 2.4.1 {
|
||||
SELECT name FROM sqlite_master;
|
||||
SELECT * FROM lost_and_found_1;
|
||||
} {lost_and_found lost_and_found_0 lost_and_found_1
|
||||
2 2 3 {} 2 3 1
|
||||
2 2 3 {} 5 6 4
|
||||
2 2 3 {} 8 9 7
|
||||
}
|
||||
|
||||
breakpoint
|
||||
do_execsql_test 2.5 {
|
||||
CREATE TABLE x1(a, b, c);
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100
|
||||
)
|
||||
INSERT INTO x1 SELECT i, i, hex(randomblob(500)) FROM s;
|
||||
DROP TABLE x1;
|
||||
}
|
||||
do_recover_test 2.5.1 {
|
||||
SELECT name FROM sqlite_master;
|
||||
SELECT * FROM lost_and_found_1;
|
||||
} {lost_and_found lost_and_found_0 lost_and_found_1
|
||||
2 2 3 {} 2 3 1
|
||||
2 2 3 {} 5 6 4
|
||||
2 2 3 {} 8 9 7
|
||||
}
|
||||
|
||||
do_test 2.6 {
|
||||
forcedelete test.db2
|
||||
set R [sqlite3_recover_init db main test.db2]
|
||||
$R config lostandfound lost_and_found
|
||||
$R config freelistcorrupt 1
|
||||
$R run
|
||||
$R finish
|
||||
sqlite3 db2 test.db2
|
||||
execsql { SELECT count(*) FROM lost_and_found_1; } db2
|
||||
} {103}
|
||||
db2 close
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
breakpoint
|
||||
reset_db
|
||||
do_recover_test 3.0
|
||||
|
||||
finish_test
|
103
ext/recover/recoverpgsz.test
Normal file
103
ext/recover/recoverpgsz.test
Normal file
@ -0,0 +1,103 @@
|
||||
# 2022 October 14
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] recover_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
|
||||
db close
|
||||
sqlite3_test_control_pending_byte 0x1000000
|
||||
|
||||
set testprefix recoverpgsz
|
||||
|
||||
foreach {pgsz bOverflow} {
|
||||
512 0 1024 0 2048 0 4096 0 8192 0 16384 0 32768 0 65536 0
|
||||
512 1 1024 1 2048 1 4096 1 8192 1 16384 1 32768 1 65536 1
|
||||
} {
|
||||
reset_db
|
||||
execsql "PRAGMA page_size = $pgsz"
|
||||
do_execsql_test 1.$pgsz.$bOverflow.1 {
|
||||
CREATE TABLE t1(a, b, c);
|
||||
CREATE INDEX i1 ON t1(b, a, c);
|
||||
INSERT INTO t1(a, b) VALUES(1, 2), (3, 4), (5, 6);
|
||||
DELETE FROM t1 WHERE a=3;
|
||||
}
|
||||
if {$bOverflow} {
|
||||
do_execsql_test 1.$pgsz.$bOverflow.1a {
|
||||
UPDATE t1 SET c = randomblob(100000);
|
||||
}
|
||||
}
|
||||
db close
|
||||
|
||||
|
||||
set fd [open test.db]
|
||||
fconfigure $fd -encoding binary -translation binary
|
||||
seek $fd $pgsz
|
||||
set pg1 [read $fd $pgsz]
|
||||
set pg2 [read $fd $pgsz]
|
||||
close $fd
|
||||
|
||||
set fd2 [open test.db2 w]
|
||||
fconfigure $fd2 -encoding binary -translation binary
|
||||
seek $fd2 $pgsz
|
||||
puts -nonewline $fd2 $pg1
|
||||
close $fd2
|
||||
|
||||
sqlite3 db2 test.db2
|
||||
do_test 1.$pgsz.$bOverflow.2 {
|
||||
set R [sqlite3_recover_init db2 main test.db3]
|
||||
$R run
|
||||
$R finish
|
||||
} {}
|
||||
|
||||
sqlite3 db3 test.db3
|
||||
do_test 1.$pgsz.$bOverflow.3 {
|
||||
db3 eval { SELECT * FROM sqlite_schema }
|
||||
db3 eval { PRAGMA page_size }
|
||||
} $pgsz
|
||||
|
||||
db2 close
|
||||
db3 close
|
||||
|
||||
forcedelete test.db3
|
||||
forcedelete test.db2
|
||||
|
||||
set fd2 [open test.db2 w]
|
||||
fconfigure $fd2 -encoding binary -translation binary
|
||||
seek $fd2 $pgsz
|
||||
puts -nonewline $fd2 $pg2
|
||||
close $fd2
|
||||
|
||||
sqlite3 db2 test.db2
|
||||
do_test 1.$pgsz.$bOverflow.4 {
|
||||
set R [sqlite3_recover_init db2 main test.db3]
|
||||
$R run
|
||||
$R finish
|
||||
} {}
|
||||
|
||||
sqlite3 db3 test.db3
|
||||
do_test 1.$pgsz.$bOverflow.5 {
|
||||
db3 eval { SELECT * FROM sqlite_schema }
|
||||
db3 eval { PRAGMA page_size }
|
||||
} $pgsz
|
||||
|
||||
db2 close
|
||||
db3 close
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
|
58
ext/recover/recoverrowid.test
Normal file
58
ext/recover/recoverrowid.test
Normal file
@ -0,0 +1,58 @@
|
||||
# 2022 September 07
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# Tests for the SQLITE_RECOVER_ROWIDS option.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] recover_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
set testprefix recoverrowid
|
||||
|
||||
ifcapable !vtab {
|
||||
finish_test; return
|
||||
}
|
||||
|
||||
proc recover {db bRowids output} {
|
||||
forcedelete $output
|
||||
|
||||
set R [sqlite3_recover_init db main test.db2]
|
||||
$R config rowids $bRowids
|
||||
$R run
|
||||
$R finish
|
||||
}
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a, b);
|
||||
INSERT INTO t1 VALUES(1, 1), (2, 2), (3, 3), (4, 4);
|
||||
DELETE FROM t1 WHERE a IN (1, 3);
|
||||
}
|
||||
|
||||
do_test 1.1 {
|
||||
recover db 0 test.db2
|
||||
sqlite3 db2 test.db2
|
||||
execsql { SELECT rowid, a, b FROM t1 ORDER BY rowid} db2
|
||||
} {1 2 2 2 4 4}
|
||||
|
||||
do_test 1.2 {
|
||||
db2 close
|
||||
recover db 1 test.db2
|
||||
sqlite3 db2 test.db2
|
||||
execsql { SELECT rowid, a, b FROM t1 ORDER BY rowid} db2
|
||||
} {2 2 2 4 4 4}
|
||||
db2 close
|
||||
|
||||
|
||||
|
||||
|
||||
finish_test
|
94
ext/recover/recoverslowidx.test
Normal file
94
ext/recover/recoverslowidx.test
Normal file
@ -0,0 +1,94 @@
|
||||
# 2022 September 25
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# Tests for the SQLITE_RECOVER_SLOWINDEXES option.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] recover_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
set testprefix recoverslowidx
|
||||
|
||||
ifcapable !vtab {
|
||||
finish_test; return
|
||||
}
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a, b);
|
||||
CREATE INDEX i1 ON t1(a);
|
||||
INSERT INTO t1 VALUES(1, 1), (2, 2), (3, 3), (4, 4);
|
||||
}
|
||||
|
||||
proc my_sql_hook {sql} {
|
||||
lappend ::lSql $sql
|
||||
return 0
|
||||
}
|
||||
|
||||
do_test 1.1 {
|
||||
set lSql [list]
|
||||
set R [sqlite3_recover_init_sql db main my_sql_hook]
|
||||
while {[$R step]==0} { }
|
||||
$R finish
|
||||
} {}
|
||||
|
||||
do_test 1.2 {
|
||||
set lSql
|
||||
} [list {*}{
|
||||
{BEGIN}
|
||||
{PRAGMA writable_schema = on}
|
||||
{PRAGMA encoding = 'UTF-8'}
|
||||
{PRAGMA page_size = '1024'}
|
||||
{PRAGMA auto_vacuum = '0'}
|
||||
{PRAGMA user_version = '0'}
|
||||
{PRAGMA application_id = '0'}
|
||||
{CREATE TABLE t1(a, b)}
|
||||
{INSERT OR IGNORE INTO 't1'(_rowid_, 'a', 'b') VALUES (1, 1, 1)}
|
||||
{INSERT OR IGNORE INTO 't1'(_rowid_, 'a', 'b') VALUES (2, 2, 2)}
|
||||
{INSERT OR IGNORE INTO 't1'(_rowid_, 'a', 'b') VALUES (3, 3, 3)}
|
||||
{INSERT OR IGNORE INTO 't1'(_rowid_, 'a', 'b') VALUES (4, 4, 4)}
|
||||
{CREATE INDEX i1 ON t1(a)}
|
||||
{PRAGMA writable_schema = off}
|
||||
{COMMIT}
|
||||
}]
|
||||
|
||||
do_test 1.3 {
|
||||
set lSql [list]
|
||||
set R [sqlite3_recover_init_sql db main my_sql_hook]
|
||||
$R config slowindexes 1
|
||||
while {[$R step]==0} { }
|
||||
$R finish
|
||||
} {}
|
||||
|
||||
do_test 1.4 {
|
||||
set lSql
|
||||
} [list {*}{
|
||||
{BEGIN}
|
||||
{PRAGMA writable_schema = on}
|
||||
{PRAGMA encoding = 'UTF-8'}
|
||||
{PRAGMA page_size = '1024'}
|
||||
{PRAGMA auto_vacuum = '0'}
|
||||
{PRAGMA user_version = '0'}
|
||||
{PRAGMA application_id = '0'}
|
||||
{CREATE TABLE t1(a, b)}
|
||||
{CREATE INDEX i1 ON t1(a)}
|
||||
{INSERT OR IGNORE INTO 't1'(_rowid_, 'a', 'b') VALUES (1, 1, 1)}
|
||||
{INSERT OR IGNORE INTO 't1'(_rowid_, 'a', 'b') VALUES (2, 2, 2)}
|
||||
{INSERT OR IGNORE INTO 't1'(_rowid_, 'a', 'b') VALUES (3, 3, 3)}
|
||||
{INSERT OR IGNORE INTO 't1'(_rowid_, 'a', 'b') VALUES (4, 4, 4)}
|
||||
{PRAGMA writable_schema = off}
|
||||
{COMMIT}
|
||||
}]
|
||||
|
||||
|
||||
finish_test
|
||||
|
60
ext/recover/recoversql.test
Normal file
60
ext/recover/recoversql.test
Normal file
@ -0,0 +1,60 @@
|
||||
# 2022 September 13
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] recover_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
set testprefix recoversql
|
||||
|
||||
ifcapable !vtab {
|
||||
finish_test; return
|
||||
}
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE "x.1" (x, y);
|
||||
INSERT INTO "x.1" VALUES(1, 1), (2, 2), (3, 3);
|
||||
CREATE INDEX "i.1" ON "x.1"(y, x);
|
||||
}
|
||||
|
||||
proc sql_hook {sql} {
|
||||
incr ::iSqlHook
|
||||
if {$::iSqlHook==$::sql_hook_cnt} { return 4 }
|
||||
return 0
|
||||
}
|
||||
|
||||
do_test 1.1 {
|
||||
set ::sql_hook_cnt -1
|
||||
set ::iSqlHook 0
|
||||
set R [sqlite3_recover_init_sql db main sql_hook]
|
||||
$R run
|
||||
$R finish
|
||||
} {}
|
||||
|
||||
set nSqlCall $iSqlHook
|
||||
|
||||
for {set ii 1} {$ii<$nSqlCall} {incr ii} {
|
||||
set iSqlHook 0
|
||||
set sql_hook_cnt $ii
|
||||
do_test 1.$ii.a {
|
||||
set R [sqlite3_recover_init_sql db main sql_hook]
|
||||
$R run
|
||||
} {1}
|
||||
do_test 1.$ii.b {
|
||||
list [catch { $R finish } msg] $msg
|
||||
} {1 {callback returned an error - 4}}
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
2804
ext/recover/sqlite3recover.c
Normal file
2804
ext/recover/sqlite3recover.c
Normal file
File diff suppressed because it is too large
Load Diff
232
ext/recover/sqlite3recover.h
Normal file
232
ext/recover/sqlite3recover.h
Normal file
@ -0,0 +1,232 @@
|
||||
/*
|
||||
** 2022-08-27
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
**
|
||||
** This file contains the public interface to the "recover" extension -
|
||||
** an SQLite extension designed to recover data from corrupted database
|
||||
** files.
|
||||
*/
|
||||
|
||||
/*
|
||||
** OVERVIEW:
|
||||
**
|
||||
** To use the API to recover data from a corrupted database, an
|
||||
** application:
|
||||
**
|
||||
** 1) Creates an sqlite3_recover handle by calling either
|
||||
** sqlite3_recover_init() or sqlite3_recover_init_sql().
|
||||
**
|
||||
** 2) Configures the new handle using one or more calls to
|
||||
** sqlite3_recover_config().
|
||||
**
|
||||
** 3) Executes the recovery by repeatedly calling sqlite3_recover_step() on
|
||||
** the handle until it returns something other than SQLITE_OK. If it
|
||||
** returns SQLITE_DONE, then the recovery operation completed without
|
||||
** error. If it returns some other non-SQLITE_OK value, then an error
|
||||
** has occurred.
|
||||
**
|
||||
** 4) Retrieves any error code and English language error message using the
|
||||
** sqlite3_recover_errcode() and sqlite3_recover_errmsg() APIs,
|
||||
** respectively.
|
||||
**
|
||||
** 5) Destroys the sqlite3_recover handle and frees all resources
|
||||
** using sqlite3_recover_finish().
|
||||
**
|
||||
** The application may abandon the recovery operation at any point
|
||||
** before it is finished by passing the sqlite3_recover handle to
|
||||
** sqlite3_recover_finish(). This is not an error, but the final state
|
||||
** of the output database, or the results of running the partial script
|
||||
** delivered to the SQL callback, are undefined.
|
||||
*/
|
||||
|
||||
#ifndef _SQLITE_RECOVER_H
|
||||
#define _SQLITE_RECOVER_H
|
||||
|
||||
#include "sqlite3.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Opaque handle type.
|
||||
*/
|
||||
typedef struct sqlite3_recover sqlite3_recover;
|
||||
|
||||
/*
|
||||
** These two APIs attempt to create and return a new sqlite3_recover object.
|
||||
** In both cases the first two arguments identify the (possibly
|
||||
** corrupt) database to recover data from. The first argument is an open
|
||||
** database handle and the second the name of a database attached to that
|
||||
** handle (i.e. "main", "temp" or the name of an attached database).
|
||||
**
|
||||
** If sqlite3_recover_init() is used to create the new sqlite3_recover
|
||||
** handle, then data is recovered into a new database, identified by
|
||||
** string parameter zUri. zUri may be an absolute or relative file path,
|
||||
** or may be an SQLite URI. If the identified database file already exists,
|
||||
** it is overwritten.
|
||||
**
|
||||
** If sqlite3_recover_init_sql() is invoked, then any recovered data will
|
||||
** be returned to the user as a series of SQL statements. Executing these
|
||||
** SQL statements results in the same database as would have been created
|
||||
** had sqlite3_recover_init() been used. For each SQL statement in the
|
||||
** output, the callback function passed as the third argument (xSql) is
|
||||
** invoked once. The first parameter is a passed a copy of the fourth argument
|
||||
** to this function (pCtx) as its first parameter, and a pointer to a
|
||||
** nul-terminated buffer containing the SQL statement formated as UTF-8 as
|
||||
** the second. If the xSql callback returns any value other than SQLITE_OK,
|
||||
** then processing is immediately abandoned and the value returned used as
|
||||
** the recover handle error code (see below).
|
||||
**
|
||||
** If an out-of-memory error occurs, NULL may be returned instead of
|
||||
** a valid handle. In all other cases, it is the responsibility of the
|
||||
** application to avoid resource leaks by ensuring that
|
||||
** sqlite3_recover_finish() is called on all allocated handles.
|
||||
*/
|
||||
sqlite3_recover *sqlite3_recover_init(
|
||||
sqlite3* db,
|
||||
const char *zDb,
|
||||
const char *zUri
|
||||
);
|
||||
sqlite3_recover *sqlite3_recover_init_sql(
|
||||
sqlite3* db,
|
||||
const char *zDb,
|
||||
int (*xSql)(void*, const char*),
|
||||
void *pCtx
|
||||
);
|
||||
|
||||
/*
|
||||
** Configure an sqlite3_recover object that has just been created using
|
||||
** sqlite3_recover_init() or sqlite3_recover_init_sql(). This function
|
||||
** may only be called before the first call to sqlite3_recover_step()
|
||||
** or sqlite3_recover_run() on the object.
|
||||
**
|
||||
** The second argument passed to this function must be one of the
|
||||
** SQLITE_RECOVER_* symbols defined below. Valid values for the third argument
|
||||
** depend on the specific SQLITE_RECOVER_* symbol in use.
|
||||
**
|
||||
** SQLITE_OK is returned if the configuration operation was successful,
|
||||
** or an SQLite error code otherwise.
|
||||
*/
|
||||
int sqlite3_recover_config(sqlite3_recover*, int op, void *pArg);
|
||||
|
||||
/*
|
||||
** SQLITE_RECOVER_LOST_AND_FOUND:
|
||||
** The pArg argument points to a string buffer containing the name
|
||||
** of a "lost-and-found" table in the output database, or NULL. If
|
||||
** the argument is non-NULL and the database contains seemingly
|
||||
** valid pages that cannot be associated with any table in the
|
||||
** recovered part of the schema, data is extracted from these
|
||||
** pages to add to the lost-and-found table.
|
||||
**
|
||||
** SQLITE_RECOVER_FREELIST_CORRUPT:
|
||||
** The pArg value must actually be a pointer to a value of type
|
||||
** int containing value 0 or 1 cast as a (void*). If this option is set
|
||||
** (argument is 1) and a lost-and-found table has been configured using
|
||||
** SQLITE_RECOVER_LOST_AND_FOUND, then is assumed that the freelist is
|
||||
** corrupt and an attempt is made to recover records from pages that
|
||||
** appear to be linked into the freelist. Otherwise, pages on the freelist
|
||||
** are ignored. Setting this option can recover more data from the
|
||||
** database, but often ends up "recovering" deleted records. The default
|
||||
** value is 0 (clear).
|
||||
**
|
||||
** SQLITE_RECOVER_ROWIDS:
|
||||
** The pArg value must actually be a pointer to a value of type
|
||||
** int containing value 0 or 1 cast as a (void*). If this option is set
|
||||
** (argument is 1), then an attempt is made to recover rowid values
|
||||
** that are not also INTEGER PRIMARY KEY values. If this option is
|
||||
** clear, then new rowids are assigned to all recovered rows. The
|
||||
** default value is 1 (set).
|
||||
**
|
||||
** SQLITE_RECOVER_SLOWINDEXES:
|
||||
** The pArg value must actually be a pointer to a value of type
|
||||
** int containing value 0 or 1 cast as a (void*). If this option is clear
|
||||
** (argument is 0), then when creating an output database, the recover
|
||||
** module creates and populates non-UNIQUE indexes right at the end of the
|
||||
** recovery operation - after all recoverable data has been inserted
|
||||
** into the new database. This is faster overall, but means that the
|
||||
** final call to sqlite3_recover_step() for a recovery operation may
|
||||
** be need to create a large number of indexes, which may be very slow.
|
||||
**
|
||||
** Or, if this option is set (argument is 1), then non-UNIQUE indexes
|
||||
** are created in the output database before it is populated with
|
||||
** recovered data. This is slower overall, but avoids the slow call
|
||||
** to sqlite3_recover_step() at the end of the recovery operation.
|
||||
**
|
||||
** The default option value is 0.
|
||||
*/
|
||||
#define SQLITE_RECOVER_LOST_AND_FOUND 1
|
||||
#define SQLITE_RECOVER_FREELIST_CORRUPT 2
|
||||
#define SQLITE_RECOVER_ROWIDS 3
|
||||
#define SQLITE_RECOVER_SLOWINDEXES 4
|
||||
|
||||
/*
|
||||
** Perform a unit of work towards the recovery operation. This function
|
||||
** must normally be called multiple times to complete database recovery.
|
||||
**
|
||||
** If no error occurs but the recovery operation is not completed, this
|
||||
** function returns SQLITE_OK. If recovery has been completed successfully
|
||||
** then SQLITE_DONE is returned. If an error has occurred, then an SQLite
|
||||
** error code (e.g. SQLITE_IOERR or SQLITE_NOMEM) is returned. It is not
|
||||
** considered an error if some or all of the data cannot be recovered
|
||||
** due to database corruption.
|
||||
**
|
||||
** Once sqlite3_recover_step() has returned a value other than SQLITE_OK,
|
||||
** all further such calls on the same recover handle are no-ops that return
|
||||
** the same non-SQLITE_OK value.
|
||||
*/
|
||||
int sqlite3_recover_step(sqlite3_recover*);
|
||||
|
||||
/*
|
||||
** Run the recovery operation to completion. Return SQLITE_OK if successful,
|
||||
** or an SQLite error code otherwise. Calling this function is the same
|
||||
** as executing:
|
||||
**
|
||||
** while( SQLITE_OK==sqlite3_recover_step(p) );
|
||||
** return sqlite3_recover_errcode(p);
|
||||
*/
|
||||
int sqlite3_recover_run(sqlite3_recover*);
|
||||
|
||||
/*
|
||||
** If an error has been encountered during a prior call to
|
||||
** sqlite3_recover_step(), then this function attempts to return a
|
||||
** pointer to a buffer containing an English language explanation of
|
||||
** the error. If no error message is available, or if an out-of memory
|
||||
** error occurs while attempting to allocate a buffer in which to format
|
||||
** the error message, NULL is returned.
|
||||
**
|
||||
** The returned buffer remains valid until the sqlite3_recover handle is
|
||||
** destroyed using sqlite3_recover_finish().
|
||||
*/
|
||||
const char *sqlite3_recover_errmsg(sqlite3_recover*);
|
||||
|
||||
/*
|
||||
** If this function is called on an sqlite3_recover handle after
|
||||
** an error occurs, an SQLite error code is returned. Otherwise, SQLITE_OK.
|
||||
*/
|
||||
int sqlite3_recover_errcode(sqlite3_recover*);
|
||||
|
||||
/*
|
||||
** Clean up a recovery object created by a call to sqlite3_recover_init().
|
||||
** The results of using a recovery object with any API after it has been
|
||||
** passed to this function are undefined.
|
||||
**
|
||||
** This function returns the same value as sqlite3_recover_errcode().
|
||||
*/
|
||||
int sqlite3_recover_finish(sqlite3_recover*);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* end of the 'extern "C"' block */
|
||||
#endif
|
||||
|
||||
#endif /* ifndef _SQLITE_RECOVER_H */
|
||||
|
308
ext/recover/test_recover.c
Normal file
308
ext/recover/test_recover.c
Normal file
@ -0,0 +1,308 @@
|
||||
/*
|
||||
** 2022-08-27
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
**
|
||||
*/
|
||||
|
||||
#include "sqlite3recover.h"
|
||||
#include "sqliteInt.h"
|
||||
|
||||
#include <tcl.h>
|
||||
#include <assert.h>
|
||||
|
||||
typedef struct TestRecover TestRecover;
|
||||
struct TestRecover {
|
||||
sqlite3_recover *p;
|
||||
Tcl_Interp *interp;
|
||||
Tcl_Obj *pScript;
|
||||
};
|
||||
|
||||
static int xSqlCallback(void *pSqlArg, const char *zSql){
|
||||
TestRecover *p = (TestRecover*)pSqlArg;
|
||||
Tcl_Obj *pEval = 0;
|
||||
int res = 0;
|
||||
|
||||
pEval = Tcl_DuplicateObj(p->pScript);
|
||||
Tcl_IncrRefCount(pEval);
|
||||
|
||||
res = Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewStringObj(zSql, -1));
|
||||
if( res==TCL_OK ){
|
||||
res = Tcl_EvalObjEx(p->interp, pEval, 0);
|
||||
}
|
||||
|
||||
Tcl_DecrRefCount(pEval);
|
||||
if( res ){
|
||||
Tcl_BackgroundError(p->interp);
|
||||
return TCL_ERROR;
|
||||
}else{
|
||||
Tcl_Obj *pObj = Tcl_GetObjResult(p->interp);
|
||||
if( Tcl_GetCharLength(pObj)==0 ){
|
||||
res = 0;
|
||||
}else if( Tcl_GetIntFromObj(p->interp, pObj, &res) ){
|
||||
Tcl_BackgroundError(p->interp);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static int getDbPointer(Tcl_Interp *interp, Tcl_Obj *pObj, sqlite3 **pDb){
|
||||
Tcl_CmdInfo info;
|
||||
if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(pObj), &info) ){
|
||||
Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(pObj), 0);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
*pDb = *(sqlite3 **)info.objClientData;
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Implementation of the command created by [sqlite3_recover_init]:
|
||||
**
|
||||
** $cmd config OP ARG
|
||||
** $cmd run
|
||||
** $cmd errmsg
|
||||
** $cmd errcode
|
||||
** $cmd finalize
|
||||
*/
|
||||
static int testRecoverCmd(
|
||||
void *clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
static struct RecoverSub {
|
||||
const char *zSub;
|
||||
int nArg;
|
||||
const char *zMsg;
|
||||
} aSub[] = {
|
||||
{ "config", 2, "ARG" }, /* 0 */
|
||||
{ "run", 0, "" }, /* 1 */
|
||||
{ "errmsg", 0, "" }, /* 2 */
|
||||
{ "errcode", 0, "" }, /* 3 */
|
||||
{ "finish", 0, "" }, /* 4 */
|
||||
{ "step", 0, "" }, /* 5 */
|
||||
{ 0 }
|
||||
};
|
||||
int rc = TCL_OK;
|
||||
int iSub = 0;
|
||||
TestRecover *pTest = (TestRecover*)clientData;
|
||||
|
||||
if( objc<2 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
rc = Tcl_GetIndexFromObjStruct(interp,
|
||||
objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
|
||||
);
|
||||
if( rc!=TCL_OK ) return rc;
|
||||
if( (objc-2)!=aSub[iSub].nArg ){
|
||||
Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
switch( iSub ){
|
||||
case 0: assert( sqlite3_stricmp("config", aSub[iSub].zSub)==0 ); {
|
||||
const char *aOp[] = {
|
||||
"testdb", /* 0 */
|
||||
"lostandfound", /* 1 */
|
||||
"freelistcorrupt", /* 2 */
|
||||
"rowids", /* 3 */
|
||||
"slowindexes", /* 4 */
|
||||
"invalid", /* 5 */
|
||||
0
|
||||
};
|
||||
int iOp = 0;
|
||||
int res = 0;
|
||||
if( Tcl_GetIndexFromObj(interp, objv[2], aOp, "option", 0, &iOp) ){
|
||||
return TCL_ERROR;
|
||||
}
|
||||
switch( iOp ){
|
||||
case 0:
|
||||
res = sqlite3_recover_config(pTest->p,
|
||||
789, (void*)Tcl_GetString(objv[3]) /* MAGIC NUMBER! */
|
||||
);
|
||||
break;
|
||||
case 1: {
|
||||
const char *zStr = Tcl_GetString(objv[3]);
|
||||
res = sqlite3_recover_config(pTest->p,
|
||||
SQLITE_RECOVER_LOST_AND_FOUND, (void*)(zStr[0] ? zStr : 0)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
int iVal = 0;
|
||||
if( Tcl_GetBooleanFromObj(interp, objv[3], &iVal) ) return TCL_ERROR;
|
||||
res = sqlite3_recover_config(pTest->p,
|
||||
SQLITE_RECOVER_FREELIST_CORRUPT, (void*)&iVal
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
int iVal = 0;
|
||||
if( Tcl_GetBooleanFromObj(interp, objv[3], &iVal) ) return TCL_ERROR;
|
||||
res = sqlite3_recover_config(pTest->p,
|
||||
SQLITE_RECOVER_ROWIDS, (void*)&iVal
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
int iVal = 0;
|
||||
if( Tcl_GetBooleanFromObj(interp, objv[3], &iVal) ) return TCL_ERROR;
|
||||
res = sqlite3_recover_config(pTest->p,
|
||||
SQLITE_RECOVER_SLOWINDEXES, (void*)&iVal
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
res = sqlite3_recover_config(pTest->p, 12345, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Tcl_SetObjResult(interp, Tcl_NewIntObj(res));
|
||||
break;
|
||||
}
|
||||
case 1: assert( sqlite3_stricmp("run", aSub[iSub].zSub)==0 ); {
|
||||
int res = sqlite3_recover_run(pTest->p);
|
||||
Tcl_SetObjResult(interp, Tcl_NewIntObj(res));
|
||||
break;
|
||||
}
|
||||
case 2: assert( sqlite3_stricmp("errmsg", aSub[iSub].zSub)==0 ); {
|
||||
const char *zErr = sqlite3_recover_errmsg(pTest->p);
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(zErr, -1));
|
||||
break;
|
||||
}
|
||||
case 3: assert( sqlite3_stricmp("errcode", aSub[iSub].zSub)==0 ); {
|
||||
int errCode = sqlite3_recover_errcode(pTest->p);
|
||||
Tcl_SetObjResult(interp, Tcl_NewIntObj(errCode));
|
||||
break;
|
||||
}
|
||||
case 4: assert( sqlite3_stricmp("finish", aSub[iSub].zSub)==0 ); {
|
||||
int res = sqlite3_recover_errcode(pTest->p);
|
||||
int res2;
|
||||
if( res!=SQLITE_OK ){
|
||||
const char *zErr = sqlite3_recover_errmsg(pTest->p);
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(zErr, -1));
|
||||
}
|
||||
res2 = sqlite3_recover_finish(pTest->p);
|
||||
assert( res2==res );
|
||||
if( res ) return TCL_ERROR;
|
||||
break;
|
||||
}
|
||||
case 5: assert( sqlite3_stricmp("step", aSub[iSub].zSub)==0 ); {
|
||||
int res = sqlite3_recover_step(pTest->p);
|
||||
Tcl_SetObjResult(interp, Tcl_NewIntObj(res));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** sqlite3_recover_init DB DBNAME URI
|
||||
*/
|
||||
static int test_sqlite3_recover_init(
|
||||
void *clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
static int iTestRecoverCmd = 1;
|
||||
|
||||
TestRecover *pNew = 0;
|
||||
sqlite3 *db = 0;
|
||||
const char *zDb = 0;
|
||||
const char *zUri = 0;
|
||||
char zCmd[128];
|
||||
int bSql = clientData ? 1 : 0;
|
||||
|
||||
if( objc!=4 ){
|
||||
const char *zErr = (bSql ? "DB DBNAME SCRIPT" : "DB DBNAME URI");
|
||||
Tcl_WrongNumArgs(interp, 1, objv, zErr);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( getDbPointer(interp, objv[1], &db) ) return TCL_ERROR;
|
||||
zDb = Tcl_GetString(objv[2]);
|
||||
if( zDb[0]=='\0' ) zDb = 0;
|
||||
|
||||
pNew = ckalloc(sizeof(TestRecover));
|
||||
if( bSql==0 ){
|
||||
zUri = Tcl_GetString(objv[3]);
|
||||
pNew->p = sqlite3_recover_init(db, zDb, zUri);
|
||||
}else{
|
||||
pNew->interp = interp;
|
||||
pNew->pScript = objv[3];
|
||||
Tcl_IncrRefCount(pNew->pScript);
|
||||
pNew->p = sqlite3_recover_init_sql(db, zDb, xSqlCallback, (void*)pNew);
|
||||
}
|
||||
|
||||
sprintf(zCmd, "sqlite_recover%d", iTestRecoverCmd++);
|
||||
Tcl_CreateObjCommand(interp, zCmd, testRecoverCmd, (void*)pNew, 0);
|
||||
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(zCmd, -1));
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** 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*);
|
||||
|
||||
/*
|
||||
** sqlite3_recover_init DB DBNAME URI
|
||||
*/
|
||||
static int test_sqlite3_dbdata_init(
|
||||
void *clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
sqlite3 *db = 0;
|
||||
|
||||
if( objc!=2 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "DB");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( getDbPointer(interp, objv[1], &db) ) return TCL_ERROR;
|
||||
sqlite3_dbdata_init(db, 0, 0);
|
||||
|
||||
Tcl_ResetResult(interp);
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int TestRecover_Init(Tcl_Interp *interp){
|
||||
struct Cmd {
|
||||
const char *zCmd;
|
||||
Tcl_ObjCmdProc *xProc;
|
||||
void *pArg;
|
||||
} aCmd[] = {
|
||||
{ "sqlite3_recover_init", test_sqlite3_recover_init, 0 },
|
||||
{ "sqlite3_recover_init_sql", test_sqlite3_recover_init, (void*)1 },
|
||||
{ "sqlite3_dbdata_init", test_sqlite3_dbdata_init, (void*)1 },
|
||||
};
|
||||
int i;
|
||||
|
||||
for(i=0; i<sizeof(aCmd)/sizeof(struct Cmd); i++){
|
||||
struct Cmd *p = &aCmd[i];
|
||||
Tcl_CreateObjCommand(interp, p->zCmd, p->xProc, p->pArg, 0);
|
||||
}
|
||||
|
||||
return TCL_OK;
|
||||
}
|
||||
|
7
main.mk
7
main.mk
@ -446,6 +446,9 @@ TESTSRC2 = \
|
||||
$(TOP)/ext/misc/stmt.c \
|
||||
$(TOP)/ext/session/sqlite3session.c \
|
||||
$(TOP)/ext/session/test_session.c \
|
||||
$(TOP)/ext/recover/sqlite3recover.c \
|
||||
$(TOP)/ext/recover/dbdata.c \
|
||||
$(TOP)/ext/recover/test_recover.c \
|
||||
fts5.c
|
||||
|
||||
# Header files used by all library source files.
|
||||
@ -761,7 +764,9 @@ SHELL_SRC = \
|
||||
$(TOP)/ext/expert/sqlite3expert.h \
|
||||
$(TOP)/ext/misc/zipfile.c \
|
||||
$(TOP)/ext/misc/memtrace.c \
|
||||
$(TOP)/ext/misc/dbdata.c \
|
||||
$(TOP)/ext/recover/dbdata.c \
|
||||
$(TOP)/ext/recover/sqlite3recover.c \
|
||||
$(TOP)/ext/recover/sqlite3recover.h \
|
||||
$(TOP)/src/test_windirent.c
|
||||
|
||||
shell.c: $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl
|
||||
|
49
manifest
49
manifest
@ -1,11 +1,11 @@
|
||||
C Make\sthe\sshell\s.recover\scommand\sand\sthe\sdbdata.c\smodule\smore\srobust\sin\sthe\sface\sof\scorrupted\sdatabases.
|
||||
D 2022-10-26T18:04:34.995
|
||||
C Add\sthe\s"recover"\sextension\sin\sext/recover/,\sfor\ssalvaging\sdata\sfrom\scorrupt\sdatabases.
|
||||
D 2022-10-26T18:41:12.936
|
||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
|
||||
F Makefile.in f1d0ee0aad6cc81de195ab1da015eb7f4d16c76697266ab2a84706f868b0c736
|
||||
F Makefile.in 579150683deaff2d8e70559a3a117a82a740b2491d4ecb56debb6a6f7c1705cd
|
||||
F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241
|
||||
F Makefile.msc 905b7205ef708fea740b17f35d8a8ec7a7aa1e034ee53b708b75c094eeab4e21
|
||||
F Makefile.msc ea790f1db3c02b62cb13416a680fa909b54bc1e0533cca222db0dae3f7af3db9
|
||||
F README.md 8b8df9ca852aeac4864eb1e400002633ee6db84065bd01b78c33817f97d31f5e
|
||||
F VERSION 8868ddfa6e1eee218286021a94b3e22d13e550c76c72d878857547ca001de24a
|
||||
F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
|
||||
@ -293,12 +293,11 @@ F ext/misc/blobio.c a867c4c4617f6ec223a307ebfe0eabb45e0992f74dd47722b96f3e631c0e
|
||||
F ext/misc/btreeinfo.c d28ce349b40054eaa9473e835837bad7a71deec33ba13e39f963d50933bfa0f9
|
||||
F ext/misc/carray.c b752f46411e4e47e34dce6f0c88bc8e51bb821ba9e49bfcd882506451c928f69
|
||||
F ext/misc/carray.h d2b1b12486d531367c37832d3d0dad34eea4bdd83ed839d445521ef01f0bc4e3
|
||||
F ext/misc/cksumvfs.c b42ef52eaaa510d54ec320c87bea149e934a3b06cd232be2093562bf669bd572
|
||||
F ext/misc/cksumvfs.c 9224e33cc0cb6aa61ff1d7d7b8fd6fe56beca9f9c47954fa4ae0a69bef608f69
|
||||
F ext/misc/closure.c dbfd8543b2a017ae6b1a5843986b22ddf99ff126ec9634a2f4047cd14c85c243
|
||||
F ext/misc/completion.c 6dafd7f4348eecc7be9e920d4b419d1fb2af75d938cd9c59a20cfe8beb2f22b9
|
||||
F ext/misc/compress.c 3354c77a7c8e86e07d849916000cdac451ed96500bfb5bd83b20eb61eee012c9
|
||||
F ext/misc/csv.c ca8d6dafc5469639de81937cb66ae2e6b358542aba94c4f791910d355a8e7f73
|
||||
F ext/misc/dbdata.c 8caece471d25790456bff52043592369f367cd8f41bcb55a2f6540a2c062c57a
|
||||
F ext/misc/dbdump.c b8592f6f2da292c62991a13864a60d6c573c47a9cc58362131b9e6a64f823e01
|
||||
F ext/misc/decimal.c 09f967dcf4a1ee35a76309829308ec278d3648168733f4a1147820e11ebefd12
|
||||
F ext/misc/eval.c 04bc9aada78c888394204b4ed996ab834b99726fb59603b0ee3ed6e049755dc1
|
||||
@ -386,6 +385,22 @@ F ext/rbu/rbuvacuum4.test a78898e438a44803eb2bc897ba3323373c9f277418e2d6d76e90f2
|
||||
F ext/rbu/sqlite3rbu.c 8737cabdfbee84bb25a7851ecef8b1312be332761238da9be6ddb10c62ad4291
|
||||
F ext/rbu/sqlite3rbu.h 1dc88ab7bd32d0f15890ea08d23476c4198d3da3056985403991f8c9cd389812
|
||||
F ext/rbu/test_rbu.c 03f6f177096a5f822d68d8e4069ad8907fe572c62ff2d19b141f59742821828a
|
||||
F ext/recover/dbdata.c a7b2fc6750139a7bb3455d784063d43f98c28f226b7f84d88270f1c083dc6b72 w ext/misc/dbdata.c
|
||||
F ext/recover/recover1.test 93acc42f95259f8b34050ad75873685a305da76d6cb1727d003f45157a4a6402
|
||||
F ext/recover/recover_common.tcl 6679af7dffc858e345053a91c9b0a897595b4a13007aceffafca75304ccb137c
|
||||
F ext/recover/recoverclobber.test 294dcc894124ab4ca3a7b35766630742a3d25810fceac22220beb64f70a33a60
|
||||
F ext/recover/recovercorrupt.test 6540aae95e17398dd70b44518367fd56588c44962cb276d2623a0fedba9efe9e
|
||||
F ext/recover/recovercorrupt2.test dff0c9f52888ae5956b1892ce502db42e6f51a666230369251bbbca923be541f
|
||||
F ext/recover/recoverfault.test 3a0a32b9fc216592b97775d69220695b0926980c0f7424b7a59144e47d7cb568
|
||||
F ext/recover/recoverfault2.test 321036336af23e778a87f148c4cc4407f88fbdab1fd72ddb661669be9020d36b
|
||||
F ext/recover/recoverold.test 46e9d99b595fac583d4c67f74d7d89c20a435c752ef6eeb3e918b599940c88e0
|
||||
F ext/recover/recoverpgsz.test 93e970eab05e4e89f8fd6b1bd23f9ec137ea09857e66ba0d4d27a83cd1ba4a89
|
||||
F ext/recover/recoverrowid.test 1694a1a5526d825f71279f3d02ab02a1ee4c5265de18858bf54cb8ec54487ac8
|
||||
F ext/recover/recoverslowidx.test f356bb9fba7ffd6fc50e045e419464f0129ac6e24decf6e919584f79c3493727
|
||||
F ext/recover/recoversql.test f9872ff2114e13ffd8ee31e1de06919f62b9b48bc080191b5bd076d10becb60f
|
||||
F ext/recover/sqlite3recover.c 537bfad4bf9f14fd43d138b988365bb38548fabec3252d3bff719524d907a1da
|
||||
F ext/recover/sqlite3recover.h f698ccc94bd4da38761035415ad08c4549a408491ff9fd5f52d34d2214f64e36
|
||||
F ext/recover/test_recover.c 61ec931e47abca6b2210f46239cafd9f3060741605e3d3c45a7c7a53f63dd957
|
||||
F ext/repair/README.md 92f5e8aae749a4dae14f02eea8e1bb42d4db2b6ce5e83dbcdd6b1446997e0c15
|
||||
F ext/repair/checkfreelist.c e21f06995ff4efdc1622dcceaea4dcba2caa83ca2f31a1607b98a8509168a996
|
||||
F ext/repair/checkindex.c 4383e4469c21e5b9ae321d0d63cec53e981af9d7a6564be6374f0eeb93dfc890
|
||||
@ -539,7 +554,7 @@ F ext/wasm/wasmfs.make ee0004813e16c283ff633e08b482008d56adf9b7d42f6c5612f7ab002
|
||||
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
|
||||
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
|
||||
F magic.txt 5ade0bc977aa135e79e3faaea894d5671b26107cc91e70783aa7dc83f22f3ba0
|
||||
F main.mk b1b02927b2e0b73d025f93e388f2f7fba07614e972453983bddc65cd2bf33bb1
|
||||
F main.mk d5fdf519235d657a3b87d70324f8ee1f057207626e5ab4daf23a0199d20a25ad
|
||||
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
|
||||
F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
|
||||
F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
|
||||
@ -567,7 +582,7 @@ F src/callback.c 4cd7225b26a97f7de5fee5ae10464bed5a78f2adefe19534cc2095b3a8ca484
|
||||
F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e
|
||||
F src/ctime.c 20507cc0b0a6c19cd882fcd0eaeda32ae6a4229fb4b024cfdf3183043d9b703d
|
||||
F src/date.c 94ce83b4cd848a387680a5f920c9018c16655db778c4d36525af0a0f34679ac5
|
||||
F src/dbpage.c 5808e91bc27fa3981b028000f8fadfdc10ce9e59a34ce7dc4e035a69be3906ec
|
||||
F src/dbpage.c 433f13b34f1e5a84cbb5f10214c12471c6f0197f276389634a1cc393ec821c3d
|
||||
F src/dbstat.c 861e08690fcb0f2ee1165eff0060ea8d4f3e2ea10f80dab7d32ad70443a6ff2d
|
||||
F src/delete.c 86573edae75e3d3e9a8b590d87db8e47222103029df4f3e11fa56044459b514e
|
||||
F src/expr.c 847f87d9df3ede2b2b0a8db088af0b9c1923b21009f8ea1b9b7b28cb0a383170
|
||||
@ -621,7 +636,7 @@ F src/random.c 546d6feb15ec69c1aafe9bb351a277cbb498fd5410e646add673acb805714960
|
||||
F src/resolve.c efea4e5fbecfd6d0a9071b0be0d952620991673391b6ffaaf4c277b0bb674633
|
||||
F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
|
||||
F src/select.c e5b6f930f068c9c5362fc76c704ae5cb5ef1e3f18759c2126f23b0cf04c0e263
|
||||
F src/shell.c.in 35873039518a51ce5c5a944183a82f615942ffc3923f41b8afe7d6289a28fdb6
|
||||
F src/shell.c.in c4497295b826971f35a7ec77c5fb33aea192c935d670a690cec71379b007eca4
|
||||
F src/sqlite.h.in d9c8a6243fc0a1c270d69db33758e34b810af3462f9bc5b4af113b347e07c69d
|
||||
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
|
||||
F src/sqlite3ext.h 5336beea1868d99d2f62e628dbea55e97267dbff8193291ab175e960c5df9141
|
||||
@ -673,7 +688,7 @@ F src/test_server.c a2615049954cbb9cfb4a62e18e2f0616e4dc38fe
|
||||
F src/test_sqllog.c 540feaea7280cd5f926168aee9deb1065ae136d0bbbe7361e2ef3541783e187a
|
||||
F src/test_superlock.c 4839644b9201da822f181c5bc406c0b2385f672e
|
||||
F src/test_syscall.c 1073306ba2e9bfc886771871a13d3de281ed3939
|
||||
F src/test_tclsh.c c4065ced25126e25c40122c5ff62dc89902ea617d72cdd27765151cdd7fcc477
|
||||
F src/test_tclsh.c 7dd98be675a1dc0d1fd302b8247bab992c909db384df054381a2279ad76f9b0e
|
||||
F src/test_tclvar.c 33ff42149494a39c5fbb0df3d25d6fafb2f668888e41c0688d07273dcb268dfc
|
||||
F src/test_thread.c 269ea9e1fa5828dba550eb26f619aa18aedbc29fd92f8a5f6b93521fbb74a61c
|
||||
F src/test_vdbecov.c f60c6f135ec42c0de013a1d5136777aa328a776d33277f92abac648930453d43
|
||||
@ -956,7 +971,7 @@ F test/e_totalchanges.test c927f7499dc3aa28b9b556b7d6d115a2f0fe41f012b128d16bf1f
|
||||
F test/e_update.test f46c2554d915c9197548681e8d8c33a267e84528
|
||||
F test/e_uri.test 86564382132d9c453845eeb5293c7e375487b625900ab56c181a0464908417d8
|
||||
F test/e_vacuum.test 89fc48e8beee2f9dfd6de1fbb2edea6542dae9121dc0fbe6313764169e742104
|
||||
F test/e_wal.test ae9a593207a77d711443ee69ffe081fda9243625
|
||||
F test/e_wal.test db7c33642711cf3c7959714b5f012aca08cacfa78da0382f95e849eb3ba66aa4
|
||||
F test/e_walauto.test 248af31e73c98df23476a22bdb815524c9dc3ba8
|
||||
F test/e_walckpt.test 28c371a6bb5e5fe7f31679c1df1763a19d19e8a0
|
||||
F test/e_walhook.test 01b494287ba9e60b70f6ebf3c6c62e0ffe01788e344a4846b08e5de0b344cb66
|
||||
@ -1369,7 +1384,7 @@ F test/parser1.test 6ccdf5e459a5dc4673d3273dc311a7e9742ca952dd0551a6a6320d27035c
|
||||
F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b
|
||||
F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442
|
||||
F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff
|
||||
F test/permutations.test 909c84575ac50f6d30fa125a109a01986e08c26b9ea38d28501a0711b50b5627
|
||||
F test/permutations.test 650d89ab5aad0c9fab9325b11deca8662cb5e72f43e005073d35f12ad00eaca2
|
||||
F test/pg_common.tcl 3b27542224db1e713ae387459b5d117c836a5f6e328846922993b6d2b7640d9f
|
||||
F test/pragma.test cae534c12a033a5c319ccc94f50b32811acdef9f67bf19a82ff42697caccd69f
|
||||
F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f
|
||||
@ -1423,7 +1438,7 @@ F test/rowvaluefault.test 963ae9cdaed30a85a29668dd514e639f3556cae903ee9f172ea972
|
||||
F test/rowvaluevtab.test cd9747bb3f308086944c07968f547ad6b05022e698d80b9ffbdfe09ce0b8da6f
|
||||
F test/rtree.test 0c8d9dd458d6824e59683c19ab2ffa9ef946f798
|
||||
F test/run-wordcount.sh 891e89c4c2d16e629cd45951d4ed899ad12afc09
|
||||
F test/savepoint.test 1f8a6b1aea9a0d05837adc463d4bf47bd9d0f1c842f1c2a9caccd639baf34bf9
|
||||
F test/savepoint.test 6e9804a17767f08432c7a5e738b9a8f4b891d243110b63d3a41d270d3d1378ec
|
||||
F test/savepoint2.test 9b8543940572a2f01a18298c3135ad0c9f4f67d7
|
||||
F test/savepoint4.test c8f8159ade6d2acd9128be61e1230f1c1edc6cc0
|
||||
F test/savepoint5.test 0735db177e0ebbaedc39812c8d065075d563c4fd
|
||||
@ -1558,7 +1573,7 @@ F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30
|
||||
F test/temptable2.test 76821347810ecc88203e6ef0dd6897b6036ac788e9dd3e6b04fd4d1631311a16
|
||||
F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637
|
||||
F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc
|
||||
F test/tester.tcl d759ac44a501fb832f2ea966429ca18acfba0f9a8d34ad5c499332b079b37023
|
||||
F test/tester.tcl 65c29b6f1dbf71b0e59a7b221d7e849dfa5a55fa7d0a2902811e8abdecdb1d44
|
||||
F test/testrunner.tcl 86b57135754ab2160aeb04b4829d321fb285a5cfa7a505fe61d69aed605854cc
|
||||
F test/thread001.test a0985c117eab62c0c65526e9fa5d1360dd1cac5b03bde223902763274ce21899
|
||||
F test/thread002.test c24c83408e35ba5a952a3638b7ac03ccdf1ce4409289c54a050ac4c5f1de7502
|
||||
@ -2037,8 +2052,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 3b5aa50c223ac35c7d73e4629420a01408cd74d19ae5b887f91b7a657d91e026
|
||||
R 35ce71983125759a6aec05074aeb42bb
|
||||
P 4eef562a00ae988f2426c9af51f4165c0e4cbccd601061664a0c54c19b9cc70f a67082357a2cc348faf8236aafa7f39eb5cb673b1d114a594c6d5bb257f85b73
|
||||
R 1abad855e0472232f0e7862b9a7dd438
|
||||
U dan
|
||||
Z abadf61b49ea483e6a667d1435917fe4
|
||||
Z 07060bbf38e0bec12a8e0d42fa98e255
|
||||
# Remove this line to create a well-formed Fossil manifest.
|
||||
|
@ -1 +1 @@
|
||||
4eef562a00ae988f2426c9af51f4165c0e4cbccd601061664a0c54c19b9cc70f
|
||||
a820792548da596ed0f8ab3fd1c04ec16647abbe51a2a5aac9e17c4f58be6d97
|
18
src/dbpage.c
18
src/dbpage.c
@ -274,12 +274,18 @@ static int dbpageColumn(
|
||||
}
|
||||
case 1: { /* data */
|
||||
DbPage *pDbPage = 0;
|
||||
rc = sqlite3PagerGet(pCsr->pPager, pCsr->pgno, (DbPage**)&pDbPage, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3_result_blob(ctx, sqlite3PagerGetData(pDbPage), pCsr->szPage,
|
||||
SQLITE_TRANSIENT);
|
||||
if( pCsr->pgno==((PENDING_BYTE/pCsr->szPage)+1) ){
|
||||
/* The pending byte page. Assume it is zeroed out. Attempting to
|
||||
** request this page from the page is an SQLITE_CORRUPT error. */
|
||||
sqlite3_result_zeroblob(ctx, pCsr->szPage);
|
||||
}else{
|
||||
rc = sqlite3PagerGet(pCsr->pPager, pCsr->pgno, (DbPage**)&pDbPage, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3_result_blob(ctx, sqlite3PagerGetData(pDbPage), pCsr->szPage,
|
||||
SQLITE_TRANSIENT);
|
||||
}
|
||||
sqlite3PagerUnref(pDbPage);
|
||||
}
|
||||
sqlite3PagerUnref(pDbPage);
|
||||
break;
|
||||
}
|
||||
default: { /* schema */
|
||||
@ -288,7 +294,7 @@ static int dbpageColumn(
|
||||
break;
|
||||
}
|
||||
}
|
||||
return SQLITE_OK;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int dbpageRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
|
||||
|
660
src/shell.c.in
660
src/shell.c.in
@ -1058,7 +1058,9 @@ INCLUDE ../ext/expert/sqlite3expert.h
|
||||
INCLUDE ../ext/expert/sqlite3expert.c
|
||||
|
||||
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
|
||||
INCLUDE ../ext/misc/dbdata.c
|
||||
INCLUDE ../ext/recover/dbdata.c
|
||||
INCLUDE ../ext/recover/sqlite3recover.h
|
||||
INCLUDE ../ext/recover/sqlite3recover.c
|
||||
#endif
|
||||
|
||||
#if defined(SQLITE_ENABLE_SESSION)
|
||||
@ -7278,363 +7280,15 @@ end_ar_command:
|
||||
#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) */
|
||||
|
||||
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
|
||||
/*
|
||||
** If (*pRc) is not SQLITE_OK when this function is called, it is a no-op.
|
||||
** Otherwise, the SQL statement or statements in zSql are executed using
|
||||
** database connection db and the error code written to *pRc before
|
||||
** this function returns.
|
||||
*/
|
||||
static void shellExec(sqlite3 *db, int *pRc, const char *zSql){
|
||||
int rc = *pRc;
|
||||
if( rc==SQLITE_OK ){
|
||||
char *zErr = 0;
|
||||
rc = sqlite3_exec(db, zSql, 0, 0, &zErr);
|
||||
if( rc!=SQLITE_OK ){
|
||||
raw_printf(stderr, "SQL error: %s\n", zErr);
|
||||
}
|
||||
sqlite3_free(zErr);
|
||||
*pRc = rc;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Like shellExec(), except that zFmt is a printf() style format string.
|
||||
** This function is used as a callback by the recover extension. Simply
|
||||
** print the supplied SQL statement to stdout.
|
||||
*/
|
||||
static void shellExecPrintf(sqlite3 *db, int *pRc, const char *zFmt, ...){
|
||||
char *z = 0;
|
||||
if( *pRc==SQLITE_OK ){
|
||||
va_list ap;
|
||||
va_start(ap, zFmt);
|
||||
z = sqlite3_vmprintf(zFmt, ap);
|
||||
va_end(ap);
|
||||
if( z==0 ){
|
||||
*pRc = SQLITE_NOMEM;
|
||||
}else{
|
||||
shellExec(db, pRc, z);
|
||||
}
|
||||
sqlite3_free(z);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** If *pRc is not SQLITE_OK when this function is called, it is a no-op.
|
||||
** Otherwise, an attempt is made to allocate, zero and return a pointer
|
||||
** to a buffer nByte bytes in size. If an OOM error occurs, *pRc is set
|
||||
** to SQLITE_NOMEM and NULL returned.
|
||||
*/
|
||||
static void *shellMalloc(int *pRc, sqlite3_int64 nByte){
|
||||
void *pRet = 0;
|
||||
if( *pRc==SQLITE_OK ){
|
||||
pRet = sqlite3_malloc64(nByte);
|
||||
if( pRet==0 ){
|
||||
*pRc = SQLITE_NOMEM;
|
||||
}else{
|
||||
memset(pRet, 0, nByte);
|
||||
}
|
||||
}
|
||||
return pRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** If *pRc is not SQLITE_OK when this function is called, it is a no-op.
|
||||
** Otherwise, zFmt is treated as a printf() style string. The result of
|
||||
** formatting it along with any trailing arguments is written into a
|
||||
** buffer obtained from sqlite3_malloc(), and pointer to which is returned.
|
||||
** It is the responsibility of the caller to eventually free this buffer
|
||||
** using a call to sqlite3_free().
|
||||
**
|
||||
** If an OOM error occurs, (*pRc) is set to SQLITE_NOMEM and a NULL
|
||||
** pointer returned.
|
||||
*/
|
||||
static char *shellMPrintf(int *pRc, const char *zFmt, ...){
|
||||
char *z = 0;
|
||||
if( *pRc==SQLITE_OK ){
|
||||
va_list ap;
|
||||
va_start(ap, zFmt);
|
||||
z = sqlite3_vmprintf(zFmt, ap);
|
||||
va_end(ap);
|
||||
if( z==0 ){
|
||||
*pRc = SQLITE_NOMEM;
|
||||
}
|
||||
}
|
||||
return z;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** When running the ".recover" command, each output table, and the special
|
||||
** orphaned row table if it is required, is represented by an instance
|
||||
** of the following struct.
|
||||
*/
|
||||
typedef struct RecoverTable RecoverTable;
|
||||
struct RecoverTable {
|
||||
char *zQuoted; /* Quoted version of table name */
|
||||
int nCol; /* Number of columns in table */
|
||||
char **azlCol; /* Array of column lists */
|
||||
int iPk; /* Index of IPK column */
|
||||
};
|
||||
|
||||
/*
|
||||
** Free a RecoverTable object allocated by recoverFindTable() or
|
||||
** recoverOrphanTable().
|
||||
*/
|
||||
static void recoverFreeTable(RecoverTable *pTab){
|
||||
if( pTab ){
|
||||
sqlite3_free(pTab->zQuoted);
|
||||
if( pTab->azlCol ){
|
||||
int i;
|
||||
for(i=0; i<=pTab->nCol; i++){
|
||||
sqlite3_free(pTab->azlCol[i]);
|
||||
}
|
||||
sqlite3_free(pTab->azlCol);
|
||||
}
|
||||
sqlite3_free(pTab);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is a no-op if (*pRc) is not SQLITE_OK when it is called.
|
||||
** Otherwise, it allocates and returns a RecoverTable object based on the
|
||||
** final four arguments passed to this function. It is the responsibility
|
||||
** of the caller to eventually free the returned object using
|
||||
** recoverFreeTable().
|
||||
*/
|
||||
static RecoverTable *recoverNewTable(
|
||||
int *pRc, /* IN/OUT: Error code */
|
||||
const char *zName, /* Name of table */
|
||||
const char *zSql, /* CREATE TABLE statement */
|
||||
int bIntkey,
|
||||
int nCol
|
||||
){
|
||||
sqlite3 *dbtmp = 0; /* sqlite3 handle for testing CREATE TABLE */
|
||||
int rc = *pRc;
|
||||
RecoverTable *pTab = 0;
|
||||
|
||||
pTab = (RecoverTable*)shellMalloc(&rc, sizeof(RecoverTable));
|
||||
if( rc==SQLITE_OK ){
|
||||
int nSqlCol = 0;
|
||||
int bSqlIntkey = 0;
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
|
||||
rc = sqlite3_open("", &dbtmp);
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3_create_function(dbtmp, "shell_idquote", 1, SQLITE_UTF8, 0,
|
||||
shellIdQuote, 0, 0);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_exec(dbtmp, "PRAGMA writable_schema = on", 0, 0, 0);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_exec(dbtmp, zSql, 0, 0, 0);
|
||||
if( rc==SQLITE_ERROR ){
|
||||
rc = SQLITE_OK;
|
||||
goto finished;
|
||||
}
|
||||
}
|
||||
shellPreparePrintf(dbtmp, &rc, &pStmt,
|
||||
"SELECT count(*) FROM pragma_table_info(%Q)", zName
|
||||
);
|
||||
if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
nSqlCol = sqlite3_column_int(pStmt, 0);
|
||||
}
|
||||
shellFinalize(&rc, pStmt);
|
||||
|
||||
if( rc!=SQLITE_OK || nSqlCol<nCol ){
|
||||
goto finished;
|
||||
}
|
||||
|
||||
shellPreparePrintf(dbtmp, &rc, &pStmt,
|
||||
"SELECT ("
|
||||
" SELECT substr(data,1,1)==X'0D' FROM sqlite_dbpage WHERE pgno=rootpage"
|
||||
") FROM sqlite_schema WHERE name = %Q", zName
|
||||
);
|
||||
if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
bSqlIntkey = sqlite3_column_int(pStmt, 0);
|
||||
}
|
||||
shellFinalize(&rc, pStmt);
|
||||
|
||||
if( bIntkey==bSqlIntkey ){
|
||||
int i;
|
||||
const char *zPk = "_rowid_";
|
||||
sqlite3_stmt *pPkFinder = 0;
|
||||
|
||||
/* If this is an intkey table and there is an INTEGER PRIMARY KEY,
|
||||
** set zPk to the name of the PK column, and pTab->iPk to the index
|
||||
** of the column, where columns are 0-numbered from left to right.
|
||||
** Or, if this is a WITHOUT ROWID table or if there is no IPK column,
|
||||
** leave zPk as "_rowid_" and pTab->iPk at -2. */
|
||||
pTab->iPk = -2;
|
||||
if( bIntkey ){
|
||||
shellPreparePrintf(dbtmp, &rc, &pPkFinder,
|
||||
"SELECT cid, name FROM pragma_table_info(%Q) "
|
||||
" WHERE pk=1 AND type='integer' COLLATE nocase"
|
||||
" AND NOT EXISTS (SELECT cid FROM pragma_table_info(%Q) WHERE pk=2)"
|
||||
, zName, zName
|
||||
);
|
||||
if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPkFinder) ){
|
||||
pTab->iPk = sqlite3_column_int(pPkFinder, 0);
|
||||
zPk = (const char*)sqlite3_column_text(pPkFinder, 1);
|
||||
if( zPk==0 ){ zPk = "_"; /* Defensive. Should never happen */ }
|
||||
}
|
||||
}
|
||||
|
||||
pTab->zQuoted = shellMPrintf(&rc, "\"%w\"", zName);
|
||||
pTab->azlCol = (char**)shellMalloc(&rc, sizeof(char*) * (nSqlCol+1));
|
||||
pTab->nCol = nSqlCol;
|
||||
|
||||
if( bIntkey ){
|
||||
pTab->azlCol[0] = shellMPrintf(&rc, "\"%w\"", zPk);
|
||||
}else{
|
||||
pTab->azlCol[0] = shellMPrintf(&rc, "");
|
||||
}
|
||||
i = 1;
|
||||
shellPreparePrintf(dbtmp, &rc, &pStmt,
|
||||
"SELECT %Q || group_concat(shell_idquote(name), ', ') "
|
||||
" FILTER (WHERE cid!=%d) OVER (ORDER BY %s cid) "
|
||||
"FROM pragma_table_info(%Q)",
|
||||
bIntkey ? ", " : "", pTab->iPk,
|
||||
bIntkey ? "" : "(CASE WHEN pk=0 THEN 1000000 ELSE pk END), ",
|
||||
zName
|
||||
);
|
||||
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
const char *zText = (const char*)sqlite3_column_text(pStmt, 0);
|
||||
pTab->azlCol[i] = shellMPrintf(&rc, "%s%s", pTab->azlCol[0], zText);
|
||||
i++;
|
||||
}
|
||||
shellFinalize(&rc, pStmt);
|
||||
|
||||
shellFinalize(&rc, pPkFinder);
|
||||
}
|
||||
}
|
||||
|
||||
finished:
|
||||
sqlite3_close(dbtmp);
|
||||
*pRc = rc;
|
||||
if( rc!=SQLITE_OK || (pTab && pTab->zQuoted==0) ){
|
||||
recoverFreeTable(pTab);
|
||||
pTab = 0;
|
||||
}
|
||||
return pTab;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is called to search the schema recovered from the
|
||||
** sqlite_schema table of the (possibly) corrupt database as part
|
||||
** of a ".recover" command. Specifically, for a table with root page
|
||||
** iRoot and at least nCol columns. Additionally, if bIntkey is 0, the
|
||||
** table must be a WITHOUT ROWID table, or if non-zero, not one of
|
||||
** those.
|
||||
**
|
||||
** If a table is found, a (RecoverTable*) object is returned. Or, if
|
||||
** no such table is found, but bIntkey is false and iRoot is the
|
||||
** root page of an index in the recovered schema, then (*pbNoop) is
|
||||
** set to true and NULL returned. Or, if there is no such table or
|
||||
** index, NULL is returned and (*pbNoop) set to 0, indicating that
|
||||
** the caller should write data to the orphans table.
|
||||
*/
|
||||
static RecoverTable *recoverFindTable(
|
||||
ShellState *pState, /* Shell state object */
|
||||
int *pRc, /* IN/OUT: Error code */
|
||||
int iRoot, /* Root page of table */
|
||||
int bIntkey, /* True for an intkey table */
|
||||
int nCol, /* Number of columns in table */
|
||||
int *pbNoop /* OUT: True if iRoot is root of index */
|
||||
){
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
RecoverTable *pRet = 0;
|
||||
int bNoop = 0;
|
||||
const char *zSql = 0;
|
||||
const char *zName = 0;
|
||||
|
||||
/* Search the recovered schema for an object with root page iRoot. */
|
||||
shellPreparePrintf(pState->db, pRc, &pStmt,
|
||||
"SELECT type, name, sql FROM recovery.schema WHERE rootpage=%d", iRoot
|
||||
);
|
||||
while( *pRc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
const char *zType = (const char*)sqlite3_column_text(pStmt, 0);
|
||||
if( bIntkey==0 && sqlite3_stricmp(zType, "index")==0 ){
|
||||
bNoop = 1;
|
||||
break;
|
||||
}
|
||||
if( sqlite3_stricmp(zType, "table")==0 ){
|
||||
zName = (const char*)sqlite3_column_text(pStmt, 1);
|
||||
zSql = (const char*)sqlite3_column_text(pStmt, 2);
|
||||
if( zName!=0 && zSql!=0 ){
|
||||
pRet = recoverNewTable(pRc, zName, zSql, bIntkey, nCol);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shellFinalize(pRc, pStmt);
|
||||
*pbNoop = bNoop;
|
||||
return pRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a RecoverTable object representing the orphans table.
|
||||
*/
|
||||
static RecoverTable *recoverOrphanTable(
|
||||
ShellState *pState, /* Shell state object */
|
||||
int *pRc, /* IN/OUT: Error code */
|
||||
const char *zLostAndFound, /* Base name for orphans table */
|
||||
int nCol /* Number of user data columns */
|
||||
){
|
||||
RecoverTable *pTab = 0;
|
||||
if( nCol>=0 && *pRc==SQLITE_OK ){
|
||||
int i;
|
||||
|
||||
/* This block determines the name of the orphan table. The prefered
|
||||
** name is zLostAndFound. But if that clashes with another name
|
||||
** in the recovered schema, try zLostAndFound_0, zLostAndFound_1
|
||||
** and so on until a non-clashing name is found. */
|
||||
int iTab = 0;
|
||||
char *zTab = shellMPrintf(pRc, "%s", zLostAndFound);
|
||||
sqlite3_stmt *pTest = 0;
|
||||
shellPrepare(pState->db, pRc,
|
||||
"SELECT 1 FROM recovery.schema WHERE name=?", &pTest
|
||||
);
|
||||
if( pTest ) sqlite3_bind_text(pTest, 1, zTab, -1, SQLITE_TRANSIENT);
|
||||
while( *pRc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pTest) ){
|
||||
shellReset(pRc, pTest);
|
||||
sqlite3_free(zTab);
|
||||
zTab = shellMPrintf(pRc, "%s_%d", zLostAndFound, iTab++);
|
||||
sqlite3_bind_text(pTest, 1, zTab, -1, SQLITE_TRANSIENT);
|
||||
}
|
||||
shellFinalize(pRc, pTest);
|
||||
|
||||
pTab = (RecoverTable*)shellMalloc(pRc, sizeof(RecoverTable));
|
||||
if( pTab ){
|
||||
pTab->zQuoted = shellMPrintf(pRc, "\"%w\"", zTab);
|
||||
pTab->nCol = nCol;
|
||||
pTab->iPk = -2;
|
||||
if( nCol>0 ){
|
||||
pTab->azlCol = (char**)shellMalloc(pRc, sizeof(char*) * (nCol+1));
|
||||
if( pTab->azlCol ){
|
||||
pTab->azlCol[nCol] = shellMPrintf(pRc, "");
|
||||
for(i=nCol-1; i>=0; i--){
|
||||
pTab->azlCol[i] = shellMPrintf(pRc, "%s, NULL", pTab->azlCol[i+1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( *pRc!=SQLITE_OK ){
|
||||
recoverFreeTable(pTab);
|
||||
pTab = 0;
|
||||
}else{
|
||||
raw_printf(pState->out,
|
||||
"CREATE TABLE %s(rootpgno INTEGER, "
|
||||
"pgno INTEGER, nfield INTEGER, id INTEGER", pTab->zQuoted
|
||||
);
|
||||
for(i=0; i<nCol; i++){
|
||||
raw_printf(pState->out, ", c%d", i);
|
||||
}
|
||||
raw_printf(pState->out, ");\n");
|
||||
}
|
||||
}
|
||||
sqlite3_free(zTab);
|
||||
}
|
||||
return pTab;
|
||||
static int recoverSqlCb(void *pCtx, const char *zSql){
|
||||
ShellState *pState = (ShellState*)pCtx;
|
||||
utf8_printf(pState->out, "%s;\n", zSql);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -7644,17 +7298,13 @@ static RecoverTable *recoverOrphanTable(
|
||||
*/
|
||||
static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){
|
||||
int rc = SQLITE_OK;
|
||||
sqlite3_stmt *pLoop = 0; /* Loop through all root pages */
|
||||
sqlite3_stmt *pPages = 0; /* Loop through all pages in a group */
|
||||
sqlite3_stmt *pCells = 0; /* Loop through all cells in a page */
|
||||
const char *zRecoveryDb = ""; /* Name of "recovery" database */
|
||||
const char *zLostAndFound = "lost_and_found";
|
||||
int i;
|
||||
int nOrphan = -1;
|
||||
RecoverTable *pOrphan = 0;
|
||||
|
||||
const char *zLAF = "lost_and_found";
|
||||
int bFreelist = 1; /* 0 if --freelist-corrupt is specified */
|
||||
int bRowids = 1; /* 0 if --no-rowids */
|
||||
sqlite3_recover *p = 0;
|
||||
int i = 0;
|
||||
|
||||
for(i=1; i<nArg; i++){
|
||||
char *z = azArg[i];
|
||||
int n;
|
||||
@ -7669,7 +7319,7 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){
|
||||
}else
|
||||
if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){
|
||||
i++;
|
||||
zLostAndFound = azArg[i];
|
||||
zLAF = azArg[i];
|
||||
}else
|
||||
if( n<=10 && memcmp("-no-rowids", z, n)==0 ){
|
||||
bRowids = 0;
|
||||
@ -7681,278 +7331,22 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){
|
||||
}
|
||||
}
|
||||
|
||||
shellExecPrintf(pState->db, &rc,
|
||||
/* Attach an in-memory database named 'recovery'. Create an indexed
|
||||
** cache of the sqlite_dbptr virtual table. */
|
||||
"PRAGMA writable_schema = on;"
|
||||
"ATTACH %Q AS recovery;"
|
||||
"DROP TABLE IF EXISTS recovery.dbptr;"
|
||||
"DROP TABLE IF EXISTS recovery.freelist;"
|
||||
"DROP TABLE IF EXISTS recovery.map;"
|
||||
"DROP TABLE IF EXISTS recovery.schema;"
|
||||
"CREATE TABLE recovery.freelist(pgno INTEGER PRIMARY KEY);", zRecoveryDb
|
||||
p = sqlite3_recover_init_sql(
|
||||
pState->db, "main", recoverSqlCb, (void*)pState
|
||||
);
|
||||
|
||||
if( bFreelist ){
|
||||
shellExec(pState->db, &rc,
|
||||
"WITH trunk(pgno) AS ("
|
||||
" SELECT shell_int32("
|
||||
" (SELECT data FROM sqlite_dbpage WHERE pgno=1), 8) AS x "
|
||||
" WHERE x>0"
|
||||
" UNION"
|
||||
" SELECT shell_int32("
|
||||
" (SELECT data FROM sqlite_dbpage WHERE pgno=trunk.pgno), 0) AS x "
|
||||
" FROM trunk WHERE x>0"
|
||||
"),"
|
||||
"freelist(data, n, freepgno) AS ("
|
||||
" SELECT data, min(16384, shell_int32(data, 1)-1), t.pgno "
|
||||
" FROM trunk t, sqlite_dbpage s WHERE s.pgno=t.pgno"
|
||||
" UNION ALL"
|
||||
" SELECT data, n-1, shell_int32(data, 2+n) "
|
||||
" FROM freelist WHERE n>=0"
|
||||
")"
|
||||
"REPLACE INTO recovery.freelist SELECT freepgno FROM freelist;"
|
||||
);
|
||||
sqlite3_recover_config(p, 789, (void*)zRecoveryDb);
|
||||
sqlite3_recover_config(p, SQLITE_RECOVER_LOST_AND_FOUND, (void*)zLAF);
|
||||
sqlite3_recover_config(p, SQLITE_RECOVER_ROWIDS, (void*)&bRowids);
|
||||
sqlite3_recover_config(p, SQLITE_RECOVER_FREELIST_CORRUPT,(void*)&bFreelist);
|
||||
|
||||
sqlite3_recover_run(p);
|
||||
if( sqlite3_recover_errcode(p)!=SQLITE_OK ){
|
||||
const char *zErr = sqlite3_recover_errmsg(p);
|
||||
int errCode = sqlite3_recover_errcode(p);
|
||||
raw_printf(stderr, "sql error: %s (%d)\n", zErr, errCode);
|
||||
}
|
||||
|
||||
/* If this is an auto-vacuum database, add all pointer-map pages to
|
||||
** the freelist table. Do this regardless of whether or not
|
||||
** --freelist-corrupt was specified. */
|
||||
shellExec(pState->db, &rc,
|
||||
"WITH ptrmap(pgno) AS ("
|
||||
" SELECT 2 WHERE shell_int32("
|
||||
" (SELECT data FROM sqlite_dbpage WHERE pgno=1), 13"
|
||||
" )"
|
||||
" UNION ALL "
|
||||
" SELECT pgno+1+(SELECT page_size FROM pragma_page_size)/5 AS pp "
|
||||
" FROM ptrmap WHERE pp<=(SELECT page_count FROM pragma_page_count)"
|
||||
")"
|
||||
"REPLACE INTO recovery.freelist SELECT pgno FROM ptrmap"
|
||||
);
|
||||
|
||||
shellExec(pState->db, &rc,
|
||||
"CREATE TABLE recovery.dbptr("
|
||||
" pgno, child, PRIMARY KEY(child, pgno)"
|
||||
") WITHOUT ROWID;"
|
||||
"INSERT OR IGNORE INTO recovery.dbptr(pgno, child) "
|
||||
" SELECT * FROM sqlite_dbptr"
|
||||
" WHERE pgno NOT IN freelist AND child NOT IN freelist;"
|
||||
|
||||
/* Delete any pointer to page 1. This ensures that page 1 is considered
|
||||
** a root page, regardless of how corrupt the db is. */
|
||||
"DELETE FROM recovery.dbptr WHERE child = 1;"
|
||||
|
||||
/* Delete all pointers to any pages that have more than one pointer
|
||||
** to them. Such pages will be treated as root pages when recovering
|
||||
** data. */
|
||||
"DELETE FROM recovery.dbptr WHERE child IN ("
|
||||
" SELECT child FROM recovery.dbptr GROUP BY child HAVING count(*)>1"
|
||||
");"
|
||||
|
||||
/* Create the "map" table that will (eventually) contain instructions
|
||||
** for dealing with each page in the db that contains one or more
|
||||
** records. */
|
||||
"CREATE TABLE recovery.map("
|
||||
"pgno INTEGER PRIMARY KEY, maxlen INT, intkey, root INT"
|
||||
");"
|
||||
|
||||
/* Populate table [map]. If there are circular loops of pages in the
|
||||
** database, the following adds all pages in such a loop to the map
|
||||
** as individual root pages. This could be handled better. */
|
||||
"WITH pages(i, maxlen) AS ("
|
||||
" SELECT page_count, ("
|
||||
" SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=page_count"
|
||||
" ) FROM pragma_page_count WHERE page_count>0"
|
||||
" UNION ALL"
|
||||
" SELECT i-1, ("
|
||||
" SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=i-1"
|
||||
" ) FROM pages WHERE i>=2"
|
||||
")"
|
||||
"INSERT INTO recovery.map(pgno, maxlen, intkey, root) "
|
||||
" SELECT i, maxlen, NULL, ("
|
||||
" WITH p(orig, pgno, parent) AS ("
|
||||
" SELECT 0, i, (SELECT pgno FROM recovery.dbptr WHERE child=i)"
|
||||
" UNION "
|
||||
" SELECT i, p.parent, "
|
||||
" (SELECT pgno FROM recovery.dbptr WHERE child=p.parent) FROM p"
|
||||
" )"
|
||||
" SELECT pgno FROM p WHERE (parent IS NULL OR pgno = orig)"
|
||||
") "
|
||||
"FROM pages WHERE maxlen IS NOT NULL AND i NOT IN freelist;"
|
||||
"UPDATE recovery.map AS o SET intkey = ("
|
||||
" SELECT substr(data, 1, 1)==X'0D' FROM sqlite_dbpage WHERE pgno=o.pgno"
|
||||
");"
|
||||
|
||||
/* Extract data from page 1 and any linked pages into table
|
||||
** recovery.schema. With the same schema as an sqlite_schema table. */
|
||||
"CREATE TABLE recovery.schema(type, name, tbl_name, rootpage, sql);"
|
||||
"INSERT INTO recovery.schema SELECT "
|
||||
" max(CASE WHEN field=0 THEN value ELSE NULL END),"
|
||||
" max(CASE WHEN field=1 THEN value ELSE NULL END),"
|
||||
" max(CASE WHEN field=2 THEN value ELSE NULL END),"
|
||||
" max(CASE WHEN field=3 THEN value ELSE NULL END),"
|
||||
" max(CASE WHEN field=4 THEN value ELSE NULL END)"
|
||||
"FROM sqlite_dbdata WHERE pgno IN ("
|
||||
" SELECT pgno FROM recovery.map WHERE root=1"
|
||||
")"
|
||||
"GROUP BY pgno, cell;"
|
||||
"CREATE INDEX recovery.schema_rootpage ON schema(rootpage);"
|
||||
);
|
||||
|
||||
/* Open a transaction, then print out all non-virtual, non-"sqlite_%"
|
||||
** CREATE TABLE statements that extracted from the existing schema. */
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
/* ".recover" might output content in an order which causes immediate
|
||||
** foreign key constraints to be violated. So disable foreign-key
|
||||
** constraint enforcement to prevent problems when running the output
|
||||
** script. */
|
||||
raw_printf(pState->out, "PRAGMA foreign_keys=OFF;\n");
|
||||
raw_printf(pState->out, "BEGIN;\n");
|
||||
raw_printf(pState->out, "PRAGMA writable_schema = on;\n");
|
||||
shellPrepare(pState->db, &rc,
|
||||
"SELECT sql FROM recovery.schema "
|
||||
"WHERE type='table' AND sql LIKE 'create table%'", &pStmt
|
||||
);
|
||||
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
const char *zCreateTable = (const char*)sqlite3_column_text(pStmt, 0);
|
||||
raw_printf(pState->out, "CREATE TABLE IF NOT EXISTS %s;\n",
|
||||
&zCreateTable[12]
|
||||
);
|
||||
}
|
||||
shellFinalize(&rc, pStmt);
|
||||
}
|
||||
|
||||
/* Figure out if an orphan table will be required. And if so, how many
|
||||
** user columns it should contain */
|
||||
shellPrepare(pState->db, &rc,
|
||||
"SELECT coalesce(max(maxlen), -2) FROM recovery.map WHERE root>1"
|
||||
, &pLoop
|
||||
);
|
||||
if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){
|
||||
nOrphan = sqlite3_column_int(pLoop, 0);
|
||||
}
|
||||
shellFinalize(&rc, pLoop);
|
||||
pLoop = 0;
|
||||
|
||||
shellPrepare(pState->db, &rc,
|
||||
"SELECT pgno FROM recovery.map WHERE root=?", &pPages
|
||||
);
|
||||
|
||||
shellPrepare(pState->db, &rc,
|
||||
"SELECT max(field), group_concat(shell_escape_crnl(quote"
|
||||
"(case when (? AND field<0) then NULL else value end)"
|
||||
"), ', ')"
|
||||
", min(field) "
|
||||
"FROM sqlite_dbdata WHERE pgno = ? AND field != ?"
|
||||
"GROUP BY cell", &pCells
|
||||
);
|
||||
|
||||
/* Loop through each root page. */
|
||||
shellPrepare(pState->db, &rc,
|
||||
"SELECT root, intkey, max(maxlen) FROM recovery.map"
|
||||
" WHERE root>1 GROUP BY root, intkey ORDER BY root=("
|
||||
" SELECT rootpage FROM recovery.schema WHERE name='sqlite_sequence'"
|
||||
")", &pLoop
|
||||
);
|
||||
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){
|
||||
int iRoot = sqlite3_column_int(pLoop, 0);
|
||||
int bIntkey = sqlite3_column_int(pLoop, 1);
|
||||
int nCol = sqlite3_column_int(pLoop, 2);
|
||||
int bNoop = 0;
|
||||
RecoverTable *pTab;
|
||||
|
||||
assert( bIntkey==0 || bIntkey==1 );
|
||||
pTab = recoverFindTable(pState, &rc, iRoot, bIntkey, nCol, &bNoop);
|
||||
if( bNoop || rc ) continue;
|
||||
if( pTab==0 ){
|
||||
if( pOrphan==0 ){
|
||||
pOrphan = recoverOrphanTable(pState, &rc, zLostAndFound, nOrphan);
|
||||
}
|
||||
pTab = pOrphan;
|
||||
if( pTab==0 ) break;
|
||||
}
|
||||
|
||||
if( 0==sqlite3_stricmp(pTab->zQuoted, "\"sqlite_sequence\"") ){
|
||||
raw_printf(pState->out, "DELETE FROM sqlite_sequence;\n");
|
||||
}
|
||||
sqlite3_bind_int(pPages, 1, iRoot);
|
||||
if( bRowids==0 && pTab->iPk<0 ){
|
||||
sqlite3_bind_int(pCells, 1, 1);
|
||||
}else{
|
||||
sqlite3_bind_int(pCells, 1, 0);
|
||||
}
|
||||
sqlite3_bind_int(pCells, 3, pTab->iPk);
|
||||
|
||||
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPages) ){
|
||||
int iPgno = sqlite3_column_int(pPages, 0);
|
||||
sqlite3_bind_int(pCells, 2, iPgno);
|
||||
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pCells) ){
|
||||
int nField = sqlite3_column_int(pCells, 0);
|
||||
int iMin = sqlite3_column_int(pCells, 2);
|
||||
const char *zVal = (const char*)sqlite3_column_text(pCells, 1);
|
||||
|
||||
RecoverTable *pTab2 = pTab;
|
||||
if( pTab!=pOrphan && (iMin<0)!=bIntkey ){
|
||||
if( pOrphan==0 ){
|
||||
pOrphan = recoverOrphanTable(pState, &rc, zLostAndFound, nOrphan);
|
||||
}
|
||||
pTab2 = pOrphan;
|
||||
if( pTab2==0 ) break;
|
||||
}
|
||||
|
||||
nField = nField+1;
|
||||
if( pTab2==pOrphan ){
|
||||
raw_printf(pState->out,
|
||||
"INSERT INTO %s VALUES(%d, %d, %d, %s%s%s);\n",
|
||||
pTab2->zQuoted, iRoot, iPgno, nField,
|
||||
iMin<0 ? "" : "NULL, ", zVal, pTab2->azlCol[nField]
|
||||
);
|
||||
}else{
|
||||
raw_printf(pState->out, "INSERT INTO %s(%s) VALUES( %s );\n",
|
||||
pTab2->zQuoted, pTab2->azlCol[nField], zVal
|
||||
);
|
||||
}
|
||||
}
|
||||
shellReset(&rc, pCells);
|
||||
}
|
||||
shellReset(&rc, pPages);
|
||||
if( pTab!=pOrphan ) recoverFreeTable(pTab);
|
||||
}
|
||||
shellFinalize(&rc, pLoop);
|
||||
shellFinalize(&rc, pPages);
|
||||
shellFinalize(&rc, pCells);
|
||||
recoverFreeTable(pOrphan);
|
||||
|
||||
/* The rest of the schema */
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
shellPrepare(pState->db, &rc,
|
||||
"SELECT sql, name FROM recovery.schema "
|
||||
"WHERE sql NOT LIKE 'create table%'", &pStmt
|
||||
);
|
||||
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
const char *zSql = (const char*)sqlite3_column_text(pStmt, 0);
|
||||
if( sqlite3_strnicmp(zSql, "create virt", 11)==0 ){
|
||||
const char *zName = (const char*)sqlite3_column_text(pStmt, 1);
|
||||
char *zPrint = shellMPrintf(&rc,
|
||||
"INSERT INTO sqlite_schema VALUES('table', %Q, %Q, 0, %Q)",
|
||||
zName, zName, zSql
|
||||
);
|
||||
raw_printf(pState->out, "%s;\n", zPrint);
|
||||
sqlite3_free(zPrint);
|
||||
}else{
|
||||
raw_printf(pState->out, "%s;\n", zSql);
|
||||
}
|
||||
}
|
||||
shellFinalize(&rc, pStmt);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
raw_printf(pState->out, "PRAGMA writable_schema = off;\n");
|
||||
raw_printf(pState->out, "COMMIT;\n");
|
||||
}
|
||||
sqlite3_exec(pState->db, "DETACH recovery", 0, 0, 0);
|
||||
rc = sqlite3_recover_finish(p);
|
||||
return rc;
|
||||
}
|
||||
#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
|
||||
|
@ -108,6 +108,7 @@ const char *sqlite3TestInit(Tcl_Interp *interp){
|
||||
extern int TestExpert_Init(Tcl_Interp*);
|
||||
extern int Sqlitetest_window_Init(Tcl_Interp *);
|
||||
extern int Sqlitetestvdbecov_Init(Tcl_Interp *);
|
||||
extern int TestRecover_Init(Tcl_Interp*);
|
||||
|
||||
Tcl_CmdInfo cmdInfo;
|
||||
|
||||
@ -175,6 +176,7 @@ const char *sqlite3TestInit(Tcl_Interp *interp){
|
||||
TestExpert_Init(interp);
|
||||
Sqlitetest_window_Init(interp);
|
||||
Sqlitetestvdbecov_Init(interp);
|
||||
TestRecover_Init(interp);
|
||||
|
||||
Tcl_CreateObjCommand(
|
||||
interp, "load_testfixture_extensions", load_testfixture_extensions,0,0
|
||||
|
@ -15,6 +15,7 @@ source $testdir/tester.tcl
|
||||
set testprefix e_wal
|
||||
|
||||
db close
|
||||
forcedelete test.db-shm
|
||||
testvfs oldvfs -iversion 1
|
||||
|
||||
|
||||
|
@ -91,6 +91,7 @@ foreach f [glob -nocomplain \
|
||||
$testdir/../ext/fts5/test/*.test \
|
||||
$testdir/../ext/expert/*.test \
|
||||
$testdir/../ext/lsm1/test/*.test \
|
||||
$testdir/../ext/recover/*.test \
|
||||
] {
|
||||
lappend alltests $f
|
||||
}
|
||||
@ -129,6 +130,7 @@ set allquicktests [test_set $alltests -exclude {
|
||||
fts4merge5.test
|
||||
fts3cov.test fts3snippet.test fts3corrupt2.test fts3an.test
|
||||
fts3defer.test fts4langid.test fts3sort.test fts5unicode.test
|
||||
recovercorrupt.test
|
||||
|
||||
rtree4.test
|
||||
sessionbig.test
|
||||
|
@ -16,6 +16,8 @@ source $testdir/tester.tcl
|
||||
source $testdir/lock_common.tcl
|
||||
source $testdir/malloc_common.tcl
|
||||
|
||||
forcedelete test2.db
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# The following tests - savepoint-1.* - test that the SAVEPOINT, RELEASE
|
||||
# and ROLLBACK TO comands are correctly parsed, and that the auto-commit
|
||||
|
@ -1548,6 +1548,47 @@ proc explain_i {sql {db db}} {
|
||||
output2 "---- ------------ ------ ------ ------ ---------------- -- -"
|
||||
}
|
||||
|
||||
proc execsql_pp {sql {db db}} {
|
||||
set nCol 0
|
||||
$db eval $sql A {
|
||||
if {$nCol==0} {
|
||||
set nCol [llength $A(*)]
|
||||
foreach c $A(*) {
|
||||
set aWidth($c) [string length $c]
|
||||
lappend data $c
|
||||
}
|
||||
}
|
||||
foreach c $A(*) {
|
||||
set n [string length $A($c)]
|
||||
if {$n > $aWidth($c)} {
|
||||
set aWidth($c) $n
|
||||
}
|
||||
lappend data $A($c)
|
||||
}
|
||||
}
|
||||
if {$nCol>0} {
|
||||
set nTotal 0
|
||||
foreach e [array names aWidth] { incr nTotal $aWidth($e) }
|
||||
incr nTotal [expr ($nCol-1) * 3]
|
||||
incr nTotal 4
|
||||
|
||||
set fmt ""
|
||||
foreach c $A(*) {
|
||||
lappend fmt "% -$aWidth($c)s"
|
||||
}
|
||||
set fmt "| [join $fmt { | }] |"
|
||||
|
||||
puts [string repeat - $nTotal]
|
||||
for {set i 0} {$i < [llength $data]} {incr i $nCol} {
|
||||
set vals [lrange $data $i [expr $i+$nCol-1]]
|
||||
puts [format $fmt {*}$vals]
|
||||
if {$i==0} { puts [string repeat - $nTotal] }
|
||||
}
|
||||
puts [string repeat - $nTotal]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Show the VDBE program for an SQL statement but omit the Trace
|
||||
# opcode at the beginning. This procedure can be used to prove
|
||||
# that different SQL statements generate exactly the same VDBE code.
|
||||
|
Loading…
Reference in New Issue
Block a user