#include "sqliteInt.h"
#if (defined(SQLITE_ENABLE_DBSTAT_VTAB) || defined(SQLITE_TEST)) \
&& !defined(SQLITE_OMIT_VIRTUALTABLE)
#define DBSTAT_PAGE_PADDING_BYTES 256
static const char zDbstatSchema[] =
"CREATE TABLE x("
" name TEXT,"
" path TEXT,"
" pageno INTEGER,"
" pagetype TEXT,"
" ncell INTEGER,"
" payload INTEGER,"
" unused INTEGER,"
" mx_payload INTEGER,"
" pgoffset INTEGER,"
" pgsize INTEGER,"
" schema TEXT HIDDEN,"
" aggregate BOOLEAN HIDDEN"
")"
;
typedef struct StatTable StatTable;
typedef struct StatCursor StatCursor;
typedef struct StatPage StatPage;
typedef struct StatCell StatCell;
struct StatCell {
int nLocal;
u32 iChildPg;
int nOvfl;
u32 *aOvfl;
int nLastOvfl;
int iOvfl;
};
struct StatPage {
u32 iPgno;
u8 *aPg;
int iCell;
char *zPath;
u8 flags;
int nCell;
int nUnused;
StatCell *aCell;
u32 iRightChildPg;
int nMxPayload;
};
struct StatCursor {
sqlite3_vtab_cursor base;
sqlite3_stmt *pStmt;
u8 isEof;
u8 isAgg;
int iDb;
StatPage aPage[32];
int iPage;
u32 iPageno;
char *zName;
char *zPath;
char *zPagetype;
int nPage;
int nCell;
int nMxPayload;
i64 nUnused;
i64 nPayload;
i64 iOffset;
i64 szPage;
};
struct StatTable {
sqlite3_vtab base;
sqlite3 *db;
int iDb;
};
#ifndef get2byte
# define get2byte(x) ((x)[0]<<8 | (x)[1])
#endif
static int statConnect(
sqlite3 *db,
void *pAux,
int argc, const char *const*argv,
sqlite3_vtab **ppVtab,
char **pzErr
){
StatTable *pTab = 0;
int rc = SQLITE_OK;
int iDb;
(void)pAux;
if( argc>=4 ){
Token nm;
sqlite3TokenInit(&nm, (char*)argv[3]);
iDb = sqlite3FindDb(db, &nm);
if( iDb<0 ){
*pzErr = sqlite3_mprintf("no such database: %s", argv[3]);
return SQLITE_ERROR;
}
}else{
iDb = 0;
}
sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY);
rc = sqlite3_declare_vtab(db, zDbstatSchema);
if( rc==SQLITE_OK ){
pTab = (StatTable *)sqlite3_malloc64(sizeof(StatTable));
if( pTab==0 ) rc = SQLITE_NOMEM_BKPT;
}
assert( rc==SQLITE_OK || pTab==0 );
if( rc==SQLITE_OK ){
memset(pTab, 0, sizeof(StatTable));
pTab->db = db;
pTab->iDb = iDb;
}
*ppVtab = (sqlite3_vtab*)pTab;
return rc;
}
static int statDisconnect(sqlite3_vtab *pVtab){
sqlite3_free(pVtab);
return SQLITE_OK;
}
static int statBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
int i;
int iSchema = -1;
int iName = -1;
int iAgg = -1;
(void)tab;
for(i=0; i<pIdxInfo->nConstraint; i++){
if( pIdxInfo->aConstraint[i].op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue;
if( pIdxInfo->aConstraint[i].usable==0 ){
return SQLITE_CONSTRAINT;
}
switch( pIdxInfo->aConstraint[i].iColumn ){
case 0: {
iName = i;
break;
}
case 10: {
iSchema = i;
break;
}
case 11: {
iAgg = i;
break;
}
}
}
i = 0;
if( iSchema>=0 ){
pIdxInfo->aConstraintUsage[iSchema].argvIndex = ++i;
pIdxInfo->aConstraintUsage[iSchema].omit = 1;
pIdxInfo->idxNum |= 0x01;
}
if( iName>=0 ){
pIdxInfo->aConstraintUsage[iName].argvIndex = ++i;
pIdxInfo->idxNum |= 0x02;
}
if( iAgg>=0 ){
pIdxInfo->aConstraintUsage[iAgg].argvIndex = ++i;
pIdxInfo->idxNum |= 0x04;
}
pIdxInfo->estimatedCost = 1.0;
if( ( pIdxInfo->nOrderBy==1
&& pIdxInfo->aOrderBy[0].iColumn==0
&& pIdxInfo->aOrderBy[0].desc==0
) ||
( pIdxInfo->nOrderBy==2
&& pIdxInfo->aOrderBy[0].iColumn==0
&& pIdxInfo->aOrderBy[0].desc==0
&& pIdxInfo->aOrderBy[1].iColumn==1
&& pIdxInfo->aOrderBy[1].desc==0
)
){
pIdxInfo->orderByConsumed = 1;
pIdxInfo->idxNum |= 0x08;
}
return SQLITE_OK;
}
static int statOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
StatTable *pTab = (StatTable *)pVTab;
StatCursor *pCsr;
pCsr = (StatCursor *)sqlite3_malloc64(sizeof(StatCursor));
if( pCsr==0 ){
return SQLITE_NOMEM_BKPT;
}else{
memset(pCsr, 0, sizeof(StatCursor));
pCsr->base.pVtab = pVTab;
pCsr->iDb = pTab->iDb;
}
*ppCursor = (sqlite3_vtab_cursor *)pCsr;
return SQLITE_OK;
}
static void statClearCells(StatPage *p){
int i;
if( p->aCell ){
for(i=0; i<p->nCell; i++){
sqlite3_free(p->aCell[i].aOvfl);
}
sqlite3_free(p->aCell);
}
p->nCell = 0;
p->aCell = 0;
}
static void statClearPage(StatPage *p){
u8 *aPg = p->aPg;
statClearCells(p);
sqlite3_free(p->zPath);
memset(p, 0, sizeof(StatPage));
p->aPg = aPg;
}
static void statResetCsr(StatCursor *pCsr){
int i;
for(i=0; i<ArraySize(pCsr->aPage); i++){
statClearPage(&pCsr->aPage[i]);
sqlite3_free(pCsr->aPage[i].aPg);
pCsr->aPage[i].aPg = 0;
}
sqlite3_reset(pCsr->pStmt);
pCsr->iPage = 0;
sqlite3_free(pCsr->zPath);
pCsr->zPath = 0;
pCsr->isEof = 0;
}
static void statResetCounts(StatCursor *pCsr){
pCsr->nCell = 0;
pCsr->nMxPayload = 0;
pCsr->nUnused = 0;
pCsr->nPayload = 0;
pCsr->szPage = 0;
pCsr->nPage = 0;
}
static int statClose(sqlite3_vtab_cursor *pCursor){
StatCursor *pCsr = (StatCursor *)pCursor;
statResetCsr(pCsr);
sqlite3_finalize(pCsr->pStmt);
sqlite3_free(pCsr);
return SQLITE_OK;
}
static int getLocalPayload(
int nUsable,
u8 flags,
int nTotal
){
int nLocal;
int nMinLocal;
int nMaxLocal;
if( flags==0x0D ){
nMinLocal = (nUsable - 12) * 32 / 255 - 23;
nMaxLocal = nUsable - 35;
}else{
nMinLocal = (nUsable - 12) * 32 / 255 - 23;
nMaxLocal = (nUsable - 12) * 64 / 255 - 23;
}
nLocal = nMinLocal + (nTotal - nMinLocal) % (nUsable - 4);
if( nLocal>nMaxLocal ) nLocal = nMinLocal;
return nLocal;
}
static int statDecodePage(Btree *pBt, StatPage *p){
int nUnused;
int iOff;
int nHdr;
int isLeaf;
int szPage;
u8 *aData = p->aPg;
u8 *aHdr = &aData[p->iPgno==1 ? 100 : 0];
p->flags = aHdr[0];
if( p->flags==0x0A || p->flags==0x0D ){
isLeaf = 1;
nHdr = 8;
}else if( p->flags==0x05 || p->flags==0x02 ){
isLeaf = 0;
nHdr = 12;
}else{
goto statPageIsCorrupt;
}
if( p->iPgno==1 ) nHdr += 100;
p->nCell = get2byte(&aHdr[3]);
p->nMxPayload = 0;
szPage = sqlite3BtreeGetPageSize(pBt);
nUnused = get2byte(&aHdr[5]) - nHdr - 2*p->nCell;
nUnused += (int)aHdr[7];
iOff = get2byte(&aHdr[1]);
while( iOff ){
int iNext;
if( iOff>=szPage ) goto statPageIsCorrupt;
nUnused += get2byte(&aData[iOff+2]);
iNext = get2byte(&aData[iOff]);
if( iNext<iOff+4 && iNext>0 ) goto statPageIsCorrupt;
iOff = iNext;
}
p->nUnused = nUnused;
p->iRightChildPg = isLeaf ? 0 : sqlite3Get4byte(&aHdr[8]);
if( p->nCell ){
int i;
int nUsable;
sqlite3BtreeEnter(pBt);
nUsable = szPage - sqlite3BtreeGetReserveNoMutex(pBt);
sqlite3BtreeLeave(pBt);
p->aCell = sqlite3_malloc64((p->nCell+1) * sizeof(StatCell));
if( p->aCell==0 ) return SQLITE_NOMEM_BKPT;
memset(p->aCell, 0, (p->nCell+1) * sizeof(StatCell));
for(i=0; i<p->nCell; i++){
StatCell *pCell = &p->aCell[i];
iOff = get2byte(&aData[nHdr+i*2]);
if( iOff<nHdr || iOff>=szPage ) goto statPageIsCorrupt;
if( !isLeaf ){
pCell->iChildPg = sqlite3Get4byte(&aData[iOff]);
iOff += 4;
}
if( p->flags==0x05 ){
}else{
u32 nPayload;
int nLocal;
iOff += getVarint32(&aData[iOff], nPayload);
if( p->flags==0x0D ){
u64 dummy;
iOff += sqlite3GetVarint(&aData[iOff], &dummy);
}
if( nPayload>(u32)p->nMxPayload ) p->nMxPayload = nPayload;
nLocal = getLocalPayload(nUsable, p->flags, nPayload);
if( nLocal<0 ) goto statPageIsCorrupt;
pCell->nLocal = nLocal;
assert( nPayload>=(u32)nLocal );
assert( nLocal<=(nUsable-35) );
if( nPayload>(u32)nLocal ){
int j;
int nOvfl = ((nPayload - nLocal) + nUsable-4 - 1) / (nUsable - 4);
if( iOff+nLocal+4>nUsable || nPayload>0x7fffffff ){
goto statPageIsCorrupt;
}
pCell->nLastOvfl = (nPayload-nLocal) - (nOvfl-1) * (nUsable-4);
pCell->nOvfl = nOvfl;
pCell->aOvfl = sqlite3_malloc64(sizeof(u32)*nOvfl);
if( pCell->aOvfl==0 ) return SQLITE_NOMEM_BKPT;
pCell->aOvfl[0] = sqlite3Get4byte(&aData[iOff+nLocal]);
for(j=1; j<nOvfl; j++){
int rc;
u32 iPrev = pCell->aOvfl[j-1];
DbPage *pPg = 0;
rc = sqlite3PagerGet(sqlite3BtreePager(pBt), iPrev, &pPg, 0);
if( rc!=SQLITE_OK ){
assert( pPg==0 );
return rc;
}
pCell->aOvfl[j] = sqlite3Get4byte(sqlite3PagerGetData(pPg));
sqlite3PagerUnref(pPg);
}
}
}
}
}
return SQLITE_OK;
statPageIsCorrupt:
p->flags = 0;
statClearCells(p);
return SQLITE_OK;
}
static void statSizeAndOffset(StatCursor *pCsr){
StatTable *pTab = (StatTable *)((sqlite3_vtab_cursor *)pCsr)->pVtab;
Btree *pBt = pTab->db->aDb[pTab->iDb].pBt;
Pager *pPager = sqlite3BtreePager(pBt);
sqlite3_file *fd;
sqlite3_int64 x[2];
fd = sqlite3PagerFile(pPager);
x[0] = pCsr->iPageno;
if( sqlite3OsFileControl(fd, 230440, &x)==SQLITE_OK ){
pCsr->iOffset = x[0];
pCsr->szPage += x[1];
}else{
pCsr->szPage += sqlite3BtreeGetPageSize(pBt);
pCsr->iOffset = (i64)pCsr->szPage * (pCsr->iPageno - 1);
}
}
static int statGetPage(
Btree *pBt,
u32 iPg,
StatPage *pPg
){
int pgsz = sqlite3BtreeGetPageSize(pBt);
DbPage *pDbPage = 0;
int rc;
if( pPg->aPg==0 ){
pPg->aPg = (u8*)sqlite3_malloc(pgsz + DBSTAT_PAGE_PADDING_BYTES);
if( pPg->aPg==0 ){
return SQLITE_NOMEM_BKPT;
}
memset(&pPg->aPg[pgsz], 0, DBSTAT_PAGE_PADDING_BYTES);
}
rc = sqlite3PagerGet(sqlite3BtreePager(pBt), iPg, &pDbPage, 0);
if( rc==SQLITE_OK ){
const u8 *a = sqlite3PagerGetData(pDbPage);
memcpy(pPg->aPg, a, pgsz);
sqlite3PagerUnref(pDbPage);
}
return rc;
}
static int statNext(sqlite3_vtab_cursor *pCursor){
int rc;
int nPayload;
char *z;
StatCursor *pCsr = (StatCursor *)pCursor;
StatTable *pTab = (StatTable *)pCursor->pVtab;
Btree *pBt = pTab->db->aDb[pCsr->iDb].pBt;
Pager *pPager = sqlite3BtreePager(pBt);
sqlite3_free(pCsr->zPath);
pCsr->zPath = 0;
statNextRestart:
if( pCsr->iPage<0 ){
statResetCounts(pCsr);
rc = sqlite3_step(pCsr->pStmt);
if( rc==SQLITE_ROW ){
int nPage;
u32 iRoot = (u32)sqlite3_column_int64(pCsr->pStmt, 1);
sqlite3PagerPagecount(pPager, &nPage);
if( nPage==0 ){
pCsr->isEof = 1;
return sqlite3_reset(pCsr->pStmt);
}
rc = statGetPage(pBt, iRoot, &pCsr->aPage[0]);
pCsr->aPage[0].iPgno = iRoot;
pCsr->aPage[0].iCell = 0;
if( !pCsr->isAgg ){
pCsr->aPage[0].zPath = z = sqlite3_mprintf("/");
if( z==0 ) rc = SQLITE_NOMEM_BKPT;
}
pCsr->iPage = 0;
pCsr->nPage = 1;
}else{
pCsr->isEof = 1;
return sqlite3_reset(pCsr->pStmt);
}
}else{
StatPage *p = &pCsr->aPage[pCsr->iPage];
if( !pCsr->isAgg ) statResetCounts(pCsr);
while( p->iCell<p->nCell ){
StatCell *pCell = &p->aCell[p->iCell];
while( pCell->iOvfl<pCell->nOvfl ){
int nUsable, iOvfl;
sqlite3BtreeEnter(pBt);
nUsable = sqlite3BtreeGetPageSize(pBt) -
sqlite3BtreeGetReserveNoMutex(pBt);
sqlite3BtreeLeave(pBt);
pCsr->nPage++;
statSizeAndOffset(pCsr);
if( pCell->iOvfl<pCell->nOvfl-1 ){
pCsr->nPayload += nUsable - 4;
}else{
pCsr->nPayload += pCell->nLastOvfl;
pCsr->nUnused += nUsable - 4 - pCell->nLastOvfl;
}
iOvfl = pCell->iOvfl;
pCell->iOvfl++;
if( !pCsr->isAgg ){
pCsr->zName = (char *)sqlite3_column_text(pCsr->pStmt, 0);
pCsr->iPageno = pCell->aOvfl[iOvfl];
pCsr->zPagetype = "overflow";
pCsr->zPath = z = sqlite3_mprintf(
"%s%.3x+%.6x", p->zPath, p->iCell, iOvfl
);
return z==0 ? SQLITE_NOMEM_BKPT : SQLITE_OK;
}
}
if( p->iRightChildPg ) break;
p->iCell++;
}
if( !p->iRightChildPg || p->iCell>p->nCell ){
statClearPage(p);
pCsr->iPage--;
if( pCsr->isAgg && pCsr->iPage<0 ){
return SQLITE_OK;
}
goto statNextRestart;
}
pCsr->iPage++;
if( pCsr->iPage>=ArraySize(pCsr->aPage) ){
statResetCsr(pCsr);
return SQLITE_CORRUPT_BKPT;
}
assert( p==&pCsr->aPage[pCsr->iPage-1] );
if( p->iCell==p->nCell ){
p[1].iPgno = p->iRightChildPg;
}else{
p[1].iPgno = p->aCell[p->iCell].iChildPg;
}
rc = statGetPage(pBt, p[1].iPgno, &p[1]);
pCsr->nPage++;
p[1].iCell = 0;
if( !pCsr->isAgg ){
p[1].zPath = z = sqlite3_mprintf("%s%.3x/", p->zPath, p->iCell);
if( z==0 ) rc = SQLITE_NOMEM_BKPT;
}
p->iCell++;
}
if( rc==SQLITE_OK ){
int i;
StatPage *p = &pCsr->aPage[pCsr->iPage];
pCsr->zName = (char *)sqlite3_column_text(pCsr->pStmt, 0);
pCsr->iPageno = p->iPgno;
rc = statDecodePage(pBt, p);
if( rc==SQLITE_OK ){
statSizeAndOffset(pCsr);
switch( p->flags ){
case 0x05:
case 0x02:
pCsr->zPagetype = "internal";
break;
case 0x0D:
case 0x0A:
pCsr->zPagetype = "leaf";
break;
default:
pCsr->zPagetype = "corrupted";
break;
}
pCsr->nCell += p->nCell;
pCsr->nUnused += p->nUnused;
if( p->nMxPayload>pCsr->nMxPayload ) pCsr->nMxPayload = p->nMxPayload;
if( !pCsr->isAgg ){
pCsr->zPath = z = sqlite3_mprintf("%s", p->zPath);
if( z==0 ) rc = SQLITE_NOMEM_BKPT;
}
nPayload = 0;
for(i=0; i<p->nCell; i++){
nPayload += p->aCell[i].nLocal;
}
pCsr->nPayload += nPayload;
if( pCsr->isAgg ) goto statNextRestart;
}
}
return rc;
}
static int statEof(sqlite3_vtab_cursor *pCursor){
StatCursor *pCsr = (StatCursor *)pCursor;
return pCsr->isEof;
}
static int statFilter(
sqlite3_vtab_cursor *pCursor,
int idxNum, const char *idxStr,
int argc, sqlite3_value **argv
){
StatCursor *pCsr = (StatCursor *)pCursor;
StatTable *pTab = (StatTable*)(pCursor->pVtab);
sqlite3_str *pSql;
char *zSql;
int iArg = 0;
int rc = SQLITE_OK;
const char *zName = 0;
(void)argc;
(void)idxStr;
statResetCsr(pCsr);
sqlite3_finalize(pCsr->pStmt);
pCsr->pStmt = 0;
if( idxNum & 0x01 ){
const char *zDbase = (const char*)sqlite3_value_text(argv[iArg++]);
pCsr->iDb = sqlite3FindDbName(pTab->db, zDbase);
if( pCsr->iDb<0 ){
pCsr->iDb = 0;
pCsr->isEof = 1;
return SQLITE_OK;
}
}else{
pCsr->iDb = pTab->iDb;
}
if( idxNum & 0x02 ){
zName = (const char*)sqlite3_value_text(argv[iArg++]);
}
if( idxNum & 0x04 ){
pCsr->isAgg = sqlite3_value_double(argv[iArg++])!=0.0;
}else{
pCsr->isAgg = 0;
}
pSql = sqlite3_str_new(pTab->db);
sqlite3_str_appendf(pSql,
"SELECT * FROM ("
"SELECT 'sqlite_schema' AS name,1 AS rootpage,'table' AS type"
" UNION ALL "
"SELECT name,rootpage,type"
" FROM \"%w\".sqlite_schema WHERE rootpage!=0)",
pTab->db->aDb[pCsr->iDb].zDbSName);
if( zName ){
sqlite3_str_appendf(pSql, "WHERE name=%Q", zName);
}
if( idxNum & 0x08 ){
sqlite3_str_appendf(pSql, " ORDER BY name");
}
zSql = sqlite3_str_finish(pSql);
if( zSql==0 ){
return SQLITE_NOMEM_BKPT;
}else{
rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pCsr->pStmt, 0);
sqlite3_free(zSql);
}
if( rc==SQLITE_OK ){
pCsr->iPage = -1;
rc = statNext(pCursor);
}
return rc;
}
static int statColumn(
sqlite3_vtab_cursor *pCursor,
sqlite3_context *ctx,
int i
){
StatCursor *pCsr = (StatCursor *)pCursor;
switch( i ){
case 0:
sqlite3_result_text(ctx, pCsr->zName, -1, SQLITE_TRANSIENT);
break;
case 1:
if( !pCsr->isAgg ){
sqlite3_result_text(ctx, pCsr->zPath, -1, SQLITE_TRANSIENT);
}
break;
case 2:
if( pCsr->isAgg ){
sqlite3_result_int64(ctx, pCsr->nPage);
}else{
sqlite3_result_int64(ctx, pCsr->iPageno);
}
break;
case 3:
if( !pCsr->isAgg ){
sqlite3_result_text(ctx, pCsr->zPagetype, -1, SQLITE_STATIC);
}
break;
case 4:
sqlite3_result_int64(ctx, pCsr->nCell);
break;
case 5:
sqlite3_result_int64(ctx, pCsr->nPayload);
break;
case 6:
sqlite3_result_int64(ctx, pCsr->nUnused);
break;
case 7:
sqlite3_result_int64(ctx, pCsr->nMxPayload);
break;
case 8:
if( !pCsr->isAgg ){
sqlite3_result_int64(ctx, pCsr->iOffset);
}
break;
case 9:
sqlite3_result_int64(ctx, pCsr->szPage);
break;
case 10: {
sqlite3 *db = sqlite3_context_db_handle(ctx);
int iDb = pCsr->iDb;
sqlite3_result_text(ctx, db->aDb[iDb].zDbSName, -1, SQLITE_STATIC);
break;
}
default: {
sqlite3_result_int(ctx, pCsr->isAgg);
break;
}
}
return SQLITE_OK;
}
static int statRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
StatCursor *pCsr = (StatCursor *)pCursor;
*pRowid = pCsr->iPageno;
return SQLITE_OK;
}
int sqlite3DbstatRegister(sqlite3 *db){
static sqlite3_module dbstat_module = {
0,
statConnect,
statConnect,
statBestIndex,
statDisconnect,
statDisconnect,
statOpen,
statClose,
statFilter,
statNext,
statEof,
statColumn,
statRowid,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
};
return sqlite3_create_module(db, "dbstat", &dbstat_module, 0);
}
#elif defined(SQLITE_ENABLE_DBSTAT_VTAB)
int sqlite3DbstatRegister(sqlite3 *db){ return SQLITE_OK; }
#endif