Enhancements to DBSTAT: (1) Make name=? queries efficient. (2) Add the
ability to aggregate results across each individual btree using the aggregate=TRUE constraint in the WHERE clause. FossilOrigin-Name: a48f6e17599d7ec794cfcd60858d6a69cf05caaae9321048b111fedd86a3b5b2
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
C Make\sthe\sresult\sof\ssqlite3_normalized_sql()\ssurvive\sits\sstatement\sbeing\sreprepared.
|
||||
D 2019-11-19T00:13:42.428
|
||||
C Enhancements\sto\sDBSTAT:\s\s(1)\sMake\sname=?\squeries\sefficient.\s\s(2)\sAdd\sthe\nability\sto\saggregate\sresults\sacross\seach\sindividual\sbtree\susing\sthe\naggregate=TRUE\sconstraint\sin\sthe\sWHERE\sclause.
|
||||
D 2019-11-19T21:22:16.359
|
||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
|
||||
@@ -476,7 +476,7 @@ F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e
|
||||
F src/ctime.c 1b0724e66f95f33b160b1af85caaf9cceb325d22abf39bd24df4f54a73982251
|
||||
F src/date.c e1d8ac7102f3f283e63e13867acb0efa33861cf34f0faf4cdbaf9fa7a1eb7041
|
||||
F src/dbpage.c 135eb3b5e74f9ef74bde5cec2571192c90c86984fa534c88bf4a055076fa19b7
|
||||
F src/dbstat.c c12833de69cb655751487d2c5a59607e36be1c58ba1f4bd536609909ad47b319
|
||||
F src/dbstat.c 6c407e549406c10fde9ac3987f6d734459205239ad370369bc5fcd683084a4fa
|
||||
F src/delete.c a5c59b9c0251cf7682bc52af0d64f09b1aefc6781a63592c8f1136f7b73c66e4
|
||||
F src/expr.c a138de8ae79628a73da2597617dbeafb0f083172be81d93ffa1cafa45161ee8c
|
||||
F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007
|
||||
@@ -1373,7 +1373,7 @@ F test/spellfix4.test 51c7c26514ade169855c66bcf130bd5acfb4d7fd090cc624645ab275ae
|
||||
F test/sqldiff1.test 28cd737cf1b0078b1ec1bbf425e674c47785835e
|
||||
F test/sqllimits1.test 264f4b0f941800ba139d25e33ee919c5d95fea06dfbe8ac291d6811a30984ca5
|
||||
F test/sqllog.test 6af6cb0b09f4e44e1917e06ce85be7670302517a
|
||||
F test/stat.test f8f1279ffffabe6df825723af18cc6e0ae70a893
|
||||
F test/stat.test 05669008edc5ed950e817c24e8c4b66840fda64d8c76bae27e5fc1bd23d95675
|
||||
F test/statfault.test f525a7bf633e50afd027700e9a486090684b1ac1
|
||||
F test/stmt.test 54ed2cc0764bf3e48a058331813c3dbd19fc1d0827c3d8369914a5d8f564ec75
|
||||
F test/stmtvtab1.test 6873dfb24f8e79cbb5b799b95c2e4349060eb7a3b811982749a84b359468e2d5
|
||||
@@ -1850,7 +1850,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
|
||||
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||
P 175c15008e9f19b8f6762c2fe4a545735128765081980eed01d5e46ca4acb500
|
||||
R 5a42019895e7f78a0e0e0e8c537226aa
|
||||
U mistachkin
|
||||
Z 324f0ce91b2d6545c1ae3061db5c270b
|
||||
P 4330f0795dbc2ab41dddd41d5979331fb9b78c477c66367c4be52f929531a45f 16fef3db063830884de46d53a289f637a7204fe84fcdee7ea81dbb8bca578952
|
||||
R 021ee7df1dd822f13f493e93c0d0159d
|
||||
T +closed 16fef3db063830884de46d53a289f637a7204fe84fcdee7ea81dbb8bca578952
|
||||
U drh
|
||||
Z 0ac7770b122892497299e1307cf84ac5
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
4330f0795dbc2ab41dddd41d5979331fb9b78c477c66367c4be52f929531a45f
|
||||
a48f6e17599d7ec794cfcd60858d6a69cf05caaae9321048b111fedd86a3b5b2
|
||||
+233
-111
@@ -12,7 +12,7 @@
|
||||
**
|
||||
** This file contains an implementation of the "dbstat" virtual table.
|
||||
**
|
||||
** The dbstat virtual table is used to extract low-level formatting
|
||||
** The dbstat virtual table is used to extract low-level storage
|
||||
** information from an SQLite database in order to implement the
|
||||
** "sqlite3_analyzer" utility. See the ../tool/spaceanal.tcl script
|
||||
** for an example implementation.
|
||||
@@ -56,27 +56,30 @@
|
||||
**
|
||||
** '/1c2/000/' // Left-most child of 451st child of root
|
||||
*/
|
||||
#define VTAB_SCHEMA \
|
||||
"CREATE TABLE xx( " \
|
||||
" name TEXT, /* Name of table or index */" \
|
||||
" path TEXT, /* Path to page from root */" \
|
||||
" pageno INTEGER, /* Page number */" \
|
||||
" pagetype TEXT, /* '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 */" \
|
||||
" pgoffset INTEGER, /* Offset of page in file */" \
|
||||
" pgsize INTEGER, /* Size of the page */" \
|
||||
" schema TEXT HIDDEN /* Database schema being analyzed */" \
|
||||
");"
|
||||
|
||||
static const char zDbstatSchema[] =
|
||||
"CREATE TABLE x("
|
||||
" name TEXT," /* 0 Name of table or index */
|
||||
" path TEXT," /* 1 Path to page from root (NULL for agg) */
|
||||
" pageno INTEGER," /* 2 Page number (page count for aggregates) */
|
||||
" pagetype TEXT," /* 3 'internal', 'leaf', 'overflow', or NULL */
|
||||
" ncell INTEGER," /* 4 Cells on page (0 for overflow) */
|
||||
" payload INTEGER," /* 5 Bytes of payload on this page */
|
||||
" unused INTEGER," /* 6 Bytes of unused space on this page */
|
||||
" mx_payload INTEGER," /* 7 Largest payload size of all cells */
|
||||
" pgoffset INTEGER," /* 8 Offset of page in file (NULL for agg) */
|
||||
" pgsize INTEGER," /* 9 Size of the page (sum for aggregate) */
|
||||
" schema TEXT HIDDEN," /* 10 Database schema being analyzed */
|
||||
" aggregate BOOLEAN HIDDEN" /* 11 aggregate info for each table */
|
||||
")"
|
||||
;
|
||||
|
||||
/* Forward reference to data structured used in this module */
|
||||
typedef struct StatTable StatTable;
|
||||
typedef struct StatCursor StatCursor;
|
||||
typedef struct StatPage StatPage;
|
||||
typedef struct StatCell StatCell;
|
||||
|
||||
/* Size information for a single cell within a btree page */
|
||||
struct StatCell {
|
||||
int nLocal; /* Bytes of local payload */
|
||||
u32 iChildPg; /* Child node (or 0 if this is a leaf) */
|
||||
@@ -86,10 +89,11 @@ struct StatCell {
|
||||
int iOvfl; /* Iterates through aOvfl[] */
|
||||
};
|
||||
|
||||
/* Size information for a single btree page */
|
||||
struct StatPage {
|
||||
u32 iPgno;
|
||||
DbPage *pPg;
|
||||
int iCell;
|
||||
u32 iPgno; /* Page number */
|
||||
DbPage *pPg; /* Page content */
|
||||
int iCell; /* Current cell */
|
||||
|
||||
char *zPath; /* Path to this page */
|
||||
|
||||
@@ -99,34 +103,38 @@ struct StatPage {
|
||||
int nUnused; /* Number of unused bytes on page */
|
||||
StatCell *aCell; /* Array of parsed cells */
|
||||
u32 iRightChildPg; /* Right-child page number (or 0) */
|
||||
int nMxPayload; /* Largest payload of any cell on this page */
|
||||
int nMxPayload; /* Largest payload of any cell on the page */
|
||||
};
|
||||
|
||||
/* The cursor for scanning the dbstat virtual table */
|
||||
struct StatCursor {
|
||||
sqlite3_vtab_cursor base;
|
||||
sqlite3_vtab_cursor base; /* base class. MUST BE FIRST! */
|
||||
sqlite3_stmt *pStmt; /* Iterates through set of root pages */
|
||||
int isEof; /* After pStmt has returned SQLITE_DONE */
|
||||
u8 isEof; /* After pStmt has returned SQLITE_DONE */
|
||||
u8 isAgg; /* Aggregate results for each table */
|
||||
int iDb; /* Schema used for this query */
|
||||
|
||||
StatPage aPage[32];
|
||||
StatPage aPage[32]; /* Pages in path to current page */
|
||||
int iPage; /* Current entry in aPage[] */
|
||||
|
||||
/* Values to return. */
|
||||
u32 iPageno; /* Value of 'pageno' column */
|
||||
char *zName; /* Value of 'name' column */
|
||||
char *zPath; /* Value of 'path' column */
|
||||
u32 iPageno; /* Value of 'pageno' column */
|
||||
char *zPagetype; /* Value of 'pagetype' column */
|
||||
int nPage; /* Number of pages in current btree */
|
||||
int nCell; /* Value of 'ncell' column */
|
||||
int nPayload; /* Value of 'payload' column */
|
||||
int nUnused; /* Value of 'unused' column */
|
||||
int nMxPayload; /* Value of 'mx_payload' column */
|
||||
i64 nUnused; /* Value of 'unused' column */
|
||||
i64 nPayload; /* Value of 'payload' column */
|
||||
i64 iOffset; /* Value of 'pgOffset' column */
|
||||
int szPage; /* Value of 'pgSize' column */
|
||||
i64 szPage; /* Value of 'pgSize' column */
|
||||
};
|
||||
|
||||
/* An instance of the DBSTAT virtual table */
|
||||
struct StatTable {
|
||||
sqlite3_vtab base;
|
||||
sqlite3 *db;
|
||||
sqlite3_vtab base; /* base class. MUST BE FIRST! */
|
||||
sqlite3 *db; /* Database connection that owns this vtab */
|
||||
int iDb; /* Index of database to analyze */
|
||||
};
|
||||
|
||||
@@ -135,7 +143,7 @@ struct StatTable {
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Connect to or create a statvfs virtual table.
|
||||
** Connect to or create a new DBSTAT virtual table.
|
||||
*/
|
||||
static int statConnect(
|
||||
sqlite3 *db,
|
||||
@@ -159,7 +167,7 @@ static int statConnect(
|
||||
}else{
|
||||
iDb = 0;
|
||||
}
|
||||
rc = sqlite3_declare_vtab(db, VTAB_SCHEMA);
|
||||
rc = sqlite3_declare_vtab(db, zDbstatSchema);
|
||||
if( rc==SQLITE_OK ){
|
||||
pTab = (StatTable *)sqlite3_malloc64(sizeof(StatTable));
|
||||
if( pTab==0 ) rc = SQLITE_NOMEM_BKPT;
|
||||
@@ -177,7 +185,7 @@ static int statConnect(
|
||||
}
|
||||
|
||||
/*
|
||||
** Disconnect from or destroy a statvfs virtual table.
|
||||
** Disconnect from or destroy the DBSTAT virtual table.
|
||||
*/
|
||||
static int statDisconnect(sqlite3_vtab *pVtab){
|
||||
sqlite3_free(pVtab);
|
||||
@@ -185,14 +193,20 @@ static int statDisconnect(sqlite3_vtab *pVtab){
|
||||
}
|
||||
|
||||
/*
|
||||
** There is no "best-index". This virtual table always does a linear
|
||||
** scan. However, a schema=? constraint should cause this table to
|
||||
** operate on a different database schema, so check for it.
|
||||
** Compute the best query strategy and return the result in idxNum.
|
||||
**
|
||||
** idxNum is normally 0, but will be 1 if a schema=? constraint exists.
|
||||
** idxNum-Bit Meaning
|
||||
** ---------- ----------------------------------------------
|
||||
** 0x01 There is a schema=? term in the WHERE clause
|
||||
** 0x02 There is a name=? term in the WHERE clause
|
||||
** 0x04 There is an aggregate=? term in the WHERE clause
|
||||
** 0x08 Output should be ordered by name and path
|
||||
*/
|
||||
static int statBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
|
||||
int i;
|
||||
int iSchema = -1;
|
||||
int iName = -1;
|
||||
int iAgg = -1;
|
||||
|
||||
/* Look for a valid schema=? constraint. If found, change the idxNum to
|
||||
** 1 and request the value of that constraint be sent to xFilter. And
|
||||
@@ -200,16 +214,43 @@ static int statBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
|
||||
** used.
|
||||
*/
|
||||
for(i=0; i<pIdxInfo->nConstraint; i++){
|
||||
if( pIdxInfo->aConstraint[i].iColumn!=10 ) continue;
|
||||
if( pIdxInfo->aConstraint[i].usable==0 ) return SQLITE_CONSTRAINT;
|
||||
if( pIdxInfo->aConstraint[i].op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue;
|
||||
pIdxInfo->idxNum = 1;
|
||||
pIdxInfo->estimatedCost = 1.0;
|
||||
pIdxInfo->aConstraintUsage[i].argvIndex = 1;
|
||||
pIdxInfo->aConstraintUsage[i].omit = 1;
|
||||
break;
|
||||
if( pIdxInfo->aConstraint[i].usable==0 ){
|
||||
/* Force DBSTAT table should always be the right-most table in a join */
|
||||
return SQLITE_CONSTRAINT;
|
||||
}
|
||||
switch( pIdxInfo->aConstraint[i].iColumn ){
|
||||
case 0: { /* name */
|
||||
iName = i;
|
||||
break;
|
||||
}
|
||||
case 10: { /* schema */
|
||||
iSchema = i;
|
||||
break;
|
||||
}
|
||||
case 11: { /* aggregate */
|
||||
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->aConstraintUsage[iName].omit = 1;
|
||||
pIdxInfo->idxNum |= 0x02;
|
||||
}
|
||||
if( iAgg>=0 ){
|
||||
pIdxInfo->aConstraintUsage[iAgg].argvIndex = ++i;
|
||||
pIdxInfo->aConstraintUsage[iAgg].omit = 1;
|
||||
pIdxInfo->idxNum |= 0x04;
|
||||
}
|
||||
pIdxInfo->estimatedCost = 1.0;
|
||||
|
||||
/* Records are always returned in ascending order of (name, path).
|
||||
** If this will satisfy the client, set the orderByConsumed flag so that
|
||||
@@ -227,13 +268,14 @@ static int statBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
|
||||
)
|
||||
){
|
||||
pIdxInfo->orderByConsumed = 1;
|
||||
pIdxInfo->idxNum |= 0x08;
|
||||
}
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Open a new statvfs cursor.
|
||||
** Open a new DBSTAT cursor.
|
||||
*/
|
||||
static int statOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
|
||||
StatTable *pTab = (StatTable *)pVTab;
|
||||
@@ -283,8 +325,18 @@ static void statResetCsr(StatCursor *pCsr){
|
||||
pCsr->isEof = 0;
|
||||
}
|
||||
|
||||
/* Resize the space-used counters inside of the cursor */
|
||||
static void statResetCounts(StatCursor *pCsr){
|
||||
pCsr->nCell = 0;
|
||||
pCsr->nMxPayload = 0;
|
||||
pCsr->nUnused = 0;
|
||||
pCsr->nPayload = 0;
|
||||
pCsr->szPage = 0;
|
||||
pCsr->nPage = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Close a statvfs cursor.
|
||||
** Close a DBSTAT cursor.
|
||||
*/
|
||||
static int statClose(sqlite3_vtab_cursor *pCursor){
|
||||
StatCursor *pCsr = (StatCursor *)pCursor;
|
||||
@@ -294,11 +346,15 @@ static int statClose(sqlite3_vtab_cursor *pCursor){
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
static void getLocalPayload(
|
||||
/*
|
||||
** For a single cell on a btree page, compute the number of bytes of
|
||||
** content (payload) stored on that page. That is to say, compute the
|
||||
** number of bytes of content not found on overflow pages.
|
||||
*/
|
||||
static int getLocalPayload(
|
||||
int nUsable, /* Usable bytes per page */
|
||||
u8 flags, /* Page flags */
|
||||
int nTotal, /* Total record (payload) size */
|
||||
int *pnLocal /* OUT: Bytes stored locally */
|
||||
int nTotal /* Total record (payload) size */
|
||||
){
|
||||
int nLocal;
|
||||
int nMinLocal;
|
||||
@@ -314,9 +370,12 @@ static void getLocalPayload(
|
||||
|
||||
nLocal = nMinLocal + (nTotal - nMinLocal) % (nUsable - 4);
|
||||
if( nLocal>nMaxLocal ) nLocal = nMinLocal;
|
||||
*pnLocal = nLocal;
|
||||
return nLocal;
|
||||
}
|
||||
|
||||
/* Populate the StatPage object with information about the all
|
||||
** cells found on the page currently under analysis.
|
||||
*/
|
||||
static int statDecodePage(Btree *pBt, StatPage *p){
|
||||
int nUnused;
|
||||
int iOff;
|
||||
@@ -387,7 +446,7 @@ static int statDecodePage(Btree *pBt, StatPage *p){
|
||||
iOff += sqlite3GetVarint(&aData[iOff], &dummy);
|
||||
}
|
||||
if( nPayload>(u32)p->nMxPayload ) p->nMxPayload = nPayload;
|
||||
getLocalPayload(nUsable, p->flags, nPayload, &nLocal);
|
||||
nLocal = getLocalPayload(nUsable, p->flags, nPayload);
|
||||
if( nLocal<0 ) goto statPageIsCorrupt;
|
||||
pCell->nLocal = nLocal;
|
||||
assert( nPayload>=(u32)nLocal );
|
||||
@@ -437,23 +496,25 @@ static void statSizeAndOffset(StatCursor *pCsr){
|
||||
sqlite3_file *fd;
|
||||
sqlite3_int64 x[2];
|
||||
|
||||
/* The default page size and offset */
|
||||
pCsr->szPage = sqlite3BtreeGetPageSize(pBt);
|
||||
pCsr->iOffset = (i64)pCsr->szPage * (pCsr->iPageno - 1);
|
||||
|
||||
/* If connected to a ZIPVFS backend, override the page size and
|
||||
** offset with actual values obtained from ZIPVFS.
|
||||
/* If connected to a ZIPVFS backend, find the page size and
|
||||
** offset from ZIPVFS.
|
||||
*/
|
||||
fd = sqlite3PagerFile(pPager);
|
||||
x[0] = pCsr->iPageno;
|
||||
if( sqlite3OsFileControl(fd, 230440, &x)==SQLITE_OK ){
|
||||
pCsr->iOffset = x[0];
|
||||
pCsr->szPage = (int)x[1];
|
||||
pCsr->szPage += x[1];
|
||||
}else{
|
||||
/* Not ZIPVFS: The default page size and offset */
|
||||
pCsr->szPage += sqlite3BtreeGetPageSize(pBt);
|
||||
pCsr->iOffset = (i64)pCsr->szPage * (pCsr->iPageno - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Move a statvfs cursor to the next entry in the file.
|
||||
** Move a DBSTAT cursor to the next entry. Normally, the next
|
||||
** entry will be the next page, but in aggregated mode (pCsr->isAgg!=0),
|
||||
** the next entry is the next btree.
|
||||
*/
|
||||
static int statNext(sqlite3_vtab_cursor *pCursor){
|
||||
int rc;
|
||||
@@ -469,6 +530,8 @@ static int statNext(sqlite3_vtab_cursor *pCursor){
|
||||
|
||||
statNextRestart:
|
||||
if( pCsr->aPage[0].pPg==0 ){
|
||||
/* Start measuring space on the next btree */
|
||||
statResetCounts(pCsr);
|
||||
rc = sqlite3_step(pCsr->pStmt);
|
||||
if( rc==SQLITE_ROW ){
|
||||
int nPage;
|
||||
@@ -481,44 +544,47 @@ statNextRestart:
|
||||
rc = sqlite3PagerGet(pPager, iRoot, &pCsr->aPage[0].pPg, 0);
|
||||
pCsr->aPage[0].iPgno = iRoot;
|
||||
pCsr->aPage[0].iCell = 0;
|
||||
pCsr->aPage[0].zPath = z = sqlite3_mprintf("/");
|
||||
if( !pCsr->isAgg ){
|
||||
pCsr->aPage[0].zPath = z = sqlite3_mprintf("/");
|
||||
if( z==0 ) rc = SQLITE_NOMEM_BKPT;
|
||||
}
|
||||
pCsr->iPage = 0;
|
||||
if( z==0 ) rc = SQLITE_NOMEM_BKPT;
|
||||
pCsr->nPage = 1;
|
||||
}else{
|
||||
pCsr->isEof = 1;
|
||||
return sqlite3_reset(pCsr->pStmt);
|
||||
}
|
||||
}else{
|
||||
|
||||
/* Page p itself has already been visited. */
|
||||
/* Continue analyzing the btree previously started */
|
||||
StatPage *p = &pCsr->aPage[pCsr->iPage];
|
||||
|
||||
if( !pCsr->isAgg ) statResetCounts(pCsr);
|
||||
while( p->iCell<p->nCell ){
|
||||
StatCell *pCell = &p->aCell[p->iCell];
|
||||
if( pCell->iOvfl<pCell->nOvfl ){
|
||||
int nUsable;
|
||||
while( pCell->iOvfl<pCell->nOvfl ){
|
||||
int nUsable, iOvfl;
|
||||
sqlite3BtreeEnter(pBt);
|
||||
nUsable = sqlite3BtreeGetPageSize(pBt) -
|
||||
sqlite3BtreeGetReserveNoMutex(pBt);
|
||||
sqlite3BtreeLeave(pBt);
|
||||
pCsr->zName = (char *)sqlite3_column_text(pCsr->pStmt, 0);
|
||||
pCsr->iPageno = pCell->aOvfl[pCell->iOvfl];
|
||||
pCsr->zPagetype = "overflow";
|
||||
pCsr->nCell = 0;
|
||||
pCsr->nMxPayload = 0;
|
||||
pCsr->zPath = z = 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++;
|
||||
pCsr->nPage++;
|
||||
statSizeAndOffset(pCsr);
|
||||
return z==0 ? SQLITE_NOMEM_BKPT : SQLITE_OK;
|
||||
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++;
|
||||
@@ -526,8 +592,13 @@ statNextRestart:
|
||||
|
||||
if( !p->iRightChildPg || p->iCell>p->nCell ){
|
||||
statClearPage(p);
|
||||
if( pCsr->iPage==0 ) return statNext(pCursor);
|
||||
pCsr->iPage--;
|
||||
if( pCsr->iPage>0 ){
|
||||
pCsr->iPage--;
|
||||
}else if( pCsr->isAgg ){
|
||||
/* label-statNext-done: When computing aggregate space usage over
|
||||
** an entire btree, this is the exit point from this function */
|
||||
return SQLITE_OK;
|
||||
}
|
||||
goto statNextRestart; /* Tail recursion */
|
||||
}
|
||||
pCsr->iPage++;
|
||||
@@ -543,10 +614,13 @@ statNextRestart:
|
||||
p[1].iPgno = p->aCell[p->iCell].iChildPg;
|
||||
}
|
||||
rc = sqlite3PagerGet(pPager, p[1].iPgno, &p[1].pPg, 0);
|
||||
pCsr->nPage++;
|
||||
p[1].iCell = 0;
|
||||
p[1].zPath = z = sqlite3_mprintf("%s%.3x/", p->zPath, p->iCell);
|
||||
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( z==0 ) rc = SQLITE_NOMEM_BKPT;
|
||||
}
|
||||
|
||||
|
||||
@@ -576,16 +650,23 @@ statNextRestart:
|
||||
pCsr->zPagetype = "corrupted";
|
||||
break;
|
||||
}
|
||||
pCsr->nCell = p->nCell;
|
||||
pCsr->nUnused = p->nUnused;
|
||||
pCsr->nMxPayload = p->nMxPayload;
|
||||
pCsr->zPath = z = sqlite3_mprintf("%s", p->zPath);
|
||||
if( z==0 ) rc = SQLITE_NOMEM_BKPT;
|
||||
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;
|
||||
pCsr->nPayload += nPayload;
|
||||
|
||||
/* If computing aggregate space usage by btree, continue with the
|
||||
** next page. The loop will exit via the return at label-statNext-done
|
||||
*/
|
||||
if( pCsr->isAgg ) goto statNextRestart;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,6 +678,10 @@ static int statEof(sqlite3_vtab_cursor *pCursor){
|
||||
return pCsr->isEof;
|
||||
}
|
||||
|
||||
/* Initialize a cursor according to the query plan idxNum using the
|
||||
** arguments in argv[0]. See statBestIndex() for a description of the
|
||||
** meaning of the bits in idxNum.
|
||||
*/
|
||||
static int statFilter(
|
||||
sqlite3_vtab_cursor *pCursor,
|
||||
int idxNum, const char *idxStr,
|
||||
@@ -604,11 +689,18 @@ static int statFilter(
|
||||
){
|
||||
StatCursor *pCsr = (StatCursor *)pCursor;
|
||||
StatTable *pTab = (StatTable*)(pCursor->pVtab);
|
||||
char *zSql;
|
||||
int rc = SQLITE_OK;
|
||||
sqlite3_str *pSql; /* Query of btrees to analyze */
|
||||
char *zSql; /* String value of pSql */
|
||||
int iArg = 0; /* Count of argv[] parameters used so far */
|
||||
int rc = SQLITE_OK; /* Result of this operation */
|
||||
const char *zName = 0; /* Only provide analysis of this table */
|
||||
|
||||
if( idxNum==1 ){
|
||||
const char *zDbase = (const char*)sqlite3_value_text(argv[0]);
|
||||
statResetCsr(pCsr);
|
||||
sqlite3_finalize(pCsr->pStmt);
|
||||
pCsr->pStmt = 0;
|
||||
if( idxNum & 0x01 ){
|
||||
/* schema=? constraint is present. Get its value */
|
||||
const char *zDbase = (const char*)sqlite3_value_text(argv[iArg++]);
|
||||
pCsr->iDb = sqlite3FindDbName(pTab->db, zDbase);
|
||||
if( pCsr->iDb<0 ){
|
||||
sqlite3_free(pCursor->pVtab->zErrMsg);
|
||||
@@ -618,15 +710,31 @@ static int statFilter(
|
||||
}else{
|
||||
pCsr->iDb = pTab->iDb;
|
||||
}
|
||||
statResetCsr(pCsr);
|
||||
sqlite3_finalize(pCsr->pStmt);
|
||||
pCsr->pStmt = 0;
|
||||
zSql = sqlite3_mprintf(
|
||||
"SELECT 'sqlite_master' AS name, 1 AS rootpage, 'table' AS type"
|
||||
" UNION ALL "
|
||||
"SELECT name, rootpage, type"
|
||||
" FROM \"%w\".sqlite_master WHERE rootpage!=0"
|
||||
" ORDER BY name", pTab->db->aDb[pCsr->iDb].zDbSName);
|
||||
if( idxNum & 0x02 ){
|
||||
/* name=? constraint is present */
|
||||
zName = (const char*)sqlite3_value_text(argv[iArg++]);
|
||||
}
|
||||
if( idxNum & 0x04 ){
|
||||
/* aggregate=? constraint is present */
|
||||
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_master' AS name,1 AS rootpage,'table' AS type"
|
||||
" UNION ALL "
|
||||
"SELECT name,rootpage,type"
|
||||
" FROM \"%w\".sqlite_master 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{
|
||||
@@ -651,13 +759,21 @@ static int statColumn(
|
||||
sqlite3_result_text(ctx, pCsr->zName, -1, SQLITE_TRANSIENT);
|
||||
break;
|
||||
case 1: /* path */
|
||||
sqlite3_result_text(ctx, pCsr->zPath, -1, SQLITE_TRANSIENT);
|
||||
if( !pCsr->isAgg ){
|
||||
sqlite3_result_text(ctx, pCsr->zPath, -1, SQLITE_TRANSIENT);
|
||||
}
|
||||
break;
|
||||
case 2: /* pageno */
|
||||
sqlite3_result_int64(ctx, pCsr->iPageno);
|
||||
if( pCsr->isAgg ){
|
||||
sqlite3_result_int64(ctx, pCsr->nPage);
|
||||
}else{
|
||||
sqlite3_result_int64(ctx, pCsr->iPageno);
|
||||
}
|
||||
break;
|
||||
case 3: /* pagetype */
|
||||
sqlite3_result_text(ctx, pCsr->zPagetype, -1, SQLITE_STATIC);
|
||||
if( !pCsr->isAgg ){
|
||||
sqlite3_result_text(ctx, pCsr->zPagetype, -1, SQLITE_STATIC);
|
||||
}
|
||||
break;
|
||||
case 4: /* ncell */
|
||||
sqlite3_result_int(ctx, pCsr->nCell);
|
||||
@@ -672,17 +788,23 @@ static int statColumn(
|
||||
sqlite3_result_int(ctx, pCsr->nMxPayload);
|
||||
break;
|
||||
case 8: /* pgoffset */
|
||||
sqlite3_result_int64(ctx, pCsr->iOffset);
|
||||
if( !pCsr->isAgg ){
|
||||
sqlite3_result_int64(ctx, pCsr->iOffset);
|
||||
}
|
||||
break;
|
||||
case 9: /* pgsize */
|
||||
sqlite3_result_int(ctx, pCsr->szPage);
|
||||
break;
|
||||
default: { /* schema */
|
||||
case 10: { /* schema */
|
||||
sqlite3 *db = sqlite3_context_db_handle(ctx);
|
||||
int iDb = pCsr->iDb;
|
||||
sqlite3_result_text(ctx, db->aDb[iDb].zDbSName, -1, SQLITE_STATIC);
|
||||
break;
|
||||
}
|
||||
default: { /* aggregate */
|
||||
sqlite3_result_int(ctx, pCsr->isAgg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
+29
-2
@@ -108,7 +108,7 @@ do_execsql_test stat-2.1 {
|
||||
INSERT INTO t3 SELECT a_string(110+rowid), a_string(221+rowid) FROM t3
|
||||
ORDER BY rowid;
|
||||
SELECT name, path, pageno, pagetype, ncell, payload, unused, mx_payload
|
||||
FROM stat WHERE name != 'sqlite_master';
|
||||
FROM stat WHERE name != 'sqlite_master' ORDER BY name;
|
||||
} [list \
|
||||
sqlite_autoindex_t3_1 / 3 internal 3 368 623 125 \
|
||||
sqlite_autoindex_t3_1 /000/ 8 leaf 8 946 46 123 \
|
||||
@@ -134,6 +134,14 @@ do_execsql_test stat-2.1 {
|
||||
t3 /00f/ 23 leaf 2 738 268 370 \
|
||||
]
|
||||
|
||||
do_execsql_test stat-2.1agg {
|
||||
SELECT * FROM dbstat WHERE aggregate=TRUE ORDER BY name;
|
||||
} [list \
|
||||
sqlite_autoindex_t3_1 {} 5 {} 32 3898 1065 132 {} 5120 \
|
||||
sqlite_master {} 1 {} 2 84 824 49 {} 1024 \
|
||||
t3 {} 17 {} 47 11188 5815 370 {} 17408 \
|
||||
]
|
||||
|
||||
# With every index entry overflowing, make sure no pages are missed
|
||||
# (other than the locking page which is 64 in this test build.)
|
||||
#
|
||||
@@ -150,7 +158,7 @@ do_execsql_test stat-3.1 {
|
||||
CREATE INDEX i4 ON t4(x);
|
||||
INSERT INTO t4(rowid, x) VALUES(2, a_string(7777));
|
||||
SELECT name, path, pageno, pagetype, ncell, payload, unused, mx_payload
|
||||
FROM stat WHERE name != 'sqlite_master';
|
||||
FROM stat WHERE name != 'sqlite_master' ORDER BY name;
|
||||
} [list \
|
||||
i4 / 3 leaf 1 103 905 7782 \
|
||||
i4 /000+000000 4 overflow 0 1020 0 0 \
|
||||
@@ -171,6 +179,15 @@ do_execsql_test stat-3.1 {
|
||||
t4 /000+000006 18 overflow 0 1020 0 0 \
|
||||
]
|
||||
|
||||
do_execsql_test stat-3.2 {
|
||||
SELECT *, '|' FROM dbstat WHERE aggregate=TRUE ORDER BY name;
|
||||
} [list \
|
||||
i4 {} 9 {} 1 7782 1386 7782 {} 9216 | \
|
||||
sqlite_master {} 1 {} 2 74 834 40 {} 1024 | \
|
||||
t4 {} 8 {} 1 7780 367 7780 {} 8192 | \
|
||||
]
|
||||
|
||||
|
||||
do_execsql_test stat-4.1 {
|
||||
CREATE TABLE t5(x);
|
||||
CREATE INDEX i5 ON t5(x);
|
||||
@@ -201,6 +218,16 @@ do_execsql_test stat-5.1 {
|
||||
t1 /001+000000 4 overflow 0 1020 0 0 \
|
||||
]
|
||||
|
||||
do_execsql_test stat-5.20 {
|
||||
SELECT name, quote(path), pageno, quote(pagetype), ncell, payload,
|
||||
unused, mx_payload, '|' FROM dbstat('main',1);
|
||||
} {sqlite_master NULL 1 NULL 1 34 878 34 | tx NULL 1 NULL 0 0 1016 0 |}
|
||||
do_execsql_test stat-5.21 {
|
||||
SELECT name, quote(path), pageno, quote(pagetype), ncell, payload,
|
||||
unused, mx_payload, '|' FROM dbstat('aux1',1);
|
||||
} {sqlite_master NULL 1 NULL 1 34 878 34 | t1 NULL 3 NULL 2 3033 5 1517 |}
|
||||
|
||||
|
||||
do_catchsql_test stat-6.1 {
|
||||
CREATE VIRTUAL TABLE temp.s2 USING dbstat(mainx);
|
||||
} {1 {no such database: mainx}}
|
||||
|
||||
Reference in New Issue
Block a user