blob: 3bf180aeaf2e9b05605b28812a9e4e69d5f63a88 [file] [edit]
/*
** 2026-04-13
**
** 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.
**
******************************************************************************
**
** Partial reimplement of the sqlite3_analyzer utility program as
** loadable SQL function.
*/
#include "sqlite3ext.h"
SQLITE_EXTENSION_INIT1
#include <assert.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
/*
** State information for the analysis
*/
typedef struct Analysis Analysis;
struct Analysis {
sqlite3 *db; /* Database connection */
sqlite3_context *context; /* SQL function context */
sqlite3_str *pOut; /* Write output here */
char *zSU; /* Name of the temp.space_used table */
const char *zSchema; /* Schema to be analyzed */
};
/*
** Free all resources that the Analysis object references and
** reset the Analysis object.
**
** Call this routine multiple times on the same Analysis object
** is a harmless no-op, as long as the memory for the object itself
** has not been freed.
*/
static void analysisReset(Analysis *p){
if( p->zSU ){
char *zSql = sqlite3_mprintf("DROP TABLE temp.%s;", p->zSU);
if( zSql ){
sqlite3_exec(p->db, zSql, 0, 0, 0);
sqlite3_free(zSql);
}
}
sqlite3_str_free(p->pOut);
sqlite3_free(p->zSU);
memset(p, 0, sizeof(*p));
}
/*
** Report an error using formatted text. If zFormat==NULL then report
** an OOM error.
*/
static void analysisError(Analysis *p, const char *zFormat, ...){
char *zErr;
if( zFormat ){
va_list ap;
va_start(ap, zFormat);
zErr = sqlite3_vmprintf(zFormat, ap);
va_end(ap);
}else{
zErr = 0;
}
if( zErr==0 ){
sqlite3_result_error_nomem(p->context);
}else{
sqlite3_result_error(p->context, zErr, -1);
sqlite3_free(zErr);
}
analysisReset(p);
}
/*
** Prepare and return an SQL statement.
*/
static sqlite3_stmt *analysisVPrep(Analysis *p, const char *zFmt, va_list ap){
char *zSql;
int rc;
sqlite3_stmt *pStmt = 0;
zSql = sqlite3_vmprintf(zFmt, ap);
if( zSql==0 ){ analysisError(p,0); return 0; }
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
if( rc ){
analysisError(p, "SQL parse error: %s\nOriginal SQL: %s",
sqlite3_errmsg(p->db), zSql);
sqlite3_finalize(pStmt);
analysisReset(p);
pStmt = 0;
}
sqlite3_free(zSql);
return pStmt;
}
static sqlite3_stmt *analysisPrepare(Analysis *p, const char *zFormat, ...){
va_list ap;
sqlite3_stmt *pStmt = 0;
va_start(ap, zFormat);
pStmt = analysisVPrep(p,zFormat,ap);
va_end(ap);
return pStmt;
}
/*
** If rc is something other than SQLITE_DONE or SQLITE_OK, then report
** an error and return true.
**
** If rc is SQLITE_DONE or SQLITE_OK, then return false.
**
** The prepared statement is closed in either case.
*/
static int analysisStmtFinish(Analysis *p, int rc, sqlite3_stmt *pStmt){
if( rc==SQLITE_DONE ){
rc = SQLITE_OK;
}
if( rc!=SQLITE_OK || (rc = sqlite3_reset(pStmt))!=SQLITE_OK ){
analysisError(p, "SQL run-time error: %s\nOriginal SQL: %s",
sqlite3_errmsg(p->db), sqlite3_sql(pStmt));
analysisReset(p);
}
sqlite3_finalize(pStmt);
return rc;
}
/*
** Run SQL. Return the number of errors.
*/
static int analysisSql(Analysis *p, const char *zFormat, ...){
va_list ap;
int rc;
sqlite3_stmt *pStmt = 0;
va_start(ap, zFormat);
pStmt = analysisVPrep(p,zFormat,ap);
va_end(ap);
if( pStmt==0 ) return 1;
while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){}
if( rc==SQLITE_DONE ){
rc = SQLITE_OK;
}else{
analysisError(p, "SQL run-time error: %s\nOriginal SQL: %s",
sqlite3_errmsg(p->db), sqlite3_sql(pStmt));
analysisReset(p);
}
sqlite3_finalize(pStmt);
return rc;
}
/*
** Run an SQL query that returns an integer. Write that integer
** into *piRes. Return the number of errors.
*/
static int analysisSqlInt(
Analysis *p,
sqlite3_int64 *piRes,
const char *zFormat, ...
){
va_list ap;
int rc;
sqlite3_stmt *pStmt = 0;
va_start(ap, zFormat);
pStmt = analysisVPrep(p,zFormat,ap);
va_end(ap);
if( pStmt==0 ) return 1;
rc = sqlite3_step(pStmt);
if( rc==SQLITE_ROW ){
*piRes = sqlite3_column_int64(pStmt, 0);
rc = SQLITE_OK;
}else if( rc==SQLITE_DONE ){
rc = SQLITE_OK;
}else{
if( p->db ){
/* p->db is NULL if there was some prior error */
analysisError(p, "SQL run-time error: %s\nOriginal SQL: %s",
sqlite3_errmsg(p->db), sqlite3_sql(pStmt));
}
analysisReset(p);
}
sqlite3_finalize(pStmt);
return rc;
}
/*
** Add to the output a title line that contains the text determined
** by the format string. If the output is initially empty, begin
** the title line with "/" so that it forms the beginning of a C-style
** comment. Otherwise begin with a new-line. Always finish with a
** newline.
*/
static void analysisTitle(Analysis *p, const char *zFormat, ...){
char *zFirst;
char *zTitle;
size_t nTitle;
va_list ap;
va_start(ap, zFormat);
zTitle = sqlite3_vmprintf(zFormat, ap);
va_end(ap);
if( zTitle==0 ){
analysisError(p, 0);
return;
}
zFirst = sqlite3_str_length(p->pOut)==0 ? "/" : "\n*";
nTitle = strlen(zTitle);
if( nTitle>=75 ){
sqlite3_str_appendf(p->pOut, "%s** %z\n\n", zFirst, zTitle);
}else{
int nExtra = 74 - (int)nTitle;
sqlite3_str_appendf(p->pOut, "%s** %z %.*c\n\n", zFirst, zTitle,
nExtra, '*');
}
}
/*
** Add an output line that begins with the zDesc text extended out to
** 50 columns with "." characters, and followed by whatever text is
** described by zFormat.
*/
static void analysisLine(
Analysis *p, /* Analysis context */
const char *zDesc, /* Description */
const char *zFormat, /* Argument to the description */
...
){
char *zTxt;
size_t nDesc;
va_list ap;
va_start(ap, zFormat);
zTxt = sqlite3_vmprintf(zFormat, ap);
va_end(ap);
if( zTxt==0 ){
analysisError(p, 0);
return;
}
nDesc = strlen(zDesc);
if( nDesc>=50 ){
sqlite3_str_appendf(p->pOut, "%s %z", zDesc, zTxt);
}else{
int nExtra = 50 - (int)nDesc;
sqlite3_str_appendf(p->pOut, "%s%.*c %z", zDesc, nExtra, '.', zTxt);
}
}
/*
** Write a percentage into the output. The number written should show
** two or three significant digits, with the decimal point being the fourth
** character.
*/
static void analysisPercent(Analysis *p, double r){
char zNum[100];
char *zDP;
int nLeadingDigit;
int sz;
sqlite3_snprintf(sizeof(zNum)-5, zNum, r>=10.0 ? "%.3g" :"%.2g", r);
sz = (int)strlen(zNum);
zDP = strchr(zNum, '.');
if( zDP==0 ){
memcpy(zNum+sz,".0",3);
nLeadingDigit = sz;
sz += 2;
}else{
nLeadingDigit = (int)(zDP - zNum);
}
if( nLeadingDigit<3 ){
sqlite3_str_appendchar(p->pOut, 3-nLeadingDigit, ' ');
}
sqlite3_str_append(p->pOut, zNum, sz);
sqlite3_str_append(p->pOut, "%\n", 2);
}
/*
** Create a subreport on a subset of tables and/or indexes.
**
** The title if the subreport is given by zTitle. zWhere is
** a boolean expression that can go in the WHERE clause to select
** the relevant rows of the s.zSU table.
*/
static int analysisSubreport(
Analysis *p, /* Analysis context */
char *zTitle, /* Title for this subreport */
char *zWhere, /* WHERE clause for this subreport */
sqlite3_int64 pgsz, /* Database page size */
sqlite3_int64 nPage /* Number of pages in entire database */
){
sqlite3_stmt *pStmt; /* Statement to query p->zSU */
sqlite3_int64 nentry; /* Number of btree entires */
sqlite3_int64 payload; /* Payload in bytes */
sqlite3_int64 ovfl_payload; /* overflow payload in bytes */
sqlite3_int64 mx_payload; /* largest individual payload */
sqlite3_int64 ovfl_cnt; /* Number entries using overflow */
sqlite3_int64 leaf_pages; /* Leaf pages */
sqlite3_int64 int_pages; /* internal pages */
sqlite3_int64 ovfl_pages; /* overflow pages */
sqlite3_int64 leaf_unused; /* unused bytes on leaf pages */
sqlite3_int64 int_unused; /* unused bytes on internal pages */
sqlite3_int64 ovfl_unused; /* unused bytes on overflow pages */
sqlite3_int64 int_cell; /* B-tree entries on internal pages */
sqlite3_int64 depth; /* btree depth */
sqlite3_int64 cnt; /* Number of s.zSU entries that match */
sqlite3_int64 storage; /* Total bytes */
sqlite3_int64 total_pages; /* Total page count */
sqlite3_int64 total_unused; /* Total unused bytes */
sqlite3_int64 total_meta; /* Total metadata */
int rc;
if( zTitle==0 || zWhere==0 ){
analysisError(p, 0);
return SQLITE_NOMEM;
}
pStmt = analysisPrepare(p,
"SELECT\n"
" sum(if(is_without_rowid OR is_index,nentry,leaf_entries)),\n" /* 0 */
" sum(payload),\n" /* 1 */
" sum(ovfl_payload),\n" /* 2 */
" max(mx_payload),\n" /* 3 */
" sum(ovfl_cnt),\n" /* 4 */
" sum(leaf_pages),\n" /* 5 */
" sum(int_pages),\n" /* 6 */
" sum(ovfl_pages),\n" /* 7 */
" sum(leaf_unused),\n" /* 8 */
" sum(int_unused),\n" /* 9 */
" sum(ovfl_unused),\n" /* 10 */
" max(depth),\n" /* 11 */
" count(*),\n" /* 12 */
" sum(int_entries)\n" /* 13 */
" FROM temp.%s WHERE %s",
p->zSU, zWhere);
if( pStmt==0 ) return 1;
rc = sqlite3_step(pStmt);
if( rc==SQLITE_ROW ){
analysisTitle(p, zTitle);
nentry = sqlite3_column_int64(pStmt, 0);
payload = sqlite3_column_int64(pStmt, 1);
ovfl_payload = sqlite3_column_int64(pStmt, 2);
mx_payload = sqlite3_column_int64(pStmt, 3);
ovfl_cnt = sqlite3_column_int64(pStmt, 4);
leaf_pages = sqlite3_column_int64(pStmt, 5);
int_pages = sqlite3_column_int64(pStmt, 6);
ovfl_pages = sqlite3_column_int64(pStmt, 7);
leaf_unused = sqlite3_column_int64(pStmt, 8);
int_unused = sqlite3_column_int64(pStmt, 9);
ovfl_unused = sqlite3_column_int64(pStmt, 10);
depth = sqlite3_column_int64(pStmt, 11);
cnt = sqlite3_column_int64(pStmt, 12);
int_cell = sqlite3_column_int64(pStmt, 13);
rc = SQLITE_DONE;
total_pages = leaf_pages + int_pages + ovfl_pages;
analysisLine(p, "Percentage of total database", "%.3g%%\n",
(total_pages*100.0)/(double)nPage);
analysisLine(p, "Number of entries", "%lld\n", nentry);
storage = total_pages*pgsz;
analysisLine(p, "Bytes of storage consumed", "%lld\n", storage);
analysisLine(p, "Bytes of payload", "%-11lld ", payload);
analysisPercent(p, payload*100.0/(double)storage);
if( ovfl_cnt>0 ){
analysisLine(p, "Bytes of payload in overflow","%-11lld ",ovfl_payload);
analysisPercent(p, ovfl_payload*100.0/(double)payload);
}
total_unused = leaf_unused + int_unused + ovfl_unused;
total_meta = storage - payload - total_unused;
analysisLine(p, "Bytes of metadata","%-11lld ", total_meta);
analysisPercent(p, total_meta*100.0/(double)storage);
if( cnt==1 ){
analysisLine(p, "B-tree depth", "%lld\n", depth);
if( int_cell>1 ){
analysisLine(p, "Average fanout", "%.1f\n",
(double)(int_cell+int_pages)/(double)int_pages);
}
}
if( nentry>0 ){
analysisLine(p, "Average payload per entry", "%.1f\n",
(double)payload/(double)nentry);
analysisLine(p, "Average unused bytes per entry", "%.1f\n",
(double)total_unused/(double)nentry);
analysisLine(p, "Average metadata per entry", "%.1f\n",
(double)total_meta/(double)nentry);
}
analysisLine(p, "Maximum single-entry payload", "%lld\n", mx_payload);
if( nentry>0 ){
analysisLine(p, "Entries that use overflow", "%-11lld ", ovfl_cnt);
analysisPercent(p, ovfl_cnt*100.0/(double)nentry);
}
if( int_pages>0 ){
analysisLine(p, "Index pages used", "%lld\n", int_pages);
}
analysisLine(p, "Primary pages used", "%lld\n", leaf_pages);
if( ovfl_cnt ){
analysisLine(p, "Overflow pages used", "%lld\n", ovfl_pages);
}
analysisLine(p, "Total pages used", "%lld\n", total_pages);
if( int_pages>0 ){
analysisLine(p, "Unused bytes on index pages", "%lld\n", int_unused);
}
analysisLine(p, "Unused bytes on primary pages", "%lld\n", leaf_unused);
if( ovfl_cnt ){
analysisLine(p, "Unused bytes on overflow pages", "%lld\n", ovfl_unused);
}
analysisLine(p, "Unused bytes on all pages", "%-11lld ", total_unused);
analysisPercent(p, total_unused*100.0/(double)storage);
}
return analysisStmtFinish(p, rc, pStmt);
}
/*
** SQL Function: analyze(SCHEMA)
**
** Analyze the database schema named in the argument. Return text
** containing the analysis.
*/
static void analyzeFunc(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
int rc;
sqlite3_stmt *pStmt;
int n;
sqlite3_int64 i64;
sqlite3_int64 pgsz;
sqlite3_int64 nPage;
sqlite3_int64 nPageInUse;
sqlite3_int64 nFreeList;
sqlite3_int64 nIndex;
sqlite3_int64 nWORowid;
Analysis s;
sqlite3_uint64 r[2];
memset(&s, 0, sizeof(s));
s.db = sqlite3_context_db_handle(context);
s.context = context;
s.pOut = sqlite3_str_new(0);
if( sqlite3_str_errcode(s.pOut) ){
analysisError(&s, 0);
return;
}
s.zSchema = (const char*)sqlite3_value_text(argv[0]);
if( s.zSchema==0 ){
s.zSchema = "main";
}else if( sqlite3_strlike("temp",s.zSchema,0)==0 ){
analysisReset(&s);
sqlite3_result_text(context, "cannot analyze \"temp\"",-1,SQLITE_STATIC);
return;
}
i64 = 0;
rc = analysisSqlInt(&s,&i64,"SELECT 1 FROM pragma_database_list"
" WHERE name=%Q COLLATE nocase",s.zSchema);
if( rc || i64==0 ){
analysisReset(&s);
sqlite3_result_text(context,"no such database",-1,SQLITE_STATIC);
return;
}
sqlite3_randomness(sizeof(r), &r);
s.zSU = sqlite3_mprintf("analysis%016llx%016llx", r[0], r[1]);
if( s.zSU==0 ){ analysisError(&s, 0); return; }
/* The s.zSU table contains the data used for the analysis.
** The table name contains 128-bits of randomness to avoid
** collisions with preexisting tables in temp.
*/
rc = analysisSql(&s,
"CREATE TABLE temp.%s(\n"
" name text, -- A table or index\n"
" tblname text, -- Table that owns name\n"
" is_index boolean, -- TRUE if it is an index\n"
" is_without_rowid boolean, -- TRUE if WITHOUT ROWID table\n"
" nentry int, -- Number of entries in the BTree\n"
" leaf_entries int, -- Number of leaf entries\n"
" depth int, -- Depth of the b-tree\n"
" payload int, -- Total data stored in this table/index\n"
" ovfl_payload int, -- Total data stored on overflow pages\n"
" ovfl_cnt int, -- Number of entries that use overflow\n"
" mx_payload int, -- Maximum payload size\n"
" int_pages int, -- Interior pages used\n"
" leaf_pages int, -- Leaf pages used\n"
" ovfl_pages int, -- Overflow pages used\n"
" int_unused int, -- Unused bytes on interior pages\n"
" leaf_unused int, -- Unused bytes on primary pages\n"
" ovfl_unused int, -- Unused bytes on overflow pages\n"
" int_entries int -- Btree cells on internal pages\n"
");",
s.zSU
);
if( rc ) return;
/* Populate the s.zSU table
*/
rc = analysisSql(&s,
"WITH\n"
" allidx(idxname) AS (\n"
" SELECT name FROM \"%w\".sqlite_schema WHERE type='index'\n"
" ),\n"
" allobj(allname,tblname,isidx,isworowid) AS (\n"
" SELECT 'sqlite_schema',\n"
" 'sqlite_schema',\n"
" 0,\n"
" 0\n"
" UNION ALL\n"
" SELECT name,\n"
" tbl_name,\n"
" type='index',\n"
" EXISTS(SELECT 1\n"
" FROM pragma_index_list(sqlite_schema.name,%Q)\n"
" WHERE pragma_index_list.origin='pk'\n"
" AND pragma_index_list.name NOT IN allidx)\n"
" FROM \"%w\".sqlite_schema\n"
" )\n"
"INSERT INTO temp.%s\n"
" SELECT\n"
" allname,\n"
" tblname,\n"
" isidx,\n"
" isworowid,\n"
" sum(ncell),\n"
" sum((pagetype='leaf')*ncell),\n"
" max((length(if(path GLOB '*+*','',path))+3)/4),\n"
" sum(payload),\n"
" sum((pagetype='overflow')*payload),\n"
" sum(path GLOB '*+000000'),\n"
" max(mx_payload),\n"
" sum(pagetype='internal'),\n"
" sum(pagetype='leaf'),\n"
" sum(pagetype='overflow'),\n"
" sum((pagetype='internal')*unused),\n"
" sum((pagetype='leaf')*unused),\n"
" sum((pagetype='overflow')*unused),\n"
" sum(if(pagetype='internal',ncell))\n"
" FROM allobj CROSS JOIN dbstat(%Q) \n"
" WHERE dbstat.name=allobj.allname\n"
" GROUP BY allname;\n",
s.zSchema, /* %w.sqlite_schema -- in allidx */
s.zSchema, /* pragma_index_list(...,%Q) */
s.zSchema, /* %w.sqlite_schema */
s.zSU, /* INTO temp.%s */
s.zSchema /* JOIN dbstat(%Q) */
);
if( rc ) return;
nPage = 0;
rc = analysisSqlInt(&s, &nPage, "PRAGMA \"%w\".page_count", s.zSchema);
if( rc ) return;
if( nPage<=0 ){
/* Very brief reply for an empty database */
analysisReset(&s);
sqlite3_result_text(context, "empty database", -1, SQLITE_STATIC);
return;
}
/* Begin generating the report */
analysisTitle(&s, "Database storage utilization report");
pgsz = 0;
rc = analysisSqlInt(&s, &pgsz, "PRAGMA \"%w\".page_size", s.zSchema);
if( rc ) return;
analysisLine(&s, "Page size in bytes","%lld\n",pgsz);
analysisLine(&s, "Pages in the database", "%lld\n", nPage);
nPageInUse = 0;
rc = analysisSqlInt(&s, &nPageInUse,
"SELECT sum(leaf_pages+int_pages+ovfl_pages) FROM temp.%s", s.zSU);
if( rc ) return;
analysisLine(&s, "Pages that store data", "%-11lld ", nPageInUse);
analysisPercent(&s, (nPageInUse*100.0)/(double)nPage);
nFreeList = 0;
rc = analysisSqlInt(&s, &nFreeList, "PRAGMA \"%w\".freelist_count",s.zSchema);
if( rc ) return;
analysisLine(&s, "Pages on the freelist", "%-11lld ", nFreeList);
analysisPercent(&s, (nFreeList*100.0)/(double)nPage);
i64 = 0;
rc = analysisSqlInt(&s, &i64, "PRAGMA \"%w\".auto_vacuum", s.zSchema);
if( rc ) return;
if( i64==0 || nPage<=1 ){
i64 = 0;
}else{
double rPtrsPerPage = pgsz/5;
double rAvPage = (nPage-1.0)/(rPtrsPerPage+1.0);
i64 = (sqlite3_int64)ceil(rAvPage);
}
analysisLine(&s, "Pages of auto-vacuum overhead", "%-11lld ", i64);
analysisPercent(&s, (i64*100.0)/(double)nPage);
i64 = 0;
rc = analysisSqlInt(&s, &i64,
"SELECT count(*)+1 FROM \"%w\".sqlite_schema WHERE type='table'",
s.zSchema);
if( rc ) return;
analysisLine(&s, "Number of tables", "%lld\n", i64);
nWORowid = 0;
rc = analysisSqlInt(&s, &nWORowid,
"SELECT count(*) FROM \"%w\".pragma_table_list WHERE wr",
s.zSchema);
if( rc ) return;
if( nWORowid>0 ){
analysisLine(&s, "Number of WITHOUT ROWID tables", "%lld\n", nWORowid);
analysisLine(&s, "Number of rowid tables", "%lld\n", i64 - nWORowid);
}
nIndex = 0;
rc = analysisSqlInt(&s, &nIndex,
"SELECT count(*) FROM \"%w\".sqlite_schema WHERE type='index'",
s.zSchema);
if( rc ) return;
analysisLine(&s, "Number of indexes", "%lld\n", nIndex);
i64 = 0;
rc = analysisSqlInt(&s, &i64,
"SELECT count(*) FROM \"%w\".sqlite_schema"
" WHERE name GLOB 'sqlite_autoindex_*' AND type='index'",
s.zSchema);
if( rc ) return;
analysisLine(&s, "Number of defined indexes", "%lld\n", nIndex - i64);
analysisLine(&s, "Number of implied indexes", "%lld\n", i64);
analysisLine(&s, "Size of the database in bytes", "%lld\n", pgsz*nPage);
i64 = 0;
rc = analysisSqlInt(&s, &i64,
"SELECT sum(payload) FROM temp.%s"
" WHERE NOT is_index AND name NOT LIKE 'sqlite_schema'",
s.zSU);
if( rc ) return;
analysisLine(&s, "Bytes of payload", "%-11lld ", i64);
analysisPercent(&s, i64*100.0/(double)(pgsz*nPage));
analysisTitle(&s, "Page counts for all tables with their indexes");
pStmt = analysisPrepare(&s,
"SELECT upper(tblname),\n"
" sum(int_pages+leaf_pages+ovfl_pages)\n"
" FROM temp.%s\n"
" GROUP BY 1\n"
" ORDER BY 2 DESC, 1;",
s.zSU);
if( pStmt==0 ) return;
while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){
sqlite3_int64 nn = sqlite3_column_int64(pStmt,1);
analysisLine(&s, (const char*)sqlite3_column_text(pStmt,0), "%-11lld ", nn);
analysisPercent(&s, (nn*100.0)/(double)nPage);
}
if( analysisStmtFinish(&s, rc, pStmt) ) return;
analysisTitle(&s, "Page counts for all tables and indexes separately");
pStmt = analysisPrepare(&s,
"SELECT upper(name),\n"
" sum(int_pages+leaf_pages+ovfl_pages)\n"
" FROM temp.%s\n"
" GROUP BY 1\n"
" ORDER BY 2 DESC, 1;",
s.zSU);
if( pStmt==0 ) return;
while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){
sqlite3_int64 nn = sqlite3_column_int64(pStmt,1);
analysisLine(&s, (const char*)sqlite3_column_text(pStmt,0), "%-11lld ", nn);
analysisPercent(&s, (nn*100.0)/(double)nPage);
}
if( analysisStmtFinish(&s, rc, pStmt) ) return;
rc = analysisSubreport(&s, "All tables and indexes", "1", pgsz, nPage);
if( rc ) return;
rc = analysisSubreport(&s, "All tables", "NOT is_index", pgsz, nPage);
if( rc ) return;
if( nWORowid>0 ){
rc = analysisSubreport(&s, "All WITHOUT ROWID tables", "is_without_rowid",
pgsz, nPage);
if( rc ) return;
rc = analysisSubreport(&s, "All rowid tables",
"NOT is_without_rowid AND NOT is_index",
pgsz, nPage);
if( rc ) return;
}
rc = analysisSubreport(&s, "All indexes", "is_index", pgsz, nPage);
if( rc ) return;
pStmt = analysisPrepare(&s,
"SELECT upper(tblname), tblname, sum(is_index) FROM temp.%s"
" GROUP BY 1 ORDER BY 1",
s.zSU);
if( pStmt==0 ) return;
while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){
const char *zUpper = (const char*)sqlite3_column_text(pStmt, 0);
const char *zName = (const char*)sqlite3_column_text(pStmt, 1);
int nSubIndex = sqlite3_column_int(pStmt, 2);
if( nSubIndex==0 ){
char *zTitle = sqlite3_mprintf("Table %s", zUpper);
char *zWhere = sqlite3_mprintf("name=%Q", zName);
rc = analysisSubreport(&s, zTitle, zWhere, pgsz, nPage);
sqlite3_free(zTitle);
sqlite3_free(zWhere);
if( rc ) break;
}else{
sqlite3_stmt *pS2;
char *zTitle = sqlite3_mprintf("Table %s and all its indexes", zUpper);
char *zWhere = sqlite3_mprintf("tblname=%Q", zName);
rc = analysisSubreport(&s, zTitle, zWhere, pgsz, nPage);
sqlite3_free(zTitle);
sqlite3_free(zWhere);
if( rc ) break;
zTitle = sqlite3_mprintf("Table %s w/o any indexes", zUpper);
zWhere = sqlite3_mprintf("name=%Q", zName);
rc = analysisSubreport(&s, zTitle, zWhere, pgsz, nPage);
sqlite3_free(zTitle);
sqlite3_free(zWhere);
if( rc ) break;
if( nSubIndex>1 ){
zTitle = sqlite3_mprintf("All indexes of table %s", zUpper);
zWhere = sqlite3_mprintf("tblname=%Q AND is_index", zName);
rc = analysisSubreport(&s, zTitle, zWhere, pgsz, nPage);
sqlite3_free(zTitle);
sqlite3_free(zWhere);
if( rc ) break;
}
pS2 = analysisPrepare(&s,
"SELECT name, upper(name) FROM temp.%s"
" WHERE is_index AND tblname=%Q",
s.zSU, zName);
if( pS2==0 ){
rc = SQLITE_NOMEM;
break;
}
while( (rc = sqlite3_step(pS2))==SQLITE_ROW ){
const char *zU = (const char*)sqlite3_column_text(pS2, 1);
const char *zN = (const char*)sqlite3_column_text(pS2, 0);
zTitle = sqlite3_mprintf("Index %s", zU);
zWhere = sqlite3_mprintf("name=%Q", zN);
rc = analysisSubreport(&s, zTitle, zWhere, pgsz, nPage);
sqlite3_free(zTitle);
sqlite3_free(zWhere);
if( rc ) break;
}
rc = analysisStmtFinish(&s, rc, pS2);
if( rc ) break;
}
}
if( analysisStmtFinish(&s, rc, pStmt) ) return;
/* Append SQL statements that will recreate the raw data used for
** the analysis.
*/
analysisTitle(&s, "Raw data used to generate this report");
sqlite3_str_appendf(s.pOut,
"The following SQL will create a table named \"space_used\" which\n"
"contains most of the information used to generate the report above.\n"
"*/\n"
);
sqlite3_str_appendf(s.pOut,
"BEGIN;\n"
"CREATE TABLE space_used(\n"
" name text, -- A table or index\n" /* 0 */
" tblname text, -- Table that owns name\n" /* 1 */
" is_index boolean, -- TRUE if it is an index\n" /* 2 */
" is_without_rowid boolean, -- TRUE if WITHOUT ROWID table\n" /* 3 */
" nentry int, -- Number of entries in the BTree\n" /* 4 */
" leaf_entries int, -- Number of leaf entries\n" /* 5 */
" depth int, -- Depth of the b-tree\n" /* 6 */
" payload int, -- Total data in this table/index\n" /* 7 */
" ovfl_payload int, -- Total data on overflow pages\n" /* 8 */
" ovfl_cnt int, -- Entries that use overflow\n" /* 9 */
" mx_payload int, -- Maximum payload size\n" /* 10 */
" int_pages int, -- Interior pages used\n" /* 11 */
" leaf_pages int, -- Leaf pages used\n" /* 12 */
" ovfl_pages int, -- Overflow pages used\n" /* 13 */
" int_unused int, -- Unused bytes on interior pages\n" /* 14 */
" leaf_unused int, -- Unused bytes on primary pages\n" /* 15 */
" ovfl_unused int, -- Unused bytes on overflow pages\n" /* 16 */
" int_entries int -- B-tree entries on internal pages\n"/* 17 */
");\n"
"INSERT INTO space_used VALUES\n"
);
pStmt = analysisPrepare(&s,
"SELECT quote(name), quote(tblname),\n" /* 0..1 */
" is_index, is_without_rowid, nentry, leaf_entries,\n" /* 2..5 */
" depth, payload, ovfl_payload, ovfl_cnt, mx_payload,\n" /* 6..10 */
" int_pages, leaf_pages, ovfl_pages, int_unused,\n" /* 11..14 */
" leaf_unused, ovfl_unused, int_entries\n" /* 15..17 */
" FROM temp.%s;",
s.zSU);
if( pStmt==0 ) return;
n = 0;
while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){
if( n++ ) sqlite3_str_appendf(s.pOut,",\n");
sqlite3_str_appendf(s.pOut,
" (%s,%s,%lld,%lld,%lld,%lld,%lld,%lld,%lld,"
"%lld,%lld,%lld,%lld,%lld,%lld,%lld,%lld,%lld)",
sqlite3_column_text(pStmt, 0),
sqlite3_column_text(pStmt, 1),
sqlite3_column_int64(pStmt, 2),
sqlite3_column_int64(pStmt, 3),
sqlite3_column_int64(pStmt, 4),
sqlite3_column_int64(pStmt, 5),
sqlite3_column_int64(pStmt, 6),
sqlite3_column_int64(pStmt, 7),
sqlite3_column_int64(pStmt, 8),
sqlite3_column_int64(pStmt, 9),
sqlite3_column_int64(pStmt, 10),
sqlite3_column_int64(pStmt, 11),
sqlite3_column_int64(pStmt, 12),
sqlite3_column_int64(pStmt, 13),
sqlite3_column_int64(pStmt, 14),
sqlite3_column_int64(pStmt, 15),
sqlite3_column_int64(pStmt, 16),
sqlite3_column_int64(pStmt, 17));
}
if( rc!=SQLITE_DONE ){
analysisError(&s, "SQL run-time error: %s\nSQL: %s",
sqlite3_errmsg(s.db), sqlite3_sql(pStmt));
sqlite3_finalize(pStmt);
return;
}
sqlite3_str_appendf(s.pOut,";\nCOMMIT;");
sqlite3_finalize(pStmt);
if( sqlite3_str_length(s.pOut) ){
sqlite3_result_text(context, sqlite3_str_finish(s.pOut), -1,
sqlite3_free);
s.pOut = 0;
}
analysisReset(&s);
}
#ifdef _WIN32
__declspec(dllexport)
#endif
int sqlite3_analyze_init(
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
){
int rc = SQLITE_OK;
SQLITE_EXTENSION_INIT2(pApi);
(void)pzErrMsg; /* Unused parameter */
rc = sqlite3_create_function(db, "analyze", 1,
SQLITE_UTF8|SQLITE_INNOCUOUS,
0, analyzeFunc, 0, 0);
return rc;
}