| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #include "sqliteInt.h" |
| | #if (defined(SQLITE_ENABLE_DBPAGE_VTAB) || defined(SQLITE_TEST)) \ |
| | && !defined(SQLITE_OMIT_VIRTUALTABLE) |
| |
|
| | typedef struct DbpageTable DbpageTable; |
| | typedef struct DbpageCursor DbpageCursor; |
| |
|
| | struct DbpageCursor { |
| | sqlite3_vtab_cursor base; |
| | Pgno pgno; |
| | Pgno mxPgno; |
| | Pager *pPager; |
| | DbPage *pPage1; |
| | int iDb; |
| | int szPage; |
| | }; |
| |
|
| | struct DbpageTable { |
| | sqlite3_vtab base; |
| | sqlite3 *db; |
| | int iDbTrunc; |
| | Pgno pgnoTrunc; |
| | }; |
| |
|
| | |
| | #define DBPAGE_COLUMN_PGNO 0 |
| | #define DBPAGE_COLUMN_DATA 1 |
| | #define DBPAGE_COLUMN_SCHEMA 2 |
| |
|
| |
|
| | |
| | |
| | |
| | static int dbpageConnect( |
| | sqlite3 *db, |
| | void *pAux, |
| | int argc, const char *const*argv, |
| | sqlite3_vtab **ppVtab, |
| | char **pzErr |
| | ){ |
| | DbpageTable *pTab = 0; |
| | int rc = SQLITE_OK; |
| | (void)pAux; |
| | (void)argc; |
| | (void)argv; |
| | (void)pzErr; |
| |
|
| | sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY); |
| | sqlite3_vtab_config(db, SQLITE_VTAB_USES_ALL_SCHEMAS); |
| | 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 ){ |
| | memset(pTab, 0, sizeof(DbpageTable)); |
| | pTab->db = db; |
| | } |
| |
|
| | *ppVtab = (sqlite3_vtab*)pTab; |
| | return rc; |
| | } |
| |
|
| | |
| | |
| | |
| | static int dbpageDisconnect(sqlite3_vtab *pVtab){ |
| | sqlite3_free(pVtab); |
| | return SQLITE_OK; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | static int dbpageBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ |
| | int i; |
| | int iPlan = 0; |
| | (void)tab; |
| |
|
| | |
| | |
| | |
| | |
| | for(i=0; i<pIdxInfo->nConstraint; 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 ){ |
| | |
| | return SQLITE_CONSTRAINT; |
| | } |
| | iPlan = 2; |
| | pIdxInfo->aConstraintUsage[i].argvIndex = 1; |
| | pIdxInfo->aConstraintUsage[i].omit = 1; |
| | break; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | pIdxInfo->estimatedCost = 1.0e6; |
| |
|
| | |
| | for(i=0; i<pIdxInfo->nConstraint; 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->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; |
| | } |
| | return SQLITE_OK; |
| | } |
| |
|
| | |
| | |
| | |
| | static int dbpageOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ |
| | DbpageCursor *pCsr; |
| |
|
| | pCsr = (DbpageCursor *)sqlite3_malloc64(sizeof(DbpageCursor)); |
| | if( pCsr==0 ){ |
| | return SQLITE_NOMEM_BKPT; |
| | }else{ |
| | memset(pCsr, 0, sizeof(DbpageCursor)); |
| | pCsr->base.pVtab = pVTab; |
| | pCsr->pgno = 0; |
| | } |
| |
|
| | *ppCursor = (sqlite3_vtab_cursor *)pCsr; |
| | return SQLITE_OK; |
| | } |
| |
|
| | |
| | |
| | |
| | static int dbpageClose(sqlite3_vtab_cursor *pCursor){ |
| | DbpageCursor *pCsr = (DbpageCursor *)pCursor; |
| | if( pCsr->pPage1 ) sqlite3PagerUnrefPageOne(pCsr->pPage1); |
| | sqlite3_free(pCsr); |
| | return SQLITE_OK; |
| | } |
| |
|
| | |
| | |
| | |
| | static int dbpageNext(sqlite3_vtab_cursor *pCursor){ |
| | int rc = SQLITE_OK; |
| | DbpageCursor *pCsr = (DbpageCursor *)pCursor; |
| | pCsr->pgno++; |
| | return rc; |
| | } |
| |
|
| | static int dbpageEof(sqlite3_vtab_cursor *pCursor){ |
| | DbpageCursor *pCsr = (DbpageCursor *)pCursor; |
| | return pCsr->pgno > pCsr->mxPgno; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | 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; |
| | sqlite3 *db = pTab->db; |
| | Btree *pBt; |
| |
|
| | UNUSED_PARAMETER(idxStr); |
| | UNUSED_PARAMETER(argc); |
| |
|
| | |
| | pCsr->pgno = 1; |
| | pCsr->mxPgno = 0; |
| |
|
| | 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( NEVER(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{ |
| | assert( pCsr->pgno==1 ); |
| | } |
| | if( pCsr->pPage1 ) sqlite3PagerUnrefPageOne(pCsr->pPage1); |
| | 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; |
| | int rc = SQLITE_OK; |
| | switch( i ){ |
| | case 0: { |
| | sqlite3_result_int64(ctx, (sqlite3_int64)pCsr->pgno); |
| | break; |
| | } |
| | case 1: { |
| | DbPage *pDbPage = 0; |
| | if( pCsr->pgno==(Pgno)((PENDING_BYTE/pCsr->szPage)+1) ){ |
| | |
| | |
| | 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); |
| | } |
| | break; |
| | } |
| | default: { |
| | sqlite3 *db = sqlite3_context_db_handle(ctx); |
| | sqlite3_result_text(ctx, db->aDb[pCsr->iDb].zDbSName, -1, SQLITE_STATIC); |
| | break; |
| | } |
| | } |
| | return rc; |
| | } |
| |
|
| | static int dbpageRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ |
| | DbpageCursor *pCsr = (DbpageCursor *)pCursor; |
| | *pRowid = pCsr->pgno; |
| | return SQLITE_OK; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | static int dbpageBeginTrans(DbpageTable *pTab){ |
| | sqlite3 *db = pTab->db; |
| | int rc = SQLITE_OK; |
| | int i; |
| | for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ |
| | Btree *pBt = db->aDb[i].pBt; |
| | if( pBt ) rc = sqlite3BtreeBeginTrans(pBt, 1, 0); |
| | } |
| | return rc; |
| | } |
| |
|
| | static int dbpageUpdate( |
| | sqlite3_vtab *pVtab, |
| | int argc, |
| | sqlite3_value **argv, |
| | sqlite_int64 *pRowid |
| | ){ |
| | DbpageTable *pTab = (DbpageTable *)pVtab; |
| | Pgno pgno; |
| | DbPage *pDbPage = 0; |
| | int rc = SQLITE_OK; |
| | char *zErr = 0; |
| | int iDb; |
| | Btree *pBt; |
| | Pager *pPager; |
| | int szPage; |
| | int isInsert; |
| |
|
| | (void)pRowid; |
| | if( pTab->db->flags & SQLITE_Defensive ){ |
| | zErr = "read-only"; |
| | goto update_fail; |
| | } |
| | if( argc==1 ){ |
| | zErr = "cannot delete"; |
| | goto update_fail; |
| | } |
| | if( sqlite3_value_type(argv[0])==SQLITE_NULL ){ |
| | pgno = (Pgno)sqlite3_value_int64(argv[2]); |
| | isInsert = 1; |
| | }else{ |
| | pgno = (Pgno)sqlite3_value_int64(argv[0]); |
| | if( (Pgno)sqlite3_value_int(argv[1])!=pgno ){ |
| | zErr = "cannot insert"; |
| | goto update_fail; |
| | } |
| | isInsert = 0; |
| | } |
| | if( sqlite3_value_type(argv[4])==SQLITE_NULL ){ |
| | iDb = 0; |
| | }else{ |
| | const char *zSchema = (const char*)sqlite3_value_text(argv[4]); |
| | iDb = sqlite3FindDbName(pTab->db, zSchema); |
| | if( iDb<0 ){ |
| | zErr = "no such schema"; |
| | goto update_fail; |
| | } |
| | } |
| | pBt = pTab->db->aDb[iDb].pBt; |
| | if( pgno<1 || NEVER(pBt==0) ){ |
| | zErr = "bad page number"; |
| | goto update_fail; |
| | } |
| | szPage = sqlite3BtreeGetPageSize(pBt); |
| | if( sqlite3_value_type(argv[3])!=SQLITE_BLOB |
| | || sqlite3_value_bytes(argv[3])!=szPage |
| | ){ |
| | if( sqlite3_value_type(argv[3])==SQLITE_NULL && isInsert && pgno>1 ){ |
| | |
| | |
| | pTab->iDbTrunc = iDb; |
| | pTab->pgnoTrunc = pgno-1; |
| | pgno = 1; |
| | }else{ |
| | zErr = "bad page value"; |
| | goto update_fail; |
| | } |
| | } |
| |
|
| | if( dbpageBeginTrans(pTab)!=SQLITE_OK ){ |
| | zErr = "failed to open transaction"; |
| | goto update_fail; |
| | } |
| |
|
| | pPager = sqlite3BtreePager(pBt); |
| | rc = sqlite3PagerGet(pPager, pgno, (DbPage**)&pDbPage, 0); |
| | if( rc==SQLITE_OK ){ |
| | const void *pData = sqlite3_value_blob(argv[3]); |
| | if( (rc = sqlite3PagerWrite(pDbPage))==SQLITE_OK && pData ){ |
| | unsigned char *aPage = sqlite3PagerGetData(pDbPage); |
| | memcpy(aPage, pData, szPage); |
| | pTab->pgnoTrunc = 0; |
| | } |
| | } |
| | if( rc!=SQLITE_OK ){ |
| | pTab->pgnoTrunc = 0; |
| | } |
| | sqlite3PagerUnref(pDbPage); |
| | return rc; |
| |
|
| | update_fail: |
| | pTab->pgnoTrunc = 0; |
| | sqlite3_free(pVtab->zErrMsg); |
| | pVtab->zErrMsg = sqlite3_mprintf("%s", zErr); |
| | return SQLITE_ERROR; |
| | } |
| |
|
| | static int dbpageBegin(sqlite3_vtab *pVtab){ |
| | DbpageTable *pTab = (DbpageTable *)pVtab; |
| | pTab->pgnoTrunc = 0; |
| | return SQLITE_OK; |
| | } |
| |
|
| | |
| | |
| | static int dbpageSync(sqlite3_vtab *pVtab){ |
| | DbpageTable *pTab = (DbpageTable *)pVtab; |
| | if( pTab->pgnoTrunc>0 ){ |
| | Btree *pBt = pTab->db->aDb[pTab->iDbTrunc].pBt; |
| | Pager *pPager = sqlite3BtreePager(pBt); |
| | sqlite3BtreeEnter(pBt); |
| | if( pTab->pgnoTrunc<sqlite3BtreeLastPage(pBt) ){ |
| | sqlite3PagerTruncateImage(pPager, pTab->pgnoTrunc); |
| | } |
| | sqlite3BtreeLeave(pBt); |
| | } |
| | pTab->pgnoTrunc = 0; |
| | return SQLITE_OK; |
| | } |
| |
|
| | |
| | |
| | static int dbpageRollbackTo(sqlite3_vtab *pVtab, int notUsed1){ |
| | DbpageTable *pTab = (DbpageTable *)pVtab; |
| | pTab->pgnoTrunc = 0; |
| | (void)notUsed1; |
| | return SQLITE_OK; |
| | } |
| |
|
| | |
| | |
| | |
| | int sqlite3DbpageRegister(sqlite3 *db){ |
| | static sqlite3_module dbpage_module = { |
| | 2, |
| | dbpageConnect, |
| | dbpageConnect, |
| | dbpageBestIndex, |
| | dbpageDisconnect, |
| | dbpageDisconnect, |
| | dbpageOpen, |
| | dbpageClose, |
| | dbpageFilter, |
| | dbpageNext, |
| | dbpageEof, |
| | dbpageColumn, |
| | dbpageRowid, |
| | dbpageUpdate, |
| | dbpageBegin, |
| | dbpageSync, |
| | 0, |
| | 0, |
| | 0, |
| | 0, |
| | 0, |
| | 0, |
| | dbpageRollbackTo, |
| | 0, |
| | 0 |
| | }; |
| | return sqlite3_create_module(db, "sqlite_dbpage", &dbpage_module, 0); |
| | } |
| | #elif defined(SQLITE_ENABLE_DBPAGE_VTAB) |
| | int sqlite3DbpageRegister(sqlite3 *db){ return SQLITE_OK; } |
| | #endif |
| |
|