Index: src/dbpage.c ================================================================== --- src/dbpage.c +++ src/dbpage.c @@ -40,20 +40,27 @@ struct DbpageCursor { sqlite3_vtab_cursor base; /* Base class. Must be first */ int pgno; /* Current page number */ int mxPgno; /* Last page to visit on this scan */ + Pager *pPager; /* Pager being read/written */ + DbPage *pPage1; /* Page 1 of the database */ + int iDb; /* Index of database to analyze */ + int szPage; /* Size of each page in bytes */ }; struct DbpageTable { sqlite3_vtab base; /* Base class. Must be first */ sqlite3 *db; /* The database */ - Pager *pPager; /* Pager being read/written */ - int iDb; /* Index of database to analyze */ - int szPage; /* Size of each page in bytes */ - int nPage; /* Number of pages in the file */ }; + +/* Columns */ +#define DBPAGE_COLUMN_PGNO 0 +#define DBPAGE_COLUMN_DATA 1 +#define DBPAGE_COLUMN_SCHEMA 2 + + /* ** Connect to or create a dbpagevfs virtual table. */ static int dbpageConnect( @@ -63,37 +70,22 @@ sqlite3_vtab **ppVtab, char **pzErr ){ DbpageTable *pTab = 0; int rc = SQLITE_OK; - int iDb; - - if( argc>=4 ){ - Token nm; - sqlite3TokenInit(&nm, (char*)argv[3]); - iDb = sqlite3FindDb(db, &nm); - if( iDb<0 ){ - *pzErr = sqlite3_mprintf("no such schema: %s", argv[3]); - return SQLITE_ERROR; - } - }else{ - iDb = 0; - } + rc = sqlite3_declare_vtab(db, "CREATE TABLE x(pgno INTEGER PRIMARY KEY, data BLOB, schema HIDDEN)"); if( rc==SQLITE_OK ){ pTab = (DbpageTable *)sqlite3_malloc64(sizeof(DbpageTable)); if( pTab==0 ) rc = SQLITE_NOMEM_BKPT; } assert( rc==SQLITE_OK || pTab==0 ); if( rc==SQLITE_OK ){ - Btree *pBt = db->aDb[iDb].pBt; memset(pTab, 0, sizeof(DbpageTable)); pTab->db = db; - pTab->iDb = iDb; - pTab->pPager = pBt ? sqlite3BtreePager(pBt) : 0; } *ppVtab = (sqlite3_vtab*)pTab; return rc; } @@ -107,28 +99,59 @@ } /* ** idxNum: ** -** 0 full table scan -** 1 pgno=?1 +** 0 schema=main, full table scan +** 1 schema=main, pgno=?1 +** 2 schema=?1, full table scan +** 3 schema=?1, pgno=?2 */ static int dbpageBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ int i; - pIdxInfo->estimatedCost = 1.0e6; /* Initial cost estimate */ + int iPlan = 0; + + /* If there is a schema= constraint, it must be honored. Report a + ** ridiculously large estimated cost if the schema= constraint is + ** unavailable + */ + for(i=0; inConstraint; i++){ + struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[i]; + if( p->iColumn!=DBPAGE_COLUMN_SCHEMA ) continue; + if( p->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; + if( !p->usable ){ + /* No solution. Use the default SQLITE_BIG_DBL cost */ + pIdxInfo->estimatedRows = 0x7fffffff; + return SQLITE_OK; + } + iPlan = 2; + pIdxInfo->aConstraintUsage[i].argvIndex = 1; + pIdxInfo->aConstraintUsage[i].omit = 1; + break; + } + + /* If we reach this point, it means that either there is no schema= + ** constraint (in which case we use the "main" schema) or else the + ** schema constraint was accepted. Lower the estimated cost accordingly + */ + pIdxInfo->estimatedCost = 1.0e6; + + /* Check for constraints against pgno */ for(i=0; inConstraint; i++){ struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[i]; if( p->usable && p->iColumn<=0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ ){ pIdxInfo->estimatedRows = 1; pIdxInfo->idxFlags = SQLITE_INDEX_SCAN_UNIQUE; pIdxInfo->estimatedCost = 1.0; - pIdxInfo->idxNum = 1; - pIdxInfo->aConstraintUsage[i].argvIndex = 1; + pIdxInfo->aConstraintUsage[i].argvIndex = iPlan ? 2 : 1; pIdxInfo->aConstraintUsage[i].omit = 1; + iPlan |= 1; break; } } + pIdxInfo->idxNum = iPlan; + if( pIdxInfo->nOrderBy>=1 && pIdxInfo->aOrderBy[0].iColumn<=0 && pIdxInfo->aOrderBy[0].desc==0 ){ pIdxInfo->orderByConsumed = 1; @@ -158,10 +181,11 @@ /* ** Close a dbpagevfs cursor. */ static int dbpageClose(sqlite3_vtab_cursor *pCursor){ DbpageCursor *pCsr = (DbpageCursor *)pCursor; + if( pCsr->pPage1 ) sqlite3PagerUnrefPageOne(pCsr->pPage1); sqlite3_free(pCsr); return SQLITE_OK; } /* @@ -177,63 +201,90 @@ static int dbpageEof(sqlite3_vtab_cursor *pCursor){ DbpageCursor *pCsr = (DbpageCursor *)pCursor; return pCsr->pgno > pCsr->mxPgno; } +/* +** idxNum: +** +** 0 schema=main, full table scan +** 1 schema=main, pgno=?1 +** 2 schema=?1, full table scan +** 3 schema=?1, pgno=?2 +** +** idxStr is not used +*/ static int dbpageFilter( sqlite3_vtab_cursor *pCursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv ){ DbpageCursor *pCsr = (DbpageCursor *)pCursor; DbpageTable *pTab = (DbpageTable *)pCursor->pVtab; int rc = SQLITE_OK; - Btree *pBt = pTab->db->aDb[pTab->iDb].pBt; + sqlite3 *db = pTab->db; + Btree *pBt; + + /* Default setting is no rows of result */ + pCsr->pgno = 1; + pCsr->mxPgno = 0; - pTab->szPage = sqlite3BtreeGetPageSize(pBt); - pTab->nPage = sqlite3BtreeLastPage(pBt); - if( idxNum==1 ){ - pCsr->pgno = sqlite3_value_int(argv[0]); - if( pCsr->pgno<1 || pCsr->pgno>pTab->nPage ){ + if( idxNum & 2 ){ + const char *zSchema; + assert( argc>=1 ); + zSchema = (const char*)sqlite3_value_text(argv[0]); + pCsr->iDb = sqlite3FindDbName(db, zSchema); + if( pCsr->iDb<0 ) return SQLITE_OK; + }else{ + pCsr->iDb = 0; + } + pBt = db->aDb[pCsr->iDb].pBt; + if( pBt==0 ) return SQLITE_OK; + pCsr->pPager = sqlite3BtreePager(pBt); + pCsr->szPage = sqlite3BtreeGetPageSize(pBt); + pCsr->mxPgno = sqlite3BtreeLastPage(pBt); + if( idxNum & 1 ){ + assert( argc>(idxNum>>1) ); + pCsr->pgno = sqlite3_value_int(argv[idxNum>>1]); + if( pCsr->pgno<1 || pCsr->pgno>pCsr->mxPgno ){ pCsr->pgno = 1; pCsr->mxPgno = 0; }else{ pCsr->mxPgno = pCsr->pgno; } }else{ - pCsr->pgno = 1; - pCsr->mxPgno = pTab->nPage; + assert( pCsr->pgno==1 ); } + rc = sqlite3PagerGet(pCsr->pPager, 1, &pCsr->pPage1, 0); return rc; } static int dbpageColumn( sqlite3_vtab_cursor *pCursor, sqlite3_context *ctx, int i ){ DbpageCursor *pCsr = (DbpageCursor *)pCursor; - DbpageTable *pTab = (DbpageTable *)pCursor->pVtab; int rc = SQLITE_OK; switch( i ){ case 0: { /* pgno */ sqlite3_result_int(ctx, pCsr->pgno); break; } case 1: { /* data */ DbPage *pDbPage = 0; - rc = sqlite3PagerGet(pTab->pPager, pCsr->pgno, (DbPage**)&pDbPage, 0); + rc = sqlite3PagerGet(pCsr->pPager, pCsr->pgno, (DbPage**)&pDbPage, 0); if( rc==SQLITE_OK ){ - sqlite3_result_blob(ctx, sqlite3PagerGetData(pDbPage), pTab->szPage, + sqlite3_result_blob(ctx, sqlite3PagerGetData(pDbPage), pCsr->szPage, SQLITE_TRANSIENT); } sqlite3PagerUnref(pDbPage); break; } default: { /* schema */ sqlite3 *db = sqlite3_context_db_handle(ctx); - sqlite3_result_text(ctx, db->aDb[pTab->iDb].zDbSName, -1, SQLITE_STATIC); + sqlite3_result_text(ctx, db->aDb[pCsr->iDb].zDbSName, -1, SQLITE_STATIC); break; } } return SQLITE_OK; } @@ -253,37 +304,51 @@ DbpageTable *pTab = (DbpageTable *)pVtab; int pgno; DbPage *pDbPage = 0; int rc = SQLITE_OK; char *zErr = 0; + const char *zSchema; + int iDb; + Btree *pBt; + Pager *pPager; + int szPage; if( argc==1 ){ zErr = "cannot delete"; goto update_fail; } pgno = sqlite3_value_int(argv[0]); - if( pgno<1 || pgno>pTab->nPage ){ - zErr = "bad page number"; - goto update_fail; - } if( sqlite3_value_int(argv[1])!=pgno ){ zErr = "cannot insert"; goto update_fail; } + zSchema = (const char*)sqlite3_value_text(argv[4]); + iDb = zSchema ? sqlite3FindDbName(pTab->db, zSchema) : -1; + if( iDb<0 ){ + zErr = "no such schema"; + goto update_fail; + } + pBt = pTab->db->aDb[iDb].pBt; + if( pgno<1 || pBt==0 || pgno>sqlite3BtreeLastPage(pBt) ){ + zErr = "bad page number"; + goto update_fail; + } + szPage = sqlite3BtreeGetPageSize(pBt); if( sqlite3_value_type(argv[3])!=SQLITE_BLOB - || sqlite3_value_bytes(argv[3])!=pTab->szPage + || sqlite3_value_bytes(argv[3])!=szPage ){ zErr = "bad page value"; goto update_fail; } - rc = sqlite3PagerGet(pTab->pPager, pgno, (DbPage**)&pDbPage, 0); + pPager = sqlite3BtreePager(pBt); + rc = sqlite3PagerGet(pPager, pgno, (DbPage**)&pDbPage, 0); if( rc==SQLITE_OK ){ rc = sqlite3PagerWrite(pDbPage); if( rc==SQLITE_OK ){ memcpy(sqlite3PagerGetData(pDbPage), sqlite3_value_blob(argv[3]), - pTab->szPage); + szPage); } } sqlite3PagerUnref(pDbPage); return rc; @@ -290,10 +355,26 @@ update_fail: sqlite3_free(pVtab->zErrMsg); pVtab->zErrMsg = sqlite3_mprintf("%s", zErr); return SQLITE_ERROR; } + +/* Since we do not know in advance which database files will be +** written by the sqlite_dbpage virtual table, start a write transaction +** on them all. +*/ +static int dbpageBegin(sqlite3_vtab *pVtab){ + DbpageTable *pTab = (DbpageTable *)pVtab; + sqlite3 *db = pTab->db; + int i; + for(i=0; inDb; i++){ + Btree *pBt = db->aDb[i].pBt; + if( pBt ) sqlite3BtreeBeginTrans(pBt, 1); + } + return SQLITE_OK; +} + /* ** Invoke this routine to register the "dbpage" virtual table module */ int sqlite3DbpageRegister(sqlite3 *db){ @@ -310,11 +391,11 @@ dbpageNext, /* xNext - advance a cursor */ dbpageEof, /* xEof - check for end of scan */ dbpageColumn, /* xColumn - read data */ dbpageRowid, /* xRowid - read data */ dbpageUpdate, /* xUpdate */ - 0, /* xBegin */ + dbpageBegin, /* xBegin */ 0, /* xSync */ 0, /* xCommit */ 0, /* xRollback */ 0, /* xFindMethod */ 0, /* xRename */ Index: test/dbpage.test ================================================================== --- test/dbpage.test +++ test/dbpage.test @@ -47,10 +47,17 @@ SELECT pgno, quote(substr(data,1,5)) FROM sqlite_dbpage WHERE pgno=5; } {} do_execsql_test 150 { SELECT pgno, quote(substr(data,1,5)) FROM sqlite_dbpage WHERE pgno=0; } {} +do_execsql_test 160 { + ATTACH ':memory:' AS aux1; + PRAGMA aux1.page_size=4096; + CREATE TABLE aux1.t2(a,b,c); + INSERT INTO t2 VALUES(11,12,13); + SELECT pgno, quote(substr(data,1,5)) FROM sqlite_dbpage('aux1'); +} {1 X'53514C6974' 2 X'0D00000001'} do_execsql_test 200 { CREATE TEMP TABLE saved_content(x); INSERT INTO saved_content(x) SELECT data FROM sqlite_dbpage WHERE pgno=4; UPDATE sqlite_dbpage SET data=zeroblob(4096) WHERE pgno=4; @@ -65,10 +72,25 @@ UPDATE sqlite_dbpage SET data=(SELECT x FROM saved_content) WHERE pgno=4; } {} do_catchsql_test 230 { PRAGMA integrity_check; } {0 ok} - - - +do_execsql_test 240 { + DELETE FROM saved_content; + INSERT INTO saved_content(x) + SELECT data FROM sqlite_dbpage WHERE schema='aux1' AND pgno=2; +} {} +do_execsql_test 241 { + UPDATE sqlite_dbpage SET data=zeroblob(4096) WHERE pgno=2 AND schema='aux1'; +} {} +do_catchsql_test 250 { + PRAGMA aux1.integrity_check; +} {1 {database disk image is malformed}} +do_execsql_test 260 { + UPDATE sqlite_dbpage SET data=(SELECT x FROM saved_content) + WHERE pgno=2 AND schema='aux1'; +} {} +do_catchsql_test 270 { + PRAGMA aux1.integrity_check; +} {0 ok} finish_test