summaryrefslogtreecommitdiff
path: root/shell.c
diff options
context:
space:
mode:
Diffstat (limited to 'shell.c')
-rw-r--r--shell.c1841
1 files changed, 1815 insertions, 26 deletions
diff --git a/shell.c b/shell.c
index 10d8cc1c656c..cfffb011971f 100644
--- a/shell.c
+++ b/shell.c
@@ -983,6 +983,7 @@ static void shellAddSchemaName(
** We need several support functions from the SQLite core.
*/
+/* #include "sqlite3.h" */
/*
** We need several things from the ANSI and MSVCRT headers.
@@ -1336,6 +1337,7 @@ INT closedir(
** is used. If SIZE is included it must be one of the integers 224, 256,
** 384, or 512, to determine SHA3 hash variant that is computed.
*/
+/* #include "sqlite3ext.h" */
SQLITE_EXTENSION_INIT1
#include <assert.h>
#include <string.h>
@@ -2099,6 +2101,7 @@ int sqlite3_shathree_init(
** And the paths returned in the "name" column of the table are also
** relative to directory $dir.
*/
+/* #include "sqlite3ext.h" */
SQLITE_EXTENSION_INIT1
#include <stdio.h>
#include <string.h>
@@ -3056,6 +3059,7 @@ int sqlite3_fileio_init(
** faster than any human can type.
**
*/
+/* #include "sqlite3ext.h" */
SQLITE_EXTENSION_INIT1
#include <assert.h>
#include <string.h>
@@ -3572,6 +3576,7 @@ int sqlite3_completion_init(
** If the file being opened is not an appended database, then this shim is
** a pass-through into the default underlying VFS.
**/
+/* #include "sqlite3ext.h" */
SQLITE_EXTENSION_INIT1
#include <string.h>
#include <assert.h>
@@ -4228,6 +4233,7 @@ int sqlite3MemTraceDeactivate(void){
** * No support for zip64 extensions
** * Only the "inflate/deflate" (zlib) compression method is supported
*/
+/* #include "sqlite3ext.h" */
SQLITE_EXTENSION_INIT1
#include <stdio.h>
#include <string.h>
@@ -6398,6 +6404,7 @@ int sqlite3_zipfile_init(
** for working with sqlar archives and used by the shell tool's built-in
** sqlar support.
*/
+/* #include "sqlite3ext.h" */
SQLITE_EXTENSION_INIT1
#include <zlib.h>
@@ -6520,6 +6527,7 @@ int sqlite3_sqlar_init(
*/
+/* #include "sqlite3.h" */
typedef struct sqlite3expert sqlite3expert;
@@ -6688,6 +6696,7 @@ void sqlite3_expert_destroy(sqlite3expert*);
**
*************************************************************************
*/
+/* #include "sqlite3expert.h" */
#include <assert.h>
#include <string.h>
#include <stdio.h>
@@ -8631,6 +8640,863 @@ void sqlite3_expert_destroy(sqlite3expert *p){
/************************* End ../ext/expert/sqlite3expert.c ********************/
+#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
+/************************* Begin ../ext/misc/dbdata.c ******************/
+/*
+** 2019-04-17
+**
+** 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 an implementation of two eponymous virtual tables,
+** "sqlite_dbdata" and "sqlite_dbptr". Both modules require that the
+** "sqlite_dbpage" eponymous virtual table be available.
+**
+** SQLITE_DBDATA:
+** sqlite_dbdata is used to extract data directly from a database b-tree
+** page and its associated overflow pages, bypassing the b-tree layer.
+** The table schema is equivalent to:
+**
+** CREATE TABLE sqlite_dbdata(
+** pgno INTEGER,
+** cell INTEGER,
+** field INTEGER,
+** value ANY,
+** schema TEXT HIDDEN
+** );
+**
+** IMPORTANT: THE VIRTUAL TABLE SCHEMA ABOVE IS SUBJECT TO CHANGE. IN THE
+** FUTURE NEW NON-HIDDEN COLUMNS MAY BE ADDED BETWEEN "value" AND
+** "schema".
+**
+** Each page of the database is inspected. If it cannot be interpreted as
+** a b-tree page, or if it is a b-tree page containing 0 entries, the
+** sqlite_dbdata table contains no rows for that page. Otherwise, the
+** table contains one row for each field in the record associated with
+** each cell on the page. For intkey b-trees, the key value is stored in
+** field -1.
+**
+** For example, for the database:
+**
+** CREATE TABLE t1(a, b); -- root page is page 2
+** INSERT INTO t1(rowid, a, b) VALUES(5, 'v', 'five');
+** INSERT INTO t1(rowid, a, b) VALUES(10, 'x', 'ten');
+**
+** the sqlite_dbdata table contains, as well as from entries related to
+** page 1, content equivalent to:
+**
+** INSERT INTO sqlite_dbdata(pgno, cell, field, value) VALUES
+** (2, 0, -1, 5 ),
+** (2, 0, 0, 'v' ),
+** (2, 0, 1, 'five'),
+** (2, 1, -1, 10 ),
+** (2, 1, 0, 'x' ),
+** (2, 1, 1, 'ten' );
+**
+** If database corruption is encountered, this module does not report an
+** error. Instead, it attempts to extract as much data as possible and
+** ignores the corruption.
+**
+** SQLITE_DBPTR:
+** The sqlite_dbptr table has the following schema:
+**
+** CREATE TABLE sqlite_dbptr(
+** pgno INTEGER,
+** child INTEGER,
+** schema TEXT HIDDEN
+** );
+**
+** It contains one entry for each b-tree pointer between a parent and
+** child page in the database.
+*/
+#if !defined(SQLITEINT_H)
+/* #include "sqlite3ext.h" */
+
+/* typedef unsigned char u8; */
+
+#endif
+SQLITE_EXTENSION_INIT1
+#include <string.h>
+#include <assert.h>
+
+#define DBDATA_PADDING_BYTES 100
+
+typedef struct DbdataTable DbdataTable;
+typedef struct DbdataCursor DbdataCursor;
+
+/* Cursor object */
+struct DbdataCursor {
+ sqlite3_vtab_cursor base; /* Base class. Must be first */
+ sqlite3_stmt *pStmt; /* For fetching database pages */
+
+ int iPgno; /* Current page number */
+ u8 *aPage; /* Buffer containing page */
+ int nPage; /* Size of aPage[] in bytes */
+ int nCell; /* Number of cells on aPage[] */
+ int iCell; /* Current cell number */
+ int bOnePage; /* True to stop after one page */
+ int szDb;
+ sqlite3_int64 iRowid;
+
+ /* Only for the sqlite_dbdata table */
+ u8 *pRec; /* Buffer containing current record */
+ int nRec; /* Size of pRec[] in bytes */
+ int nHdr; /* Size of header in bytes */
+ int iField; /* Current field number */
+ u8 *pHdrPtr;
+ u8 *pPtr;
+
+ sqlite3_int64 iIntkey; /* Integer key value */
+};
+
+/* Table object */
+struct DbdataTable {
+ sqlite3_vtab base; /* Base class. Must be first */
+ sqlite3 *db; /* The database connection */
+ sqlite3_stmt *pStmt; /* For fetching database pages */
+ int bPtr; /* True for sqlite3_dbptr table */
+};
+
+/* Column and schema definitions for sqlite_dbdata */
+#define DBDATA_COLUMN_PGNO 0
+#define DBDATA_COLUMN_CELL 1
+#define DBDATA_COLUMN_FIELD 2
+#define DBDATA_COLUMN_VALUE 3
+#define DBDATA_COLUMN_SCHEMA 4
+#define DBDATA_SCHEMA \
+ "CREATE TABLE x(" \
+ " pgno INTEGER," \
+ " cell INTEGER," \
+ " field INTEGER," \
+ " value ANY," \
+ " schema TEXT HIDDEN" \
+ ")"
+
+/* Column and schema definitions for sqlite_dbptr */
+#define DBPTR_COLUMN_PGNO 0
+#define DBPTR_COLUMN_CHILD 1
+#define DBPTR_COLUMN_SCHEMA 2
+#define DBPTR_SCHEMA \
+ "CREATE TABLE x(" \
+ " pgno INTEGER," \
+ " child INTEGER," \
+ " schema TEXT HIDDEN" \
+ ")"
+
+/*
+** Connect to an sqlite_dbdata (pAux==0) or sqlite_dbptr (pAux!=0) virtual
+** table.
+*/
+static int dbdataConnect(
+ sqlite3 *db,
+ void *pAux,
+ int argc, const char *const*argv,
+ sqlite3_vtab **ppVtab,
+ char **pzErr
+){
+ DbdataTable *pTab = 0;
+ int rc = sqlite3_declare_vtab(db, pAux ? DBPTR_SCHEMA : DBDATA_SCHEMA);
+
+ if( rc==SQLITE_OK ){
+ pTab = (DbdataTable*)sqlite3_malloc64(sizeof(DbdataTable));
+ if( pTab==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ memset(pTab, 0, sizeof(DbdataTable));
+ pTab->db = db;
+ pTab->bPtr = (pAux!=0);
+ }
+ }
+
+ *ppVtab = (sqlite3_vtab*)pTab;
+ return rc;
+}
+
+/*
+** Disconnect from or destroy a sqlite_dbdata or sqlite_dbptr virtual table.
+*/
+static int dbdataDisconnect(sqlite3_vtab *pVtab){
+ DbdataTable *pTab = (DbdataTable*)pVtab;
+ if( pTab ){
+ sqlite3_finalize(pTab->pStmt);
+ sqlite3_free(pVtab);
+ }
+ return SQLITE_OK;
+}
+
+/*
+** This function interprets two types of constraints:
+**
+** schema=?
+** pgno=?
+**
+** If neither are present, idxNum is set to 0. If schema=? is present,
+** the 0x01 bit in idxNum is set. If pgno=? is present, the 0x02 bit
+** in idxNum is set.
+**
+** If both parameters are present, schema is in position 0 and pgno in
+** position 1.
+*/
+static int dbdataBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdx){
+ DbdataTable *pTab = (DbdataTable*)tab;
+ int i;
+ int iSchema = -1;
+ int iPgno = -1;
+ int colSchema = (pTab->bPtr ? DBPTR_COLUMN_SCHEMA : DBDATA_COLUMN_SCHEMA);
+
+ for(i=0; i<pIdx->nConstraint; i++){
+ struct sqlite3_index_constraint *p = &pIdx->aConstraint[i];
+ if( p->op==SQLITE_INDEX_CONSTRAINT_EQ ){
+ if( p->iColumn==colSchema ){
+ if( p->usable==0 ) return SQLITE_CONSTRAINT;
+ iSchema = i;
+ }
+ if( p->iColumn==DBDATA_COLUMN_PGNO && p->usable ){
+ iPgno = i;
+ }
+ }
+ }
+
+ if( iSchema>=0 ){
+ pIdx->aConstraintUsage[iSchema].argvIndex = 1;
+ pIdx->aConstraintUsage[iSchema].omit = 1;
+ }
+ if( iPgno>=0 ){
+ pIdx->aConstraintUsage[iPgno].argvIndex = 1 + (iSchema>=0);
+ pIdx->aConstraintUsage[iPgno].omit = 1;
+ pIdx->estimatedCost = 100;
+ pIdx->estimatedRows = 50;
+
+ if( pTab->bPtr==0 && pIdx->nOrderBy && pIdx->aOrderBy[0].desc==0 ){
+ int iCol = pIdx->aOrderBy[0].iColumn;
+ if( pIdx->nOrderBy==1 ){
+ pIdx->orderByConsumed = (iCol==0 || iCol==1);
+ }else if( pIdx->nOrderBy==2 && pIdx->aOrderBy[1].desc==0 && iCol==0 ){
+ pIdx->orderByConsumed = (pIdx->aOrderBy[1].iColumn==1);
+ }
+ }
+
+ }else{
+ pIdx->estimatedCost = 100000000;
+ pIdx->estimatedRows = 1000000000;
+ }
+ pIdx->idxNum = (iSchema>=0 ? 0x01 : 0x00) | (iPgno>=0 ? 0x02 : 0x00);
+ return SQLITE_OK;
+}
+
+/*
+** Open a new sqlite_dbdata or sqlite_dbptr cursor.
+*/
+static int dbdataOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
+ DbdataCursor *pCsr;
+
+ pCsr = (DbdataCursor*)sqlite3_malloc64(sizeof(DbdataCursor));
+ if( pCsr==0 ){
+ return SQLITE_NOMEM;
+ }else{
+ memset(pCsr, 0, sizeof(DbdataCursor));
+ pCsr->base.pVtab = pVTab;
+ }
+
+ *ppCursor = (sqlite3_vtab_cursor *)pCsr;
+ return SQLITE_OK;
+}
+
+/*
+** Restore a cursor object to the state it was in when first allocated
+** by dbdataOpen().
+*/
+static void dbdataResetCursor(DbdataCursor *pCsr){
+ DbdataTable *pTab = (DbdataTable*)(pCsr->base.pVtab);
+ if( pTab->pStmt==0 ){
+ pTab->pStmt = pCsr->pStmt;
+ }else{
+ sqlite3_finalize(pCsr->pStmt);
+ }
+ pCsr->pStmt = 0;
+ pCsr->iPgno = 1;
+ pCsr->iCell = 0;
+ pCsr->iField = 0;
+ pCsr->bOnePage = 0;
+ sqlite3_free(pCsr->aPage);
+ sqlite3_free(pCsr->pRec);
+ pCsr->pRec = 0;
+ pCsr->aPage = 0;
+}
+
+/*
+** Close an sqlite_dbdata or sqlite_dbptr cursor.
+*/
+static int dbdataClose(sqlite3_vtab_cursor *pCursor){
+ DbdataCursor *pCsr = (DbdataCursor*)pCursor;
+ dbdataResetCursor(pCsr);
+ sqlite3_free(pCsr);
+ return SQLITE_OK;
+}
+
+/*
+** Utility methods to decode 16 and 32-bit big-endian unsigned integers.
+*/
+static unsigned int 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]);
+}
+
+/*
+** Load page pgno from the database via the sqlite_dbpage virtual table.
+** If successful, set (*ppPage) to point to a buffer containing the page
+** data, (*pnPage) to the size of that buffer in bytes and return
+** SQLITE_OK. In this case it is the responsibility of the caller to
+** eventually free the buffer using sqlite3_free().
+**
+** Or, if an error occurs, set both (*ppPage) and (*pnPage) to 0 and
+** return an SQLite error code.
+*/
+static int dbdataLoadPage(
+ DbdataCursor *pCsr, /* Cursor object */
+ unsigned int pgno, /* Page number of page to load */
+ u8 **ppPage, /* OUT: pointer to page buffer */
+ int *pnPage /* OUT: Size of (*ppPage) in bytes */
+){
+ int rc2;
+ int rc = SQLITE_OK;
+ sqlite3_stmt *pStmt = pCsr->pStmt;
+
+ *ppPage = 0;
+ *pnPage = 0;
+ sqlite3_bind_int64(pStmt, 2, pgno);
+ if( SQLITE_ROW==sqlite3_step(pStmt) ){
+ int nCopy = sqlite3_column_bytes(pStmt, 0);
+ if( nCopy>0 ){
+ u8 *pPage;
+ pPage = (u8*)sqlite3_malloc64(nCopy + DBDATA_PADDING_BYTES);
+ if( pPage==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ const u8 *pCopy = sqlite3_column_blob(pStmt, 0);
+ memcpy(pPage, pCopy, nCopy);
+ memset(&pPage[nCopy], 0, DBDATA_PADDING_BYTES);
+ }
+ *ppPage = pPage;
+ *pnPage = nCopy;
+ }
+ }
+ rc2 = sqlite3_reset(pStmt);
+ if( rc==SQLITE_OK ) rc = rc2;
+
+ return rc;
+}
+
+/*
+** Read a varint. Put the value in *pVal and return the number of bytes.
+*/
+static int dbdataGetVarint(const u8 *z, sqlite3_int64 *pVal){
+ sqlite3_int64 v = 0;
+ int i;
+ for(i=0; i<8; i++){
+ v = (v<<7) + (z[i]&0x7f);
+ if( (z[i]&0x80)==0 ){ *pVal = v; return i+1; }
+ }
+ v = (v<<8) + (z[i]&0xff);
+ *pVal = v;
+ return 9;
+}
+
+/*
+** Return the number of bytes of space used by an SQLite value of type
+** eType.
+*/
+static int dbdataValueBytes(int eType){
+ switch( eType ){
+ case 0: case 8: case 9:
+ case 10: case 11:
+ return 0;
+ case 1:
+ return 1;
+ case 2:
+ return 2;
+ case 3:
+ return 3;
+ case 4:
+ return 4;
+ case 5:
+ return 6;
+ case 6:
+ case 7:
+ return 8;
+ default:
+ if( eType>0 ){
+ return ((eType-12) / 2);
+ }
+ return 0;
+ }
+}
+
+/*
+** Load a value of type eType from buffer pData and use it to set the
+** result of context object pCtx.
+*/
+static void dbdataValue(
+ sqlite3_context *pCtx,
+ int eType,
+ u8 *pData,
+ int nData
+){
+ if( eType>=0 && dbdataValueBytes(eType)<=nData ){
+ switch( eType ){
+ case 0:
+ case 10:
+ case 11:
+ sqlite3_result_null(pCtx);
+ break;
+
+ case 8:
+ sqlite3_result_int(pCtx, 0);
+ break;
+ case 9:
+ sqlite3_result_int(pCtx, 1);
+ break;
+
+ case 1: case 2: case 3: case 4: case 5: case 6: case 7: {
+ sqlite3_uint64 v = (signed char)pData[0];
+ pData++;
+ switch( eType ){
+ case 7:
+ case 6: v = (v<<16) + (pData[0]<<8) + pData[1]; pData += 2;
+ case 5: v = (v<<16) + (pData[0]<<8) + pData[1]; pData += 2;
+ case 4: v = (v<<8) + pData[0]; pData++;
+ case 3: v = (v<<8) + pData[0]; pData++;
+ case 2: v = (v<<8) + pData[0]; pData++;
+ }
+
+ if( eType==7 ){
+ double r;
+ memcpy(&r, &v, sizeof(r));
+ sqlite3_result_double(pCtx, r);
+ }else{
+ sqlite3_result_int64(pCtx, (sqlite3_int64)v);
+ }
+ break;
+ }
+
+ default: {
+ int n = ((eType-12) / 2);
+ if( eType % 2 ){
+ sqlite3_result_text(pCtx, (const char*)pData, n, SQLITE_TRANSIENT);
+ }else{
+ sqlite3_result_blob(pCtx, pData, n, SQLITE_TRANSIENT);
+ }
+ }
+ }
+ }
+}
+
+/*
+** Move an sqlite_dbdata or sqlite_dbptr cursor to the next entry.
+*/
+static int dbdataNext(sqlite3_vtab_cursor *pCursor){
+ DbdataCursor *pCsr = (DbdataCursor*)pCursor;
+ DbdataTable *pTab = (DbdataTable*)pCursor->pVtab;
+
+ pCsr->iRowid++;
+ while( 1 ){
+ int rc;
+ int iOff = (pCsr->iPgno==1 ? 100 : 0);
+ int bNextPage = 0;
+
+ if( pCsr->aPage==0 ){
+ while( 1 ){
+ if( pCsr->bOnePage==0 && pCsr->iPgno>pCsr->szDb ) return SQLITE_OK;
+ rc = dbdataLoadPage(pCsr, pCsr->iPgno, &pCsr->aPage, &pCsr->nPage);
+ if( rc!=SQLITE_OK ) return rc;
+ if( pCsr->aPage ) break;
+ pCsr->iPgno++;
+ }
+ pCsr->iCell = pTab->bPtr ? -2 : 0;
+ pCsr->nCell = get_uint16(&pCsr->aPage[iOff+3]);
+ }
+
+ if( pTab->bPtr ){
+ if( pCsr->aPage[iOff]!=0x02 && pCsr->aPage[iOff]!=0x05 ){
+ pCsr->iCell = pCsr->nCell;
+ }
+ pCsr->iCell++;
+ if( pCsr->iCell>=pCsr->nCell ){
+ sqlite3_free(pCsr->aPage);
+ pCsr->aPage = 0;
+ if( pCsr->bOnePage ) return SQLITE_OK;
+ pCsr->iPgno++;
+ }else{
+ return SQLITE_OK;
+ }
+ }else{
+ /* If there is no record loaded, load it now. */
+ if( pCsr->pRec==0 ){
+ int bHasRowid = 0;
+ int nPointer = 0;
+ sqlite3_int64 nPayload = 0;
+ sqlite3_int64 nHdr = 0;
+ int iHdr;
+ int U, X;
+ int nLocal;
+
+ switch( pCsr->aPage[iOff] ){
+ case 0x02:
+ nPointer = 4;
+ break;
+ case 0x0a:
+ break;
+ case 0x0d:
+ bHasRowid = 1;
+ break;
+ default:
+ /* This is not a b-tree page with records on it. Continue. */
+ pCsr->iCell = pCsr->nCell;
+ break;
+ }
+
+ if( pCsr->iCell>=pCsr->nCell ){
+ bNextPage = 1;
+ }else{
+
+ iOff += 8 + nPointer + pCsr->iCell*2;
+ if( iOff>pCsr->nPage ){
+ bNextPage = 1;
+ }else{
+ iOff = get_uint16(&pCsr->aPage[iOff]);
+ }
+
+ /* For an interior node cell, skip past the child-page number */
+ iOff += nPointer;
+
+ /* Load the "byte of payload including overflow" field */
+ if( bNextPage || iOff>pCsr->nPage ){
+ bNextPage = 1;
+ }else{
+ iOff += dbdataGetVarint(&pCsr->aPage[iOff], &nPayload);
+ }
+
+ /* If this is a leaf intkey cell, load the rowid */
+ if( bHasRowid && !bNextPage && iOff<pCsr->nPage ){
+ iOff += dbdataGetVarint(&pCsr->aPage[iOff], &pCsr->iIntkey);
+ }
+
+ /* Figure out how much data to read from the local page */
+ U = pCsr->nPage;
+ if( bHasRowid ){
+ X = U-35;
+ }else{
+ X = ((U-12)*64/255)-23;
+ }
+ if( nPayload<=X ){
+ nLocal = nPayload;
+ }else{
+ int M, K;
+ M = ((U-12)*32/255)-23;
+ K = M+((nPayload-M)%(U-4));
+ if( K<=X ){
+ nLocal = K;
+ }else{
+ nLocal = M;
+ }
+ }
+
+ if( bNextPage || nLocal+iOff>pCsr->nPage ){
+ bNextPage = 1;
+ }else{
+
+ /* Allocate space for payload. And a bit more to catch small buffer
+ ** overruns caused by attempting to read a varint or similar from
+ ** near the end of a corrupt record. */
+ pCsr->pRec = (u8*)sqlite3_malloc64(nPayload+DBDATA_PADDING_BYTES);
+ if( pCsr->pRec==0 ) return SQLITE_NOMEM;
+ memset(pCsr->pRec, 0, nPayload+DBDATA_PADDING_BYTES);
+ pCsr->nRec = nPayload;
+
+ /* Load the nLocal bytes of payload */
+ memcpy(pCsr->pRec, &pCsr->aPage[iOff], nLocal);
+ iOff += nLocal;
+
+ /* Load content from overflow pages */
+ if( nPayload>nLocal ){
+ sqlite3_int64 nRem = nPayload - nLocal;
+ unsigned int pgnoOvfl = get_uint32(&pCsr->aPage[iOff]);
+ while( nRem>0 ){
+ u8 *aOvfl = 0;
+ int nOvfl = 0;
+ int nCopy;
+ rc = dbdataLoadPage(pCsr, pgnoOvfl, &aOvfl, &nOvfl);
+ assert( rc!=SQLITE_OK || aOvfl==0 || nOvfl==pCsr->nPage );
+ if( rc!=SQLITE_OK ) return rc;
+ if( aOvfl==0 ) break;
+
+ nCopy = U-4;
+ if( nCopy>nRem ) nCopy = nRem;
+ memcpy(&pCsr->pRec[nPayload-nRem], &aOvfl[4], nCopy);
+ nRem -= nCopy;
+
+ pgnoOvfl = get_uint32(aOvfl);
+ sqlite3_free(aOvfl);
+ }
+ }
+
+ iHdr = dbdataGetVarint(pCsr->pRec, &nHdr);
+ pCsr->nHdr = nHdr;
+ pCsr->pHdrPtr = &pCsr->pRec[iHdr];
+ pCsr->pPtr = &pCsr->pRec[pCsr->nHdr];
+ pCsr->iField = (bHasRowid ? -1 : 0);
+ }
+ }
+ }else{
+ pCsr->iField++;
+ if( pCsr->iField>0 ){
+ sqlite3_int64 iType;
+ if( pCsr->pHdrPtr>&pCsr->pRec[pCsr->nRec] ){
+ bNextPage = 1;
+ }else{
+ pCsr->pHdrPtr += dbdataGetVarint(pCsr->pHdrPtr, &iType);
+ pCsr->pPtr += dbdataValueBytes(iType);
+ }
+ }
+ }
+
+ if( bNextPage ){
+ sqlite3_free(pCsr->aPage);
+ sqlite3_free(pCsr->pRec);
+ pCsr->aPage = 0;
+ pCsr->pRec = 0;
+ if( pCsr->bOnePage ) return SQLITE_OK;
+ pCsr->iPgno++;
+ }else{
+ if( pCsr->iField<0 || pCsr->pHdrPtr<&pCsr->pRec[pCsr->nHdr] ){
+ return SQLITE_OK;
+ }
+
+ /* Advance to the next cell. The next iteration of the loop will load
+ ** the record and so on. */
+ sqlite3_free(pCsr->pRec);
+ pCsr->pRec = 0;
+ pCsr->iCell++;
+ }
+ }
+ }
+
+ assert( !"can't get here" );
+ return SQLITE_OK;
+}
+
+/*
+** Return true if the cursor is at EOF.
+*/
+static int dbdataEof(sqlite3_vtab_cursor *pCursor){
+ DbdataCursor *pCsr = (DbdataCursor*)pCursor;
+ return pCsr->aPage==0;
+}
+
+/*
+** Determine the size in pages of database zSchema (where zSchema is
+** "main", "temp" or the name of an attached database) and set
+** pCsr->szDb accordingly. If successful, return SQLITE_OK. Otherwise,
+** an SQLite error code.
+*/
+static int dbdataDbsize(DbdataCursor *pCsr, const char *zSchema){
+ DbdataTable *pTab = (DbdataTable*)pCsr->base.pVtab;
+ char *zSql = 0;
+ int rc, rc2;
+ sqlite3_stmt *pStmt = 0;
+
+ 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 ){
+ pCsr->szDb = sqlite3_column_int(pStmt, 0);
+ }
+ rc2 = sqlite3_finalize(pStmt);
+ if( rc==SQLITE_OK ) rc = rc2;
+ return rc;
+}
+
+/*
+** xFilter method for sqlite_dbdata and sqlite_dbptr.
+*/
+static int dbdataFilter(
+ sqlite3_vtab_cursor *pCursor,
+ int idxNum, const char *idxStr,
+ int argc, sqlite3_value **argv
+){
+ DbdataCursor *pCsr = (DbdataCursor*)pCursor;
+ DbdataTable *pTab = (DbdataTable*)pCursor->pVtab;
+ int rc = SQLITE_OK;
+ const char *zSchema = "main";
+
+ dbdataResetCursor(pCsr);
+ assert( pCsr->iPgno==1 );
+ if( idxNum & 0x01 ){
+ zSchema = (const char*)sqlite3_value_text(argv[0]);
+ }
+ if( idxNum & 0x02 ){
+ 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 ){
+ if( pTab->pStmt ){
+ pCsr->pStmt = pTab->pStmt;
+ pTab->pStmt = 0;
+ }else{
+ rc = sqlite3_prepare_v2(pTab->db,
+ "SELECT data FROM sqlite_dbpage(?) WHERE pgno=?", -1,
+ &pCsr->pStmt, 0
+ );
+ }
+ }
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_bind_text(pCsr->pStmt, 1, zSchema, -1, SQLITE_TRANSIENT);
+ }else{
+ pTab->base.zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(pTab->db));
+ }
+ if( rc==SQLITE_OK ){
+ rc = dbdataNext(pCursor);
+ }
+ return rc;
+}
+
+/*
+** Return a column for the sqlite_dbdata or sqlite_dbptr table.
+*/
+static int dbdataColumn(
+ sqlite3_vtab_cursor *pCursor,
+ sqlite3_context *ctx,
+ int i
+){
+ DbdataCursor *pCsr = (DbdataCursor*)pCursor;
+ DbdataTable *pTab = (DbdataTable*)pCursor->pVtab;
+ if( pTab->bPtr ){
+ switch( i ){
+ case DBPTR_COLUMN_PGNO:
+ sqlite3_result_int64(ctx, pCsr->iPgno);
+ break;
+ case DBPTR_COLUMN_CHILD: {
+ int iOff = pCsr->iPgno==1 ? 100 : 0;
+ if( pCsr->iCell<0 ){
+ iOff += 8;
+ }else{
+ iOff += 12 + pCsr->iCell*2;
+ if( iOff>pCsr->nPage ) return SQLITE_OK;
+ iOff = get_uint16(&pCsr->aPage[iOff]);
+ }
+ if( iOff<=pCsr->nPage ){
+ sqlite3_result_int64(ctx, get_uint32(&pCsr->aPage[iOff]));
+ }
+ break;
+ }
+ }
+ }else{
+ switch( i ){
+ case DBDATA_COLUMN_PGNO:
+ sqlite3_result_int64(ctx, pCsr->iPgno);
+ break;
+ case DBDATA_COLUMN_CELL:
+ sqlite3_result_int(ctx, pCsr->iCell);
+ break;
+ case DBDATA_COLUMN_FIELD:
+ sqlite3_result_int(ctx, pCsr->iField);
+ break;
+ case DBDATA_COLUMN_VALUE: {
+ if( pCsr->iField<0 ){
+ sqlite3_result_int64(ctx, pCsr->iIntkey);
+ }else{
+ sqlite3_int64 iType;
+ dbdataGetVarint(pCsr->pHdrPtr, &iType);
+ dbdataValue(
+ ctx, iType, pCsr->pPtr, &pCsr->pRec[pCsr->nRec] - pCsr->pPtr
+ );
+ }
+ break;
+ }
+ }
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Return the rowid for an sqlite_dbdata or sqlite_dptr table.
+*/
+static int dbdataRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
+ DbdataCursor *pCsr = (DbdataCursor*)pCursor;
+ *pRowid = pCsr->iRowid;
+ return SQLITE_OK;
+}
+
+
+/*
+** Invoke this routine to register the "sqlite_dbdata" virtual table module
+*/
+static int sqlite3DbdataRegister(sqlite3 *db){
+ static sqlite3_module dbdata_module = {
+ 0, /* iVersion */
+ 0, /* xCreate */
+ dbdataConnect, /* xConnect */
+ dbdataBestIndex, /* xBestIndex */
+ dbdataDisconnect, /* xDisconnect */
+ 0, /* xDestroy */
+ dbdataOpen, /* xOpen - open a cursor */
+ dbdataClose, /* xClose - close a cursor */
+ dbdataFilter, /* xFilter - configure scan constraints */
+ dbdataNext, /* xNext - advance a cursor */
+ dbdataEof, /* xEof - check for end of scan */
+ dbdataColumn, /* xColumn - read data */
+ dbdataRowid, /* xRowid - read data */
+ 0, /* xUpdate */
+ 0, /* xBegin */
+ 0, /* xSync */
+ 0, /* xCommit */
+ 0, /* xRollback */
+ 0, /* xFindMethod */
+ 0, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0 /* xShadowName */
+ };
+
+ int rc = sqlite3_create_module(db, "sqlite_dbdata", &dbdata_module, 0);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_create_module(db, "sqlite_dbptr", &dbdata_module, (void*)1);
+ }
+ return rc;
+}
+
+#ifdef _WIN32
+
+#endif
+int sqlite3_dbdata_init(
+ sqlite3 *db,
+ char **pzErrMsg,
+ const sqlite3_api_routines *pApi
+){
+ SQLITE_EXTENSION_INIT2(pApi);
+ return sqlite3DbdataRegister(db);
+}
+
+/************************* End ../ext/misc/dbdata.c ********************/
+#endif
+
#if defined(SQLITE_ENABLE_SESSION)
/*
** State information for a single open session
@@ -9360,6 +10226,8 @@ static int shellAuth(
** in FTS3/4/5 into CREATE TABLE IF NOT EXISTS statements.
*/
static void printSchemaLine(FILE *out, const char *z, const char *zTail){
+ if( z==0 ) return;
+ if( zTail==0 ) return;
if( sqlite3_strglob("CREATE TABLE ['\"]*", z)==0 ){
utf8_printf(out, "CREATE TABLE IF NOT EXISTS %s%s", z+13, zTail);
}else{
@@ -11172,7 +12040,7 @@ static const char *(azHelp[]) = {
" Options:",
" --preserve-rowids Include ROWID values in the output",
" --newlines Allow unescaped newline characters in output",
- " TABLE is LIKE pattern for the tables to dump",
+ " TABLE is a LIKE pattern for the tables to dump",
".echo on|off Turn command echo on or off",
".eqp on|off|full|... Enable or disable automatic EXPLAIN QUERY PLAN",
" Other Modes:",
@@ -11187,6 +12055,8 @@ static const char *(azHelp[]) = {
/* Because explain mode comes on automatically now, the ".explain" mode
** is removed from the help screen. It is still supported for legacy, however */
/*".explain ?on|off|auto? Turn EXPLAIN output mode on or off or to automatic",*/
+ ".filectrl CMD ... Run various sqlite3_file_control() operations",
+ " Run \".filectrl\" with no arguments for details",
".fullschema ?--indent? Show schema and the content of sqlite_stat tables",
".headers on|off Turn display of headers on or off",
".help ?-all? ?PATTERN? Show help text for PATTERN",
@@ -11257,6 +12127,9 @@ static const char *(azHelp[]) = {
".prompt MAIN CONTINUE Replace the standard prompts",
".quit Exit this program",
".read FILE Read input from FILE",
+#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
+ ".recover Recover as much data as possible from corrupt db.",
+#endif
".restore ?DB? FILE Restore content of DB (default \"main\") from FILE",
".save FILE Write in-memory database into FILE",
".scanstats on|off Turn sqlite3_stmt_scanstatus() metrics on or off",
@@ -11301,6 +12174,8 @@ static const char *(azHelp[]) = {
#endif
".tables ?TABLE? List names of tables matching LIKE pattern TABLE",
".testcase NAME Begin redirecting output to 'testcase-out.txt'",
+ ".testctrl CMD ... Run various sqlite3_test_control() operations",
+ " Run \".testctrl\" with no arguments for details",
".timeout MS Try opening locked tables for MS milliseconds",
".timer on|off Turn SQL timer on or off",
#ifndef SQLITE_OMIT_TRACE
@@ -11541,7 +12416,7 @@ static unsigned char *readHexDb(ShellState *p, int *pnData){
int j, k;
int rc;
FILE *in;
- unsigned char x[16];
+ unsigned int x[16];
char zLine[1000];
if( p->zDbFilename ){
in = fopen(p->zDbFilename, "r");
@@ -11553,14 +12428,15 @@ static unsigned char *readHexDb(ShellState *p, int *pnData){
}else{
in = p->in;
nLine = p->lineno;
+ if( in==0 ) in = stdin;
}
*pnData = 0;
nLine++;
if( fgets(zLine, sizeof(zLine), in)==0 ) goto readHexDb_error;
rc = sscanf(zLine, "| size %d pagesize %d", &n, &pgsz);
if( rc!=2 ) goto readHexDb_error;
- if( n<=0 ) goto readHexDb_error;
- a = sqlite3_malloc( n );
+ if( n<0 ) goto readHexDb_error;
+ a = sqlite3_malloc( n ? n : 1 );
if( a==0 ){
utf8_printf(stderr, "Out of memory!\n");
goto readHexDb_error;
@@ -11579,14 +12455,14 @@ static unsigned char *readHexDb(ShellState *p, int *pnData){
if( strncmp(zLine, "| end ", 6)==0 ){
break;
}
- rc = sscanf(zLine,"| %d: %hhx %hhx %hhx %hhx %hhx %hhx %hhx %hhx"
- " %hhx %hhx %hhx %hhx %hhx %hhx %hhx %hhx",
+ rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x",
&j, &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7],
&x[8], &x[9], &x[10], &x[11], &x[12], &x[13], &x[14], &x[15]);
if( rc==17 ){
k = iOffset+j;
if( k+16<=n ){
- memcpy(a+k, x, 16);
+ int ii;
+ for(ii=0; ii<16; ii++) a[k+ii] = x[ii]&0xff;
}
}
}
@@ -11599,7 +12475,7 @@ static unsigned char *readHexDb(ShellState *p, int *pnData){
return a;
readHexDb_error:
- if( in!=stdin ){
+ if( in!=p->in ){
fclose(in);
}else{
while( fgets(zLine, sizeof(zLine), p->in)!=0 ){
@@ -11614,6 +12490,127 @@ readHexDb_error:
}
#endif /* SQLITE_ENABLE_DESERIALIZE */
+/*
+** Scalar function "shell_int32". The first argument to this function
+** must be a blob. The second a non-negative integer. This function
+** reads and returns a 32-bit big-endian integer from byte
+** offset (4*<arg2>) of the blob.
+*/
+static void shellInt32(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ const unsigned char *pBlob;
+ int nBlob;
+ int iInt;
+
+ UNUSED_PARAMETER(argc);
+ nBlob = sqlite3_value_bytes(argv[0]);
+ pBlob = (const unsigned char*)sqlite3_value_blob(argv[0]);
+ iInt = sqlite3_value_int(argv[1]);
+
+ if( iInt>=0 && (iInt+1)*4<=nBlob ){
+ const unsigned char *a = &pBlob[iInt*4];
+ sqlite3_int64 iVal = ((sqlite3_int64)a[0]<<24)
+ + ((sqlite3_int64)a[1]<<16)
+ + ((sqlite3_int64)a[2]<< 8)
+ + ((sqlite3_int64)a[3]<< 0);
+ sqlite3_result_int64(context, iVal);
+ }
+}
+
+/*
+** Scalar function "shell_escape_crnl" used by the .recover command.
+** The argument passed to this function is the output of built-in
+** function quote(). If the first character of the input is "'",
+** indicating that the value passed to quote() was a text value,
+** then this function searches the input for "\n" and "\r" characters
+** and adds a wrapper similar to the following:
+**
+** replace(replace(<input>, '\n', char(10), '\r', char(13));
+**
+** Or, if the first character of the input is not "'", then a copy
+** of the input is returned.
+*/
+static void shellEscapeCrnl(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ const char *zText = (const char*)sqlite3_value_text(argv[0]);
+ UNUSED_PARAMETER(argc);
+ if( zText[0]=='\'' ){
+ int nText = sqlite3_value_bytes(argv[0]);
+ int i;
+ char zBuf1[20];
+ char zBuf2[20];
+ const char *zNL = 0;
+ const char *zCR = 0;
+ int nCR = 0;
+ int nNL = 0;
+
+ for(i=0; zText[i]; i++){
+ if( zNL==0 && zText[i]=='\n' ){
+ zNL = unused_string(zText, "\\n", "\\012", zBuf1);
+ nNL = (int)strlen(zNL);
+ }
+ if( zCR==0 && zText[i]=='\r' ){
+ zCR = unused_string(zText, "\\r", "\\015", zBuf2);
+ nCR = (int)strlen(zCR);
+ }
+ }
+
+ if( zNL || zCR ){
+ int iOut = 0;
+ i64 nMax = (nNL > nCR) ? nNL : nCR;
+ i64 nAlloc = nMax * nText + (nMax+64)*2;
+ char *zOut = (char*)sqlite3_malloc64(nAlloc);
+ if( zOut==0 ){
+ sqlite3_result_error_nomem(context);
+ return;
+ }
+
+ if( zNL && zCR ){
+ memcpy(&zOut[iOut], "replace(replace(", 16);
+ iOut += 16;
+ }else{
+ memcpy(&zOut[iOut], "replace(", 8);
+ iOut += 8;
+ }
+ for(i=0; zText[i]; i++){
+ if( zText[i]=='\n' ){
+ memcpy(&zOut[iOut], zNL, nNL);
+ iOut += nNL;
+ }else if( zText[i]=='\r' ){
+ memcpy(&zOut[iOut], zCR, nCR);
+ iOut += nCR;
+ }else{
+ zOut[iOut] = zText[i];
+ iOut++;
+ }
+ }
+
+ if( zNL ){
+ memcpy(&zOut[iOut], ",'", 2); iOut += 2;
+ memcpy(&zOut[iOut], zNL, nNL); iOut += nNL;
+ memcpy(&zOut[iOut], "', char(10))", 12); iOut += 12;
+ }
+ if( zCR ){
+ memcpy(&zOut[iOut], ",'", 2); iOut += 2;
+ memcpy(&zOut[iOut], zCR, nCR); iOut += nCR;
+ memcpy(&zOut[iOut], "', char(13))", 12); iOut += 12;
+ }
+
+ sqlite3_result_text(context, zOut, iOut, SQLITE_TRANSIENT);
+ sqlite3_free(zOut);
+ return;
+ }
+ }
+
+ sqlite3_result_value(context, argv[0]);
+}
+
/* Flags for open_db().
**
** The default behavior of open_db() is to exit(1) if the database fails to
@@ -11682,6 +12679,9 @@ static void open_db(ShellState *p, int openFlags){
sqlite3_fileio_init(p->db, 0, 0);
sqlite3_shathree_init(p->db, 0, 0);
sqlite3_completion_init(p->db, 0, 0);
+#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
+ sqlite3_dbdata_init(p->db, 0, 0);
+#endif
#ifdef SQLITE_HAVE_ZLIB
sqlite3_zipfile_init(p->db, 0, 0);
sqlite3_sqlar_init(p->db, 0, 0);
@@ -11692,6 +12692,10 @@ static void open_db(ShellState *p, int openFlags){
shellModuleSchema, 0, 0);
sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p,
shellPutsFunc, 0, 0);
+ sqlite3_create_function(p->db, "shell_escape_crnl", 1, SQLITE_UTF8, 0,
+ shellEscapeCrnl, 0, 0);
+ sqlite3_create_function(p->db, "shell_int32", 2, SQLITE_UTF8, 0,
+ shellInt32, 0, 0);
#ifndef SQLITE_NOHAVE_SYSTEM
sqlite3_create_function(p->db, "edit", 1, SQLITE_UTF8, 0,
editFunc, 0, 0);
@@ -11715,7 +12719,6 @@ static void open_db(ShellState *p, int openFlags){
}else{
aData = readHexDb(p, &nData);
if( aData==0 ){
- utf8_printf(stderr, "Error in hexdb input\n");
return;
}
}
@@ -12395,6 +13398,7 @@ static void output_reset(ShellState *p){
sqlite3_free(zCmd);
outputModePop(p);
p->doXdgOpen = 0;
+ sqlite3_sleep(100);
}
#endif /* !defined(SQLITE_NOHAVE_SYSTEM) */
}
@@ -12946,10 +13950,7 @@ static int lintDotCommand(
return SQLITE_ERROR;
}
-#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
-/*********************************************************************************
-** The ".archive" or ".ar" command.
-*/
+#if !defined SQLITE_OMIT_VIRTUALTABLE
static void shellPrepare(
sqlite3 *db,
int *pRc,
@@ -12968,7 +13969,14 @@ static void shellPrepare(
}
}
-static void shellPreparePrintf(
+/*
+** Create a prepared statement using printf-style arguments for the SQL.
+**
+** This routine is could be marked "static". But it is not always used,
+** depending on compile-time options. By omitting the "static", we avoid
+** nuisance compiler warnings about "defined but not used".
+*/
+void shellPreparePrintf(
sqlite3 *db,
int *pRc,
sqlite3_stmt **ppStmt,
@@ -12991,7 +13999,13 @@ static void shellPreparePrintf(
}
}
-static void shellFinalize(
+/* Finalize the prepared statement created using shellPreparePrintf().
+**
+** This routine is could be marked "static". But it is not always used,
+** depending on compile-time options. By omitting the "static", we avoid
+** nuisance compiler warnings about "defined but not used".
+*/
+void shellFinalize(
int *pRc,
sqlite3_stmt *pStmt
){
@@ -13007,7 +14021,13 @@ static void shellFinalize(
}
}
-static void shellReset(
+/* Reset the prepared statement created using shellPreparePrintf().
+**
+** This routine is could be marked "static". But it is not always used,
+** depending on compile-time options. By omitting the "static", we avoid
+** nuisance compiler warnings about "defined but not used".
+*/
+void shellReset(
int *pRc,
sqlite3_stmt *pStmt
){
@@ -13020,6 +14040,12 @@ static void shellReset(
*pRc = rc;
}
}
+#endif /* !defined SQLITE_OMIT_VIRTUALTABLE */
+
+#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
+/*********************************************************************************
+** The ".archive" or ".ar" command.
+*/
/*
** Structure representing a single ".ar" command.
*/
@@ -13709,6 +14735,635 @@ 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);
+ }
+ *pRc = rc;
+ }
+}
+
+/*
+** Like shellExec(), except that zFmt is a printf() style format string.
+*/
+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 ){
+ 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_master 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);
+ }
+ }
+
+ pTab->zQuoted = shellMPrintf(&rc, "%Q", zName);
+ pTab->azlCol = (char**)shellMalloc(&rc, sizeof(char*) * (nSqlCol+1));
+ pTab->nCol = nSqlCol;
+
+ if( bIntkey ){
+ pTab->azlCol[0] = shellMPrintf(&rc, "%Q", zPk);
+ }else{
+ pTab->azlCol[0] = shellMPrintf(&rc, "");
+ }
+ i = 1;
+ shellPreparePrintf(dbtmp, &rc, &pStmt,
+ "SELECT %Q || group_concat(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_master 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);
+ 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, "%Q", 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;
+}
+
+/*
+** This function is called to recover data from the database. A script
+** to construct a new database containing all recovered data is output
+** on stream pState->out.
+*/
+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;
+
+ int bFreelist = 1; /* 0 if --freelist-corrupt is specified */
+ for(i=1; i<nArg; i++){
+ char *z = azArg[i];
+ int n;
+ if( z[0]=='-' && z[1]=='-' ) z++;
+ n = strlen30(z);
+ if( n<=17 && memcmp("-freelist-corrupt", z, n)==0 ){
+ bFreelist = 0;
+ }else
+ if( n<=12 && memcmp("-recovery-db", z, n)==0 && i<(nArg-1) ){
+ i++;
+ zRecoveryDb = azArg[i];
+ }else
+ if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){
+ i++;
+ zLostAndFound = azArg[i];
+ }
+ else{
+ raw_printf(stderr, "unexpected option: %s\n", azArg[i]);
+ raw_printf(stderr, "options are:\n");
+ raw_printf(stderr, " --freelist-corrupt\n");
+ raw_printf(stderr, " --recovery-db DATABASE\n");
+ raw_printf(stderr, " --lost-and-found TABLE-NAME\n");
+ return 1;
+ }
+ }
+
+ shellExecPrintf(pState->db, &rc,
+ /* Attach an in-memory database named 'recovery'. Create an indexed
+ ** cache of the sqlite_dbptr virtual table. */
+ "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
+ );
+
+ 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;"
+ );
+ }
+
+ 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 > 0 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_master 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;
+ 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(value)), ', ')"
+ "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;
+
+ 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);
+ sqlite3_bind_int(pCells, 2, pTab->iPk);
+
+ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPages) ){
+ int iPgno = sqlite3_column_int(pPages, 0);
+ sqlite3_bind_int(pCells, 1, iPgno);
+ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pCells) ){
+ int nField = sqlite3_column_int(pCells, 0);
+ const char *zVal = (const char*)sqlite3_column_text(pCells, 1);
+
+ nField = nField+1;
+ if( pTab==pOrphan ){
+ raw_printf(pState->out,
+ "INSERT INTO %s VALUES(%d, %d, %d, %s%s%s);\n",
+ pTab->zQuoted, iRoot, iPgno, nField,
+ bIntkey ? "" : "NULL, ", zVal, pTab->azlCol[nField]
+ );
+ }else{
+ raw_printf(pState->out, "INSERT INTO %s(%s) VALUES( %s );\n",
+ pTab->zQuoted, pTab->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_master 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);
+ return rc;
+}
+#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
+
/*
** If an input line begins with "." then invoke this routine to
@@ -13965,15 +15620,19 @@ static int do_meta_command(char *zLine, ShellState *p){
const char *zName;
int op;
} aDbConfig[] = {
- { "enable_fkey", SQLITE_DBCONFIG_ENABLE_FKEY },
- { "enable_trigger", SQLITE_DBCONFIG_ENABLE_TRIGGER },
- { "fts3_tokenizer", SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER },
- { "load_extension", SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION },
- { "no_ckpt_on_close", SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE },
- { "enable_qpsg", SQLITE_DBCONFIG_ENABLE_QPSG },
- { "trigger_eqp", SQLITE_DBCONFIG_TRIGGER_EQP },
- { "reset_database", SQLITE_DBCONFIG_RESET_DATABASE },
- { "defensive", SQLITE_DBCONFIG_DEFENSIVE },
+ { "enable_fkey", SQLITE_DBCONFIG_ENABLE_FKEY },
+ { "enable_trigger", SQLITE_DBCONFIG_ENABLE_TRIGGER },
+ { "fts3_tokenizer", SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER },
+ { "load_extension", SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION },
+ { "no_ckpt_on_close", SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE },
+ { "enable_qpsg", SQLITE_DBCONFIG_ENABLE_QPSG },
+ { "trigger_eqp", SQLITE_DBCONFIG_TRIGGER_EQP },
+ { "reset_database", SQLITE_DBCONFIG_RESET_DATABASE },
+ { "defensive", SQLITE_DBCONFIG_DEFENSIVE },
+ { "writable_schema", SQLITE_DBCONFIG_WRITABLE_SCHEMA },
+ { "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE },
+ { "dqs_dml", SQLITE_DBCONFIG_DQS_DML },
+ { "dqs_ddl", SQLITE_DBCONFIG_DQS_DDL },
};
int ii, v;
open_db(p, 0);
@@ -13996,6 +15655,13 @@ static int do_meta_command(char *zLine, ShellState *p){
rc = shell_dbinfo_command(p, nArg, azArg);
}else
+#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
+ if( c=='r' && strncmp(azArg[0], "recover", n)==0 ){
+ open_db(p, 0);
+ rc = recoverDatabaseCmd(p, nArg, azArg);
+ }else
+#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
+
if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){
const char *zLike = 0;
int i;
@@ -14033,7 +15699,9 @@ static int do_meta_command(char *zLine, ShellState *p){
zLike = azArg[i];
}
}
+
open_db(p, 0);
+
/* When playing back a "dump", the content might appear in an order
** which causes immediate foreign key constraints to be violated.
** So disable foreign-key constraint enforcement to prevent problems. */
@@ -14081,7 +15749,7 @@ static int do_meta_command(char *zLine, ShellState *p){
}
sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0);
sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0);
- raw_printf(p->out, p->nErr ? "ROLLBACK; -- due to errors\n" : "COMMIT;\n");
+ raw_printf(p->out, p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n");
p->showHeader = savedShowHeader;
p->shellFlgs = savedShellFlags;
}else
@@ -14162,6 +15830,127 @@ static int do_meta_command(char *zLine, ShellState *p){
}else
#endif
+ if( c=='f' && strncmp(azArg[0], "filectrl", n)==0 ){
+ static const struct {
+ const char *zCtrlName; /* Name of a test-control option */
+ int ctrlCode; /* Integer code for that option */
+ const char *zUsage; /* Usage notes */
+ } aCtrl[] = {
+ { "size_limit", SQLITE_FCNTL_SIZE_LIMIT, "[LIMIT]" },
+ { "chunk_size", SQLITE_FCNTL_CHUNK_SIZE, "SIZE" },
+ /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY, "COUNT DELAY" },*/
+ { "persist_wal", SQLITE_FCNTL_PERSIST_WAL, "[BOOLEAN]" },
+ { "psow", SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]" },
+ /* { "pragma", SQLITE_FCNTL_PRAGMA, "NAME ARG" },*/
+ { "tempfilename", SQLITE_FCNTL_TEMPFILENAME, "" },
+ { "has_moved", SQLITE_FCNTL_HAS_MOVED, "" },
+ { "lock_timeout", SQLITE_FCNTL_LOCK_TIMEOUT, "MILLISEC" },
+ };
+ int filectrl = -1;
+ int iCtrl = -1;
+ sqlite3_int64 iRes = 0; /* Integer result to display if rc2==1 */
+ int isOk = 0; /* 0: usage 1: %lld 2: no-result */
+ int n2, i;
+ const char *zCmd = 0;
+
+ open_db(p, 0);
+ zCmd = nArg>=2 ? azArg[1] : "help";
+
+ /* The argument can optionally begin with "-" or "--" */
+ if( zCmd[0]=='-' && zCmd[1] ){
+ zCmd++;
+ if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
+ }
+
+ /* --help lists all file-controls */
+ if( strcmp(zCmd,"help")==0 ){
+ utf8_printf(p->out, "Available file-controls:\n");
+ for(i=0; i<ArraySize(aCtrl); i++){
+ utf8_printf(p->out, " .filectrl %s %s\n",
+ aCtrl[i].zCtrlName, aCtrl[i].zUsage);
+ }
+ rc = 1;
+ goto meta_command_exit;
+ }
+
+ /* convert filectrl text option to value. allow any unique prefix
+ ** of the option name, or a numerical value. */
+ n2 = strlen30(zCmd);
+ for(i=0; i<ArraySize(aCtrl); i++){
+ if( strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
+ if( filectrl<0 ){
+ filectrl = aCtrl[i].ctrlCode;
+ iCtrl = i;
+ }else{
+ utf8_printf(stderr, "Error: ambiguous file-control: \"%s\"\n"
+ "Use \".filectrl --help\" for help\n", zCmd);
+ rc = 1;
+ goto meta_command_exit;
+ }
+ }
+ }
+ if( filectrl<0 ){
+ utf8_printf(stderr,"Error: unknown file-control: %s\n"
+ "Use \".filectrl --help\" for help\n", zCmd);
+ }else{
+ switch(filectrl){
+ case SQLITE_FCNTL_SIZE_LIMIT: {
+ if( nArg!=2 && nArg!=3 ) break;
+ iRes = nArg==3 ? integerValue(azArg[2]) : -1;
+ sqlite3_file_control(p->db, 0, SQLITE_FCNTL_SIZE_LIMIT, &iRes);
+ isOk = 1;
+ break;
+ }
+ case SQLITE_FCNTL_LOCK_TIMEOUT:
+ case SQLITE_FCNTL_CHUNK_SIZE: {
+ int x;
+ if( nArg!=3 ) break;
+ x = (int)integerValue(azArg[2]);
+ sqlite3_file_control(p->db, 0, filectrl, &x);
+ isOk = 2;
+ break;
+ }
+ case SQLITE_FCNTL_PERSIST_WAL:
+ case SQLITE_FCNTL_POWERSAFE_OVERWRITE: {
+ int x;
+ if( nArg!=2 && nArg!=3 ) break;
+ x = nArg==3 ? booleanValue(azArg[2]) : -1;
+ sqlite3_file_control(p->db, 0, filectrl, &x);
+ iRes = x;
+ isOk = 1;
+ break;
+ }
+ case SQLITE_FCNTL_HAS_MOVED: {
+ int x;
+ if( nArg!=2 ) break;
+ sqlite3_file_control(p->db, 0, filectrl, &x);
+ iRes = x;
+ isOk = 1;
+ break;
+ }
+ case SQLITE_FCNTL_TEMPFILENAME: {
+ char *z = 0;
+ if( nArg!=2 ) break;
+ sqlite3_file_control(p->db, 0, filectrl, &z);
+ if( z ){
+ utf8_printf(p->out, "%s\n", z);
+ sqlite3_free(z);
+ }
+ isOk = 2;
+ break;
+ }
+ }
+ }
+ if( isOk==0 && iCtrl>=0 ){
+ utf8_printf(p->out, "Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
+ rc = 1;
+ }else if( isOk==1 ){
+ char zBuf[100];
+ sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes);
+ raw_printf(p->out, "%s\n", zBuf);
+ }
+ }else
+
if( c=='f' && strncmp(azArg[0], "fullschema", n)==0 ){
ShellState data;
char *zErrMsg = 0;