]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add the sqlite_btreeinfo eponymous-only table for introspecting the schema
authordrh <drh@noemail.net>
Wed, 25 Oct 2017 01:34:24 +0000 (01:34 +0000)
committerdrh <drh@noemail.net>
Wed, 25 Oct 2017 01:34:24 +0000 (01:34 +0000)
and estimating the sizes of various btrees.

FossilOrigin-Name: 1e30f4772db1e1086096f72d32e87c552923be8b264aa13cf822fae754eb083d

ext/misc/btreeinfo.c [new file with mode: 0644]
manifest
manifest.uuid

diff --git a/ext/misc/btreeinfo.c b/ext/misc/btreeinfo.c
new file mode 100644 (file)
index 0000000..131c63e
--- /dev/null
@@ -0,0 +1,425 @@
+/*
+** 2017-10-24
+**
+** 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 "sqlite_btreeinfo" virtual table.
+**
+** The sqlite_btreeinfo virtual table is a read-only eponymous-only virtual
+** table that shows information about all btrees in an SQLite database file.
+** The schema is like this:
+**
+** CREATE TABLE sqlite_btreeinfo(
+**    type TEXT,                   -- "table" or "index"
+**    name TEXT,                   -- Name of table or index for this btree.
+**    tbl_name TEXT,               -- Associated table
+**    rootpage INT,                -- The root page of the btree
+**    sql TEXT,                    -- SQL for this btree - from sqlite_master
+**    hasRowid BOOLEAN,            -- True if the btree has a rowid
+**    nEntry INT,                  -- Estimated number of enteries
+**    nPage INT,                   -- Estimated number of pages
+**    depth INT,                   -- Depth of the btree
+**    szPage INT,                  -- Size of each page in bytes
+**    zSchema TEXT HIDDEN          -- The schema to which this btree belongs
+** );
+**
+** The first 5 fields are taken directly from the sqlite_master table.
+** Considering only the first 5 fields, the only difference between 
+** this virtual table and the sqlite_master table is that this virtual
+** table omits all entries that have a 0 or NULL rowid - in other words
+** it omits triggers and views.
+**
+** The value added by this table comes in the next 5 fields.
+**
+** Note that nEntry and nPage are *estimated*.  They are computed doing
+** a single search from the root to a leaf, counting the number of cells
+** at each level, and assuming that unvisited pages have a similar number
+** of cells.
+**
+** The sqlite_dbpage virtual table must be available for this virtual table
+** to operate.
+**
+** USAGE EXAMPLES:
+**
+** Show the table btrees in a schema order with the tables with the most
+** rows occuring first:
+**
+**      SELECT name, nEntry
+**        FROM sqlite_btreeinfo
+**       WHERE type='table'
+**       ORDER BY nEntry DESC, name;
+**
+** Show the names of all WITHOUT ROWID tables: 
+**
+**      SELECT name FROM sqlite_btreeinfo
+**       WHERE type='table' AND NOT hasRowid;
+*/
+#include <sqlite3ext.h>
+SQLITE_EXTENSION_INIT1
+#include <string.h>
+#include <assert.h>
+
+/* Columns available in this virtual table */
+#define BINFO_COLUMN_TYPE         0
+#define BINFO_COLUMN_NAME         1
+#define BINFO_COLUMN_TBL_NAME     2
+#define BINFO_COLUMN_ROOTPAGE     3
+#define BINFO_COLUMN_SQL          4
+#define BINFO_COLUMN_HASROWID     5
+#define BINFO_COLUMN_NENTRY       6
+#define BINFO_COLUMN_NPAGE        7
+#define BINFO_COLUMN_DEPTH        8
+#define BINFO_COLUMN_SZPAGE       9
+#define BINFO_COLUMN_SCHEMA      10
+
+/* Forward declarations */
+typedef struct BinfoTable BinfoTable;
+typedef struct BinfoCursor BinfoCursor;
+
+/* A cursor for the sqlite_btreeinfo table */
+struct BinfoCursor {
+  sqlite3_vtab_cursor base;       /* Base class.  Must be first */
+  sqlite3_stmt *pStmt;            /* Query against sqlite_master */
+  int rc;                         /* Result of previous sqlite_step() call */
+  int hasRowid;                   /* hasRowid value.  Negative if unknown. */
+  sqlite3_int64 nEntry;           /* nEntry value */
+  int nPage;                      /* nPage value */
+  int depth;                      /* depth value */
+  int szPage;                     /* size of a btree page.  0 if unknown */
+  char *zSchema;                  /* Schema being interrogated */
+};
+
+/* The sqlite_btreeinfo table */
+struct BinfoTable {
+  sqlite3_vtab base;              /* Base class.  Must be first */
+  sqlite3 *db;                    /* The databse connection */
+};
+
+/*
+** Connect to the sqlite_btreeinfo virtual table.
+*/
+static int binfoConnect(
+  sqlite3 *db,
+  void *pAux,
+  int argc, const char *const*argv,
+  sqlite3_vtab **ppVtab,
+  char **pzErr
+){
+  BinfoTable *pTab = 0;
+  int rc = SQLITE_OK;
+  rc = sqlite3_declare_vtab(db, 
+      "CREATE TABLE x(\n"
+      " type TEXT,\n"
+      " name TEXT,\n"
+      " tbl_name TEXT,\n"
+      " rootpage INT,\n"
+      " sql TEXT,\n"
+      " hasRowid BOOLEAN,\n"
+      " nEntry INT,\n"
+      " nPage INT,\n"
+      " depth INT,\n"
+      " szPage INT,\n"
+      " zSchema TEXT HIDDEN\n"
+      ")");
+  if( rc==SQLITE_OK ){
+    pTab = (BinfoTable *)sqlite3_malloc64(sizeof(BinfoTable));
+    if( pTab==0 ) rc = SQLITE_NOMEM;
+  }
+  assert( rc==SQLITE_OK || pTab==0 );
+  if( pTab ){
+    pTab->db = db;
+  }
+  *ppVtab = (sqlite3_vtab*)pTab;
+  return rc;
+}
+
+/*
+** Disconnect from or destroy a btreeinfo virtual table.
+*/
+static int binfoDisconnect(sqlite3_vtab *pVtab){
+  sqlite3_free(pVtab);
+  return SQLITE_OK;
+}
+
+/*
+** idxNum:
+**
+**     0     Use "main" for the schema
+**     1     Schema identified by parameter ?1
+*/
+static int binfoBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
+  int i;
+  pIdxInfo->estimatedCost = 10000.0;  /* Cost estimate */
+  pIdxInfo->estimatedRows = 100;
+  for(i=0; i<pIdxInfo->nConstraint; i++){
+    struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[i];
+    if( p->usable
+     && p->iColumn==BINFO_COLUMN_SCHEMA
+     && p->op==SQLITE_INDEX_CONSTRAINT_EQ
+    ){
+      pIdxInfo->estimatedCost = 1000.0;
+      pIdxInfo->idxNum = 1;
+      pIdxInfo->aConstraintUsage[i].argvIndex = 1;
+      pIdxInfo->aConstraintUsage[i].omit = 1;
+      break;
+    }
+  }
+  return SQLITE_OK;
+}
+
+/*
+** Open a new btreeinfo cursor.
+*/
+static int binfoOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
+  BinfoCursor *pCsr;
+
+  pCsr = (BinfoCursor *)sqlite3_malloc64(sizeof(BinfoCursor));
+  if( pCsr==0 ){
+    return SQLITE_NOMEM;
+  }else{
+    memset(pCsr, 0, sizeof(BinfoCursor));
+    pCsr->base.pVtab = pVTab;
+  }
+
+  *ppCursor = (sqlite3_vtab_cursor *)pCsr;
+  return SQLITE_OK;
+}
+
+/*
+** Close a btreeinfo cursor.
+*/
+static int binfoClose(sqlite3_vtab_cursor *pCursor){
+  BinfoCursor *pCsr = (BinfoCursor *)pCursor;
+  sqlite3_finalize(pCsr->pStmt);
+  sqlite3_free(pCsr->zSchema);
+  sqlite3_free(pCsr);
+  return SQLITE_OK;
+}
+
+/*
+** Move a btreeinfo cursor to the next entry in the file.
+*/
+static int binfoNext(sqlite3_vtab_cursor *pCursor){
+  BinfoCursor *pCsr = (BinfoCursor *)pCursor;
+  pCsr->rc = sqlite3_step(pCsr->pStmt);
+  pCsr->hasRowid = -1;
+  return pCsr->rc==SQLITE_ERROR ? SQLITE_ERROR : SQLITE_OK;
+}
+
+/* We have reached EOF if previous sqlite3_step() returned
+** anything other than SQLITE_ROW;
+*/
+static int binfoEof(sqlite3_vtab_cursor *pCursor){
+  BinfoCursor *pCsr = (BinfoCursor *)pCursor;
+  return pCsr->rc!=SQLITE_ROW;
+}
+
+/* Position a cursor back to the beginning.
+*/
+static int binfoFilter(
+  sqlite3_vtab_cursor *pCursor, 
+  int idxNum, const char *idxStr,
+  int argc, sqlite3_value **argv
+){
+  BinfoCursor *pCsr = (BinfoCursor *)pCursor;
+  BinfoTable *pTab = (BinfoTable *)pCursor->pVtab;
+  char *zSql;
+  int rc;
+
+  sqlite3_free(pCsr->zSchema);
+  if( idxNum==1 && sqlite3_value_type(argv[0])!=SQLITE_NULL ){
+    pCsr->zSchema = sqlite3_mprintf("%s", sqlite3_value_text(argv[0]));
+  }else{
+    pCsr->zSchema = sqlite3_mprintf("main");
+  }
+  zSql = sqlite3_mprintf(
+      "SELECT 0, 'table','sqlite_master','sqlite_master',1,NULL "
+      "UNION ALL "
+      "SELECT rowid, type, name, tbl_name, rootpage, sql"
+      " FROM \"%w\".sqlite_master WHERE rootpage>=1",
+       pCsr->zSchema);
+  sqlite3_finalize(pCsr->pStmt);
+  pCsr->pStmt = 0;
+  pCsr->hasRowid = -1;
+  rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pCsr->pStmt, 0);
+  sqlite3_free(zSql);
+  if( rc==SQLITE_OK ){
+    rc = binfoNext(pCursor);
+  }
+  return rc;
+}
+
+/* Decode big-endian integers */
+static unsigned int get_uint16(unsigned char *a){
+  return (a[0]<<8)|a[1];
+}
+static unsigned int get_uint32(unsigned char *a){
+  return (a[0]<<24)|(a[1]<<16)|(a[2]<<8)|a[3];
+}
+
+/* Examine the b-tree rooted at pgno and estimate its size.
+** Return non-zero if anything goes wrong.
+*/
+static int binfoCompute(sqlite3 *db, int pgno, BinfoCursor *pCsr){
+  sqlite3_int64 nEntry = 1;
+  int nPage = 1;
+  unsigned char *aData;
+  sqlite3_stmt *pStmt = 0;
+  int rc = SQLITE_OK;
+  int pgsz = 0;
+  int nCell;
+  int iCell;
+
+  rc = sqlite3_prepare_v2(db, 
+           "SELECT data FROM sqlite_dbpage('main') WHERE pgno=?1", -1,
+           &pStmt, 0);
+  if( rc ) return rc;
+  pCsr->depth = 1;
+  while(1){
+    sqlite3_bind_int(pStmt, 1, pgno);
+    rc = sqlite3_step(pStmt);
+    if( rc!=SQLITE_ROW ){
+      rc = SQLITE_ERROR;
+      break;
+    }
+    pCsr->szPage = pgsz = sqlite3_column_bytes(pStmt, 0);
+    aData = (unsigned char*)sqlite3_column_blob(pStmt, 0);
+    if( aData==0 ){    
+      rc = SQLITE_NOMEM;
+      break;
+    }
+    if( pgno==1 ){
+      aData += 100;
+      pgsz -= 100;
+    }
+    pCsr->hasRowid = aData[0]!=2 && aData[0]!=10;
+    nCell = get_uint16(aData+3);
+    nEntry *= (nCell+1);
+    if( aData[0]==10 || aData[0]==13 ) break;
+    nPage *= (nCell+1);
+    if( nCell<=1 ){
+      pgno = get_uint32(aData+8);
+    }else{
+      iCell = get_uint16(aData+12+2*(nCell/2));
+      if( pgno==1 ) iCell -= 100;
+      if( iCell<=12 || iCell>=pgsz-4 ){
+        rc = SQLITE_CORRUPT;
+        break;
+      }
+      pgno = get_uint32(aData+iCell);
+    }
+    pCsr->depth++;
+    sqlite3_reset(pStmt);
+  }
+  sqlite3_finalize(pStmt);
+  pCsr->nPage = nPage;
+  pCsr->nEntry = nEntry;
+  if( rc==SQLITE_ROW ) rc = SQLITE_OK;
+  return rc;
+}
+
+/* Return a column for the sqlite_btreeinfo table */
+static int binfoColumn(
+  sqlite3_vtab_cursor *pCursor, 
+  sqlite3_context *ctx, 
+  int i
+){
+  BinfoCursor *pCsr = (BinfoCursor *)pCursor;
+  if( i>=BINFO_COLUMN_HASROWID && i<=BINFO_COLUMN_SZPAGE && pCsr->hasRowid<0 ){
+    int pgno = sqlite3_column_int(pCsr->pStmt, BINFO_COLUMN_ROOTPAGE+1);
+    sqlite3 *db = sqlite3_context_db_handle(ctx);
+    int rc = binfoCompute(db, pgno, pCsr);
+    if( rc ){
+      return rc;
+    }
+  }
+  switch( i ){
+    case BINFO_COLUMN_NAME:
+    case BINFO_COLUMN_TYPE:
+    case BINFO_COLUMN_TBL_NAME:
+    case BINFO_COLUMN_ROOTPAGE:
+    case BINFO_COLUMN_SQL: {
+      sqlite3_result_value(ctx, sqlite3_column_value(pCsr->pStmt, i+1));
+      break;
+    }
+    case BINFO_COLUMN_HASROWID: {
+      sqlite3_result_int(ctx, pCsr->hasRowid);
+      break;
+    }
+    case BINFO_COLUMN_NENTRY: {
+      sqlite3_result_int64(ctx, pCsr->nEntry);
+      break;
+    }
+    case BINFO_COLUMN_NPAGE: {
+      sqlite3_result_int(ctx, pCsr->nPage);
+      break;
+    }
+    case BINFO_COLUMN_DEPTH: {
+      sqlite3_result_int(ctx, pCsr->depth);
+      break;
+    }
+    case BINFO_COLUMN_SCHEMA: {
+      sqlite3_result_text(ctx, pCsr->zSchema, -1, SQLITE_STATIC);
+      break;
+    }
+  }
+  return SQLITE_OK;
+}
+
+/* Return the ROWID for the sqlite_btreeinfo table */
+static int binfoRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
+  BinfoCursor *pCsr = (BinfoCursor *)pCursor;
+  *pRowid = sqlite3_column_int64(pCsr->pStmt, 0);
+  return SQLITE_OK;
+}
+
+/*
+** Invoke this routine to register the "sqlite_btreeinfo" virtual table module
+*/
+int sqlite3BinfoRegister(sqlite3 *db){
+  static sqlite3_module binfo_module = {
+    0,                           /* iVersion */
+    0,                           /* xCreate */
+    binfoConnect,                /* xConnect */
+    binfoBestIndex,              /* xBestIndex */
+    binfoDisconnect,             /* xDisconnect */
+    0,                           /* xDestroy */
+    binfoOpen,                   /* xOpen - open a cursor */
+    binfoClose,                  /* xClose - close a cursor */
+    binfoFilter,                 /* xFilter - configure scan constraints */
+    binfoNext,                   /* xNext - advance a cursor */
+    binfoEof,                    /* xEof - check for end of scan */
+    binfoColumn,                 /* xColumn - read data */
+    binfoRowid,                  /* xRowid - read data */
+    0,                           /* xUpdate */
+    0,                           /* xBegin */
+    0,                           /* xSync */
+    0,                           /* xCommit */
+    0,                           /* xRollback */
+    0,                           /* xFindMethod */
+    0,                           /* xRename */
+    0,                           /* xSavepoint */
+    0,                           /* xRelease */
+    0,                           /* xRollbackTo */
+  };
+  return sqlite3_create_module(db, "sqlite_btreeinfo", &binfo_module, 0);
+}
+
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+int sqlite3_btreeinfo_init(
+  sqlite3 *db, 
+  char **pzErrMsg, 
+  const sqlite3_api_routines *pApi
+){
+  SQLITE_EXTENSION_INIT2(pApi);
+  return sqlite3BinfoRegister(db);
+}
index d4ab8e50ee47ffcd77802a53bb93dd0aa223e5fb..c05d3b07285f4d5335bf893fd28609107a8002f1 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Force\sthe\sautoconf\smakefile\sfor\sMSVC\sto\salways\suse\sthe\s'find.exe'\sincluded\swith\sWindows.
-D 2017-10-24T21:17:12.950
+C Add\sthe\ssqlite_btreeinfo\seponymous-only\stable\sfor\sintrospecting\sthe\sschema\nand\sestimating\sthe\ssizes\sof\svarious\sbtrees.
+D 2017-10-25T01:34:24.293
 F Makefile.in e016061b23e60ac9ec27c65cb577292b6bde0307ca55abd874ab3487b3b1beb2
 F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
 F Makefile.msc 37740aba9c4bb359c627eadccf1cfd7be4f5f847078723777ea7763969e533b1
@@ -258,6 +258,7 @@ F ext/lsm1/test/lsm1_simple.test ca949efefa102f4644231dcd9291d8cda7699a4ce1006b2
 F ext/misc/README.md 8e008c8d2b02e09096b31dfba033253ac27c6c06a18aa5826e299fa7601d90b2
 F ext/misc/amatch.c 6db4607cb17c54b853a2d7c7c36046d004853f65b9b733e6f019d543d5dfae87
 F ext/misc/anycollseq.c 5ffdfde9829eeac52219136ad6aa7cd9a4edb3b15f4f2532de52f4a22525eddb
+F ext/misc/btreeinfo.c 89488a065f5fc3c54f81c589460d69d83cd2f337ae59ed1cf813b8572b258516 w ext/misc/btreelist.c
 F ext/misc/carray.c ed96c218ea940b85c9a274c4d9c59fe9491c299147a38a8bba537687bd6c6005
 F ext/misc/closure.c 0d2a038df8fbae7f19de42e7c7d71f2e4dc88704
 F ext/misc/completion.c 52c3f01523e3e387eb321b4739a89d1fe47cbe6025aa1f2d8d3685e9e365df0f
@@ -1664,7 +1665,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 5ab662b443df17c5ebdadc0bdac2f447c5c7e86f7a32a6943bb4ac8605879441
-R 54e49932e96b845a01a6eb1a3cc08724
-U mistachkin
-Z 580a702e0e85c423f77f3c922262f566
+P b76bffd332585e8412a0a994ae6dee79a83213d8b709d7f858c5c05678ab0887
+R 2cb01c8a7e418d5768c813428fd16a20
+U drh
+Z 2d2691251ae11d16de493df9f4384426
index 5f2c91379178ddeac71fd2116db07d3b95204450..fb6118cf93994cb5183c56f5df4421acffdd337c 100644 (file)
@@ -1 +1 @@
-b76bffd332585e8412a0a994ae6dee79a83213d8b709d7f858c5c05678ab0887
\ No newline at end of file
+1e30f4772db1e1086096f72d32e87c552923be8b264aa13cf822fae754eb083d
\ No newline at end of file