using System.Diagnostics; using u8 = System.Byte; using u32 = System.UInt32; namespace Community.CsharpSqlite { #if TCLSH using tcl.lang; using DbPage = Sqlite3.PgHdr; using sqlite_int64 = System.Int64; using sqlite3_stmt = Sqlite3.Vdbe; using sqlite3_value = Sqlite3.Mem; using Tcl_CmdInfo = tcl.lang.WrappedCommand; using Tcl_Interp = tcl.lang.Interp; using Tcl_Obj = tcl.lang.TclObject; using ClientData = System.Object; using System; #endif public partial class Sqlite3 { /* ** 2010 July 12 ** ** 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 the "dbstat" virtual table. ** ** The dbstat virtual table is used to extract low-level formatting ** information from an SQLite database in order to implement the ** "sqlite3_analyzer" utility. See the ../tool/spaceanal.tcl script ** for an example implementation. ************************************************************************* ** Included in SQLite3 port to C#-SQLite; 2008 Noah B Hart ** C#-SQLite is an independent reimplementation of the SQLite software library ** ** SQLITE_SOURCE_ID: 2011-06-23 19:49:22 4374b7e83ea0a3fbc3691f9c0c936272862f32f2 ** **************************************************************************/ //#include "sqliteInt.h" #if !SQLITE_OMIT_VIRTUALTABLE /* ** Page paths: ** ** The value of the 'path' column describes the path taken from the ** root-node of the b-tree structure to each page. The value of the ** root-node path is '/'. ** ** The value of the path for the left-most child page of the root of ** a b-tree is '/000/'. (Btrees store content ordered from left to right ** so the pages to the left have smaller keys than the pages to the right.) ** The next to left-most child of the root page is ** '/001', and so on, each sibling page identified by a 3-digit hex ** value. The children of the 451st left-most sibling have paths such ** as '/1c2/000/, '/1c2/001/' etc. ** ** Overflow pages are specified by appending a '+' character and a ** six-digit hexadecimal value to the path to the cell they are linked ** from. For example, the three overflow pages in a chain linked from ** the left-most cell of the 450th child of the root page are identified ** by the paths: ** ** '/1c2/000+000000' // First page in overflow chain ** '/1c2/000+000001' // Second page in overflow chain ** '/1c2/000+000002' // Third page in overflow chain ** ** If the paths are sorted using the BINARY collation sequence, then ** the overflow pages associated with a cell will appear earlier in the ** sort-order than its child page: ** ** '/1c2/000/' // Left-most child of 451st child of root */ const string VTAB_SCHEMA = "CREATE TABLE xx( " + " name STRING, /* Name of table or index */" + " path INTEGER, /* Path to page from root */" + " pageno INTEGER, /* Page number */" + " pagetype STRING, /* 'internal', 'leaf' or 'overflow' */" + " ncell INTEGER, /* Cells on page (0 for overflow) */" + " payload INTEGER, /* Bytes of payload on this page */" + " unused INTEGER, /* Bytes of unused space on this page */" + " mx_payload INTEGER /* Largest payload size of all cells */" + ");"; #if FALSE //#define VTAB_SCHEMA2 \ "CREATE TABLE yy( " \ " pageno INTEGER, /* B-tree page number */" \ " cellno INTEGER, /* Cell number within page */" \ " local INTEGER, /* Bytes of content stored locally */" \ " payload INTEGER, /* Total cell payload size */" \ " novfl INTEGER /* Number of overflow pages */" \ ");" #endif //typedef struct StatTable StatTable; //typedef struct StatCursor StatCursor; //typedef struct StatPage StatPage; //typedef struct StatCell StatCell; class StatCell { public int nLocal; /* Bytes of local payload */ public u32 iChildPg; /* Child node (or 0 if this is a leaf) */ public int nOvfl; /* Entries in aOvfl[] */ public u32[] aOvfl; /* Array of overflow page numbers */ public int nLastOvfl; /* Bytes of payload on final overflow page */ public int iOvfl; /* Iterates through aOvfl[] */ }; class StatPage { public u32 iPgno; public DbPage pPg; public int iCell; public string zPath; /* Path to this page */ /* Variables populated by statDecodePage(): */ public u8 flags; /* Copy of flags byte */ public int nCell; /* Number of cells on page */ public int nUnused; /* Number of unused bytes on page */ public StatCell[] aCell; /* Array of parsed cells */ public u32 iRightChildPg; /* Right-child page number (or 0) */ public int nMxPayload; /* Largest payload of any cell on this page */ }; class StatCursor : sqlite3_vtab_cursor { //sqlite3_vtab_cursor base; public sqlite3_stmt pStmt; /* Iterates through set of root pages */ public int isEof; /* After pStmt has returned SQLITE_DONE */ public StatPage[] aPage = new StatPage[32]; public int iPage; /* Current entry in aPage[] */ /* Values to return. */ public string zName; /* Value of 'name' column */ public string zPath; /* Value of 'path' column */ public u32 iPageno; /* Value of 'pageno' column */ public string zPagetype; /* Value of 'pagetype' column */ public int nCell; /* Value of 'ncell' column */ public int nPayload; /* Value of 'payload' column */ public int nUnused; /* Value of 'unused' column */ public int nMxPayload; /* Value of 'mx_payload' column */ }; class StatTable : sqlite3_vtab { //sqlite3_vtab base; public sqlite3 db; }; //#if !get2byte //# define get2byte(x) ((x)[0]<<8 | (x)[1]) //#endif /* ** Connect to or create a statvfs virtual table. */ static int statConnect( sqlite3 db, object pAux, int argc, string[] argv, out sqlite3_vtab ppVtab, out string pzErr ) { StatTable pTab; pTab = new StatTable();//(StatTable )sqlite3_malloc(sizeof(StatTable)); //memset(pTab, 0, sizeof(StatTable)); pTab.db = db; sqlite3_declare_vtab( db, VTAB_SCHEMA ); ppVtab = pTab; pzErr = ""; return SQLITE_OK; } /* ** Disconnect from or destroy a statvfs virtual table. */ static int statDisconnect( ref object pVtab ) { pVtab = null;//sqlite3_free( pVtab ); return SQLITE_OK; } /* ** There is no "best-index". This virtual table always does a linear ** scan of the binary VFS log file. */ static int statBestIndex( sqlite3_vtab tab, ref sqlite3_index_info pIdxInfo ) { /* Records are always returned in ascending order of (name, path). ** If this will satisfy the client, set the orderByConsumed flag so that ** SQLite does not do an external sort. */ if ( ( pIdxInfo.nOrderBy == 1 && pIdxInfo.aOrderBy[0].iColumn == 0 && pIdxInfo.aOrderBy[0].desc == false ) || ( pIdxInfo.nOrderBy == 2 && pIdxInfo.aOrderBy[0].iColumn == 0 && pIdxInfo.aOrderBy[0].desc == false && pIdxInfo.aOrderBy[1].iColumn == 1 && pIdxInfo.aOrderBy[1].desc == false ) ) { pIdxInfo.orderByConsumed = true; } pIdxInfo.estimatedCost = 10.0; return SQLITE_OK; } /* ** Open a new statvfs cursor. */ static int statOpen( sqlite3_vtab pVTab, out sqlite3_vtab_cursor ppCursor ) { StatTable pTab = (StatTable)pVTab; StatCursor pCsr; int rc; pCsr = new StatCursor();//(StatCursor )sqlite3_malloc(sizeof(StatCursor)); //memset(pCsr, 0, sizeof(StatCursor)); pCsr.pVtab = pVTab; rc = sqlite3_prepare_v2( pTab.db, "SELECT 'sqlite_master' AS name, 1 AS rootpage, 'table' AS type" + " UNION ALL " + "SELECT name, rootpage, type FROM sqlite_master WHERE rootpage!=0" + " ORDER BY name", -1, ref pCsr.pStmt, 0 ); if ( rc != SQLITE_OK ) { pCsr = null;//sqlite3_free( pCsr ); ppCursor = null; return rc; } ppCursor = (sqlite3_vtab_cursor)pCsr; return SQLITE_OK; } static void statClearPage( ref StatPage p ) { int i; if ( p != null && p.aCell != null ) { for ( i = 0; i < p.nCell; i++ ) { p.aCell[i].aOvfl = null;//sqlite3_free( p.aCell[i].aOvfl ); } sqlite3PagerUnref( p.pPg ); // sqlite3_free( p.aCell ); // sqlite3_free( p.zPath ); } p = new StatPage();//memset( p, 0, sizeof( StatPage ) ); } static void statResetCsr( StatCursor pCsr ) { int i; sqlite3_reset( pCsr.pStmt ); for ( i = 0; i < ArraySize( pCsr.aPage ); i++ ) { statClearPage( ref pCsr.aPage[i] ); } pCsr.iPage = 0; //sqlite3_free(pCsr.zPath); pCsr.zPath = null; } /* ** Close a statvfs cursor. */ static int statClose( ref sqlite3_vtab_cursor pCursor ) { StatCursor pCsr = (StatCursor)pCursor; statResetCsr( pCsr ); sqlite3_finalize( pCsr.pStmt ); pCsr = null;//sqlite3_free( pCsr ); return SQLITE_OK; } static void getLocalPayload( int nUsable, /* Usable bytes per page */ u8 flags, /* Page flags */ int nTotal, /* Total record (payload) size */ out int pnLocal /* OUT: Bytes stored locally */ ) { int nLocal; int nMinLocal; int nMaxLocal; if ( flags == 0x0D ) { /* Table leaf node */ nMinLocal = ( nUsable - 12 ) * 32 / 255 - 23; nMaxLocal = nUsable - 35; } else { /* Index interior and leaf nodes */ nMinLocal = ( nUsable - 12 ) * 32 / 255 - 23; nMaxLocal = ( nUsable - 12 ) * 64 / 255 - 23; } nLocal = nMinLocal + ( nTotal - nMinLocal ) % ( nUsable - 4 ); if ( nLocal > nMaxLocal ) nLocal = nMinLocal; pnLocal = nLocal; } static int statDecodePage( Btree pBt, StatPage p ) { int nUnused; int iOff; int nHdr; int isLeaf; u8[] aData = sqlite3PagerGetData( p.pPg ); u8[] aHdr = new byte[p.iPgno == 1 ? aData.Length - 100 : aData.Length]; Buffer.BlockCopy( aData, p.iPgno == 1 ? 100 : 0, aHdr, 0, aHdr.Length ); p.flags = aHdr[0]; p.nCell = get2byte( aHdr, 3 ); p.nMxPayload = 0; isLeaf = ( p.flags == 0x0A || p.flags == 0x0D ) ? 1 : 0; nHdr = 12 - isLeaf * 4 + ( ( p.iPgno == 1 ) ? 1 : 0 ) * 100; nUnused = get2byte( aHdr, 5 ) - nHdr - 2 * p.nCell; nUnused += (int)aHdr[7]; iOff = get2byte( aHdr, 1 ); while ( iOff != 0 ) { nUnused += get2byte( aData, iOff + 2 ); iOff = get2byte( aData, iOff ); } p.nUnused = nUnused; p.iRightChildPg = isLeaf != 0 ? 0 : sqlite3Get4byte( aHdr, 8 ); if ( p.nCell != 0 ) { int i; /* Used to iterate through cells */ int nUsable = sqlite3BtreeGetPageSize( pBt ) - sqlite3BtreeGetReserve( pBt ); p.aCell = new StatCell[p.nCell + 1];// sqlite3_malloc( ( p.nCell + 1 ) * sizeof( StatCell ) ); //memset(p.aCell, 0, (p.nCell+1) * sizeof(StatCell)); for ( i = 0; i < p.nCell; i++ ) { p.aCell[i] = new StatCell(); StatCell pCell = p.aCell[i]; iOff = get2byte( aData, nHdr + i * 2 ); if ( 0 == isLeaf ) { pCell.iChildPg = sqlite3Get4byte( aData, iOff ); iOff += 4; } if ( p.flags == 0x05 ) { /* A table interior node. nPayload==0. */ } else { u32 nPayload; /* Bytes of payload total (local+overflow) */ int nLocal; /* Bytes of payload stored locally */ iOff += getVarint32( aData, iOff, out nPayload ); if ( p.flags == 0x0D ) { ulong dummy; iOff += sqlite3GetVarint( aData, iOff, out dummy ); } if ( nPayload > p.nMxPayload ) p.nMxPayload = (int)nPayload; getLocalPayload( nUsable, p.flags, (int)nPayload, out nLocal ); pCell.nLocal = nLocal; Debug.Assert( nPayload >= nLocal ); Debug.Assert( nLocal <= ( nUsable - 35 ) ); if ( nPayload > nLocal ) { int j; int nOvfl = (int)( ( nPayload - nLocal ) + nUsable - 4 - 1 ) / ( nUsable - 4 ); pCell.nLastOvfl = (int)( nPayload - nLocal ) - ( nOvfl - 1 ) * ( nUsable - 4 ); pCell.nOvfl = nOvfl; pCell.aOvfl = new uint[nOvfl];//sqlite3_malloc(sizeof(u32)*nOvfl); pCell.aOvfl[0] = sqlite3Get4byte( aData, iOff + nLocal ); for ( j = 1; j < nOvfl; j++ ) { int rc; u32 iPrev = pCell.aOvfl[j - 1]; DbPage pPg = null; rc = sqlite3PagerGet( sqlite3BtreePager( pBt ), iPrev, ref pPg ); if ( rc != SQLITE_OK ) { Debug.Assert( pPg == null ); return rc; } pCell.aOvfl[j] = sqlite3Get4byte( sqlite3PagerGetData( pPg ) ); sqlite3PagerUnref( pPg ); } } } } } return SQLITE_OK; } /* ** Move a statvfs cursor to the next entry in the file. */ static int statNext( sqlite3_vtab_cursor pCursor ) { int rc = 0; int nPayload; StatCursor pCsr = (StatCursor)pCursor; StatTable pTab = (StatTable)pCursor.pVtab; Btree pBt = pTab.db.aDb[0].pBt; Pager pPager = sqlite3BtreePager( pBt ); //sqlite3_free(pCsr.zPath); pCsr.zPath = null; if ( pCsr.aPage[0].pPg == null ) { rc = sqlite3_step( pCsr.pStmt ); if ( rc == SQLITE_ROW ) { u32 nPage; u32 iRoot = (u32)sqlite3_column_int64( pCsr.pStmt, 1 ); sqlite3PagerPagecount( pPager, out nPage ); if ( nPage == 0 ) { pCsr.isEof = 1; return sqlite3_reset( pCsr.pStmt ); } rc = sqlite3PagerGet( pPager, iRoot, ref pCsr.aPage[0].pPg ); pCsr.aPage[0].iPgno = iRoot; pCsr.aPage[0].iCell = 0; pCsr.aPage[0].zPath = sqlite3_mprintf( "/" ); pCsr.iPage = 0; } else { pCsr.isEof = 1; return sqlite3_reset( pCsr.pStmt ); } } else { /* Page p itself has already been visited. */ StatPage p = pCsr.aPage[pCsr.iPage]; StatPage p1 = pCsr.aPage[pCsr.iPage + 1]; while ( p.iCell < p.nCell ) { StatCell pCell = p.aCell[p.iCell]; if ( pCell.iOvfl < pCell.nOvfl ) { int nUsable = sqlite3BtreeGetPageSize( pBt ) - sqlite3BtreeGetReserve( pBt ); pCsr.zName = sqlite3_column_text( pCsr.pStmt, 0 ); pCsr.iPageno = pCell.aOvfl[pCell.iOvfl]; pCsr.zPagetype = "overflow"; pCsr.nCell = 0; pCsr.nMxPayload = 0; pCsr.zPath = sqlite3_mprintf( "%s%.3x+%.6x", p.zPath, p.iCell, pCell.iOvfl ); if ( pCell.iOvfl < pCell.nOvfl - 1 ) { pCsr.nUnused = 0; pCsr.nPayload = nUsable - 4; } else { pCsr.nPayload = pCell.nLastOvfl; pCsr.nUnused = nUsable - 4 - pCsr.nPayload; } pCell.iOvfl++; return SQLITE_OK; } if ( p.iRightChildPg != 0 ) break; p.iCell++; } while ( 0 == p.iRightChildPg || p.iCell > p.nCell ) { statClearPage( ref p ); pCsr.aPage[pCsr.iPage] = p; if ( pCsr.iPage == 0 ) return statNext( pCursor ); pCsr.iPage--; p = pCsr.aPage[pCsr.iPage]; if ( pCsr.aPage[pCsr.iPage + 1] == null ) pCsr.aPage[pCsr.iPage + 1] = new StatPage(); p1 = pCsr.aPage[pCsr.iPage + 1]; } pCsr.iPage++; Debug.Assert( p == pCsr.aPage[pCsr.iPage - 1] ); if ( p.iCell == p.nCell ) { p1.iPgno = p.iRightChildPg; } else { p1.iPgno = p.aCell[p.iCell].iChildPg; } rc = sqlite3PagerGet( pPager, p1.iPgno, ref p1.pPg ); p1.iCell = 0; p1.zPath = sqlite3_mprintf( "%s%.3x/", p.zPath, p.iCell ); p.iCell++; } /* Populate the StatCursor fields with the values to be returned ** by the xColumn() and xRowid() methods. */ if ( rc == SQLITE_OK ) { int i; StatPage p = pCsr.aPage[pCsr.iPage]; pCsr.zName = sqlite3_column_text( pCsr.pStmt, 0 ); pCsr.iPageno = p.iPgno; statDecodePage( pBt, p ); switch ( p.flags ) { case 0x05: /* table internal */ case 0x02: /* index internal */ pCsr.zPagetype = "internal"; break; case 0x0D: /* table leaf */ case 0x0A: /* index leaf */ pCsr.zPagetype = "leaf"; break; default: pCsr.zPagetype = "corrupted"; break; } pCsr.nCell = p.nCell; pCsr.nUnused = p.nUnused; pCsr.nMxPayload = p.nMxPayload; pCsr.zPath = sqlite3_mprintf( "%s", p.zPath ); nPayload = 0; for ( i = 0; i < p.nCell; i++ ) { nPayload += p.aCell[i].nLocal; } pCsr.nPayload = nPayload; } 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, string idxStr, int argc, sqlite3_value[] argv ) { StatCursor pCsr = (StatCursor)pCursor; statResetCsr( pCsr ); return statNext( pCursor ); } static int statColumn( sqlite3_vtab_cursor pCursor, sqlite3_context ctx, int i ) { StatCursor pCsr = (StatCursor)pCursor; switch ( i ) { case 0: /* name */ sqlite3_result_text( ctx, pCsr.zName, -1, SQLITE_STATIC ); break; case 1: /* path */ sqlite3_result_text( ctx, pCsr.zPath, -1, SQLITE_TRANSIENT ); break; case 2: /* pageno */ sqlite3_result_int64( ctx, pCsr.iPageno ); break; case 3: /* pagetype */ sqlite3_result_text( ctx, pCsr.zPagetype, -1, SQLITE_STATIC ); break; case 4: /* ncell */ sqlite3_result_int( ctx, pCsr.nCell ); break; case 5: /* payload */ sqlite3_result_int( ctx, pCsr.nPayload ); break; case 6: /* unused */ sqlite3_result_int( ctx, pCsr.nUnused ); break; case 7: /* mx_payload */ sqlite3_result_int( ctx, pCsr.nMxPayload ); break; } return SQLITE_OK; } static int statRowid( sqlite3_vtab_cursor pCursor, out sqlite_int64 pRowid ) { StatCursor pCsr = (StatCursor)pCursor; pRowid = pCsr.iPageno; return SQLITE_OK; } static sqlite3_module dbstat_module = new sqlite3_module( 0, /* iVersion */ statConnect, /* xCreate */ statConnect, /* xConnect */ statBestIndex, /* xBestIndex */ statDisconnect, /* xDisconnect */ statDisconnect, /* xDestroy */ statOpen, /* xOpen - open a cursor */ statClose, /* xClose - close a cursor */ statFilter, /* xFilter - configure scan constraints */ statNext, /* xNext - advance a cursor */ statEof, /* xEof - check for end of scan */ statColumn, /* xColumn - read data */ statRowid, /* xRowid - read data */ null, /* xUpdate */ null, /* xBegin */ null, /* xSync */ null, /* xCommit */ null, /* xRollback */ null, /* xFindMethod */ null /* xRename */ ); static int sqlite3_dbstat_register( sqlite3 db ) { sqlite3_create_module( db, "dbstat", dbstat_module, 0 ); return SQLITE_OK; } #endif #if SQLITE_TEST //#include static int test_dbstat( object clientData, Tcl_Interp interp, int objc, Tcl_Obj[] objv ) { #if SQLITE_OMIT_VIRTUALTABLE Tcl_AppendResult(interp, "dbstat not available because of " "SQLITE_OMIT_VIRTUALTABLE", (void*)0); return TCL.TCL_ERROR; #else //sqlite3 db; string zDb; Tcl_CmdInfo cmdInfo; if ( objc != 2 ) { TCL.Tcl_WrongNumArgs( interp, 1, objv, "DB" ); return TCL.TCL_ERROR; } zDb = TCL.Tcl_GetString( objv[1] ); if ( !TCL.Tcl_GetCommandInfo( interp, zDb, out cmdInfo ) ) { sqlite3 db = ( (SqliteDb)cmdInfo.objClientData ).db; sqlite3_dbstat_register( db ); } return TCL.TCL_OK; #endif } public static int SqlitetestStat_Init( Tcl_Interp interp ) { TCL.Tcl_CreateObjCommand( interp, "register_dbstat_vtab", test_dbstat, null, null ); return TCL.TCL_OK; } #endif } }