Experimental code to measure memory consumed by database schemas and prepared statements.

FossilOrigin-Name: 9aa30342f4de4eff630520ea8e07ad253d3f0877
diff --git a/src/analyze.c b/src/analyze.c
index c41fba3..d96deb3 100644
--- a/src/analyze.c
+++ b/src/analyze.c
@@ -490,18 +490,17 @@
 ** If the Index.aSample variable is not NULL, delete the aSample[] array
 ** and its contents.
 */
-void sqlite3DeleteIndexSamples(Index *pIdx){
+void sqlite3DeleteIndexSamples(sqlite3 *db, Index *pIdx){
 #ifdef SQLITE_ENABLE_STAT2
   if( pIdx->aSample ){
     int j;
     for(j=0; j<SQLITE_INDEX_SAMPLES; j++){
       IndexSample *p = &pIdx->aSample[j];
       if( p->eType==SQLITE_TEXT || p->eType==SQLITE_BLOB ){
-        sqlite3_free(p->u.z);
+        sqlite3DbFree(db, p->u.z);
       }
     }
-    sqlite3DbFree(0, pIdx->aSample);
-    pIdx->aSample = 0;
+    sqlite3DbFree(db, pIdx->aSample);
   }
 #else
   UNUSED_PARAMETER(pIdx);
@@ -542,7 +541,8 @@
   for(i=sqliteHashFirst(&db->aDb[iDb].pSchema->idxHash);i;i=sqliteHashNext(i)){
     Index *pIdx = sqliteHashData(i);
     sqlite3DefaultRowEst(pIdx);
-    sqlite3DeleteIndexSamples(pIdx);
+    sqlite3DeleteIndexSamples(db, pIdx);
+    pIdx->aSample = 0;
   }
 
   /* Check to make sure the sqlite_stat1 table exists */
diff --git a/src/build.c b/src/build.c
index 1ca6e24..989489f 100644
--- a/src/build.c
+++ b/src/build.c
@@ -347,31 +347,13 @@
 */
 static void freeIndex(sqlite3 *db, Index *p){
 #ifndef SQLITE_OMIT_ANALYZE
-  sqlite3DeleteIndexSamples(p);
+  sqlite3DeleteIndexSamples(db, p);
 #endif
   sqlite3DbFree(db, p->zColAff);
   sqlite3DbFree(db, p);
 }
 
 /*
-** Remove the given index from the index hash table, and free
-** its memory structures.
-**
-** The index is removed from the database hash tables but
-** it is not unlinked from the Table that it indexes.
-** Unlinking from the Table must be done by the calling function.
-*/
-static void sqlite3DeleteIndex(sqlite3 *db, Index *p){
-  Index *pOld;
-  const char *zName = p->zName;
-
-  pOld = sqlite3HashInsert(&p->pSchema->idxHash, zName,
-                           sqlite3Strlen30(zName), 0);
-  assert( pOld==0 || pOld==p );
-  freeIndex(db, p);
-}
-
-/*
 ** For the index called zIdxName which is found in the database iDb,
 ** unlike that index from its Table then remove the index from
 ** the index hash table and free all memory structures associated
@@ -468,9 +450,10 @@
 }
 
 /*
-** Clear the column names from a table or view.
+** Delete memory allocated for the column names of a table or view (the
+** Table.aCol[] array).
 */
-static void sqliteResetColumnNames(sqlite3 *db, Table *pTable){
+static void sqliteDeleteColumnNames(sqlite3 *db, Table *pTable){
   int i;
   Column *pCol;
   assert( pTable!=0 );
@@ -484,8 +467,6 @@
     }
     sqlite3DbFree(db, pTable->aCol);
   }
-  pTable->aCol = 0;
-  pTable->nCol = 0;
 }
 
 /*
@@ -500,21 +481,24 @@
 void sqlite3DeleteTable(sqlite3 *db, Table *pTable){
   Index *pIndex, *pNext;
 
-  if( pTable==0 ) return;
+  assert( !pTable || pTable->nRef>0 );
 
   /* Do not delete the table until the reference count reaches zero. */
-  pTable->nRef--;
-  if( pTable->nRef>0 ){
-    return;
-  }
-  assert( pTable->nRef==0 );
+  if( !pTable ) return;
+  if( ((!db || db->pnBytesFreed==0) && (--pTable->nRef)>0) ) return;
 
-  /* Delete all indices associated with this table
-  */
+  /* Delete all indices associated with this table. */
   for(pIndex = pTable->pIndex; pIndex; pIndex=pNext){
     pNext = pIndex->pNext;
     assert( pIndex->pSchema==pTable->pSchema );
-    sqlite3DeleteIndex(db, pIndex);
+    if( !db || db->pnBytesFreed==0 ){
+      char *zName = pIndex->zName; 
+      TESTONLY ( Index *pOld = ) sqlite3HashInsert(
+	  &pIndex->pSchema->idxHash, zName, sqlite3Strlen30(zName), 0
+      );
+      assert( pOld==pIndex || pOld==0 );
+    }
+    freeIndex(db, pIndex);
   }
 
   /* Delete any foreign keys attached to this table. */
@@ -522,7 +506,7 @@
 
   /* Delete the Table structure itself.
   */
-  sqliteResetColumnNames(db, pTable);
+  sqliteDeleteColumnNames(db, pTable);
   sqlite3DbFree(db, pTable->zName);
   sqlite3DbFree(db, pTable->zColAff);
   sqlite3SelectDelete(db, pTable->pSelect);
@@ -1817,7 +1801,9 @@
   for(i=sqliteHashFirst(&db->aDb[idx].pSchema->tblHash); i;i=sqliteHashNext(i)){
     Table *pTab = sqliteHashData(i);
     if( pTab->pSelect ){
-      sqliteResetColumnNames(db, pTab);
+      sqliteDeleteColumnNames(db, pTab);
+      pTab->aCol = 0;
+      pTab->nCol = 0;
     }
   }
   DbClearProperty(db, idx, DB_UnresetViews);
diff --git a/src/fkey.c b/src/fkey.c
index 217badf..399e350 100644
--- a/src/fkey.c
+++ b/src/fkey.c
@@ -1158,16 +1158,23 @@
   for(pFKey=pTab->pFKey; pFKey; pFKey=pNext){
 
     /* Remove the FK from the fkeyHash hash table. */
-    if( pFKey->pPrevTo ){
-      pFKey->pPrevTo->pNextTo = pFKey->pNextTo;
-    }else{
-      void *data = (void *)pFKey->pNextTo;
-      const char *z = (data ? pFKey->pNextTo->zTo : pFKey->zTo);
-      sqlite3HashInsert(&pTab->pSchema->fkeyHash, z, sqlite3Strlen30(z), data);
+    if( !db || db->pnBytesFreed==0 ){
+      if( pFKey->pPrevTo ){
+        pFKey->pPrevTo->pNextTo = pFKey->pNextTo;
+      }else{
+        void *p = (void *)pFKey->pNextTo;
+        const char *z = (p ? pFKey->pNextTo->zTo : pFKey->zTo);
+        sqlite3HashInsert(&pTab->pSchema->fkeyHash, z, sqlite3Strlen30(z), p);
+      }
+      if( pFKey->pNextTo ){
+        pFKey->pNextTo->pPrevTo = pFKey->pPrevTo;
+      }
     }
-    if( pFKey->pNextTo ){
-      pFKey->pNextTo->pPrevTo = pFKey->pPrevTo;
-    }
+
+    /* EV: R-30323-21917 Each foreign key constraint in SQLite is
+    ** classified as either immediate or deferred.
+    */
+    assert( pFKey->isDeferred==0 || pFKey->isDeferred==1 );
 
     /* Delete any triggers created to implement actions for this FK. */
 #ifndef SQLITE_OMIT_TRIGGER
@@ -1175,11 +1182,6 @@
     fkTriggerDelete(db, pFKey->apTrigger[1]);
 #endif
 
-    /* EV: R-30323-21917 Each foreign key constraint in SQLite is
-    ** classified as either immediate or deferred.
-    */
-    assert( pFKey->isDeferred==0 || pFKey->isDeferred==1 );
-
     pNext = pFKey->pNextFrom;
     sqlite3DbFree(db, pFKey);
   }
diff --git a/src/malloc.c b/src/malloc.c
index e34c279..05ea510 100644
--- a/src/malloc.c
+++ b/src/malloc.c
@@ -455,7 +455,13 @@
 */
 void sqlite3DbFree(sqlite3 *db, void *p){
   assert( db==0 || sqlite3_mutex_held(db->mutex) );
-  if( isLookaside(db, p) ){
+  if( db && db->pnBytesFreed ){
+    if( isLookaside(db, p) ){
+      *db->pnBytesFreed += db->lookaside.sz;
+    }else{
+      *db->pnBytesFreed += sqlite3MallocSize(p);
+    }
+  }else if( isLookaside(db, p) ){
     LookasideSlot *pBuf = (LookasideSlot*)p;
     pBuf->pNext = db->lookaside.pFree;
     db->lookaside.pFree = pBuf;
diff --git a/src/sqlite.h.in b/src/sqlite.h.in
index 711e815..3e18d22 100644
--- a/src/sqlite.h.in
+++ b/src/sqlite.h.in
@@ -5252,7 +5252,9 @@
 */
 #define SQLITE_DBSTATUS_LOOKASIDE_USED     0
 #define SQLITE_DBSTATUS_CACHE_USED         1
-#define SQLITE_DBSTATUS_MAX                1   /* Largest defined DBSTATUS */
+#define SQLITE_DBSTATUS_SCHEMA_USED        2
+#define SQLITE_DBSTATUS_STMT_USED          3
+#define SQLITE_DBSTATUS_MAX                3   /* Largest defined DBSTATUS */
 
 
 /*
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index a9a0885..66f1cff 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -860,6 +860,8 @@
   int nStatement;               /* Number of nested statement-transactions  */
   u8 isTransactionSavepoint;    /* True if the outermost savepoint is a TS */
   i64 nDeferredCons;            /* Net deferred constraints this transaction. */
+  int *pnBytesFreed;            /* If not NULL, increment this in DbFree() */
+  SubProgram *pSubProgram;      /* List of sub-programs already visited*/
 
 #ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
   /* The following variables are all protected by the STATIC_MASTER 
@@ -2908,7 +2910,7 @@
 int sqlite3FindDb(sqlite3*, Token*);
 int sqlite3FindDbName(sqlite3 *, const char *);
 int sqlite3AnalysisLoad(sqlite3*,int iDB);
-void sqlite3DeleteIndexSamples(Index*);
+void sqlite3DeleteIndexSamples(sqlite3*,Index*);
 void sqlite3DefaultRowEst(Index*);
 void sqlite3RegisterLikeFunctions(sqlite3*, int);
 int sqlite3IsLikeFunction(sqlite3*,Expr*,int*,char*);
diff --git a/src/status.c b/src/status.c
index be2e6dc..711827b 100644
--- a/src/status.c
+++ b/src/status.c
@@ -14,6 +14,7 @@
 ** functionality.
 */
 #include "sqliteInt.h"
+#include "vdbeInt.h"
 
 /*
 ** Variables in which to record status information.
@@ -136,6 +137,66 @@
       *pHighwater = 0;
       break;
     }
+
+    case SQLITE_DBSTATUS_SCHEMA_USED: {
+      int i;                      /* Used to iterate through schemas */
+      int nByte = 0;              /* Used to accumulate return value */
+
+      assert( db->pSubProgram==0 );
+      db->pnBytesFreed = &nByte;
+      for(i=0; i<db->nDb; i++){
+	Schema *pSchema = db->aDb[i].pSchema;
+	if( pSchema ){
+  	  HashElem *p;
+
+	  nByte += sizeof(HashElem) * (
+	      pSchema->tblHash.count 
+	    + pSchema->trigHash.count
+	    + pSchema->idxHash.count
+	    + pSchema->fkeyHash.count
+	  );
+	  nByte += sqlite3MallocSize(pSchema->tblHash.ht);
+	  nByte += sqlite3MallocSize(pSchema->trigHash.ht);
+	  nByte += sqlite3MallocSize(pSchema->idxHash.ht);
+	  nByte += sqlite3MallocSize(pSchema->fkeyHash.ht);
+
+          for(p=sqliteHashFirst(&pSchema->trigHash); p; p=sqliteHashNext(p)){
+            sqlite3DeleteTrigger(db, (Trigger*)sqliteHashData(p));
+          }
+          for(p=sqliteHashFirst(&pSchema->tblHash); p; p=sqliteHashNext(p)){
+            sqlite3DeleteTable(db, (Table *)sqliteHashData(p));
+          }
+	}
+      }
+      db->pnBytesFreed = 0;
+
+      *pHighwater = 0;
+      *pCurrent = nByte;
+      break;
+    }
+
+    case SQLITE_DBSTATUS_STMT_USED: {
+      struct Vdbe *pVdbe;         /* Used to iterate through VMs */
+      int nByte = 0;              /* Used to accumulate return value */
+
+      db->pnBytesFreed = &nByte;
+      for(pVdbe=db->pVdbe; pVdbe; pVdbe=pVdbe->pNext){
+        SubProgram *pSub, *pNext;
+	sqlite3VdbeDeleteObject(db, pVdbe);
+        for(pSub=db->pSubProgram; pSub; pSub=pNext){
+	  pNext = pSub->pNext;
+	  pSub->pNext = 0;
+        }
+	db->pSubProgram = 0;
+      }
+      db->pnBytesFreed = 0;
+
+      *pHighwater = 0;
+      *pCurrent = nByte;
+
+      break;
+    }
+
     default: {
       rc = SQLITE_ERROR;
     }
diff --git a/src/test_malloc.c b/src/test_malloc.c
index 1267f6e..e32b78e 100644
--- a/src/test_malloc.c
+++ b/src/test_malloc.c
@@ -1288,6 +1288,8 @@
   } aOp[] = {
     { "SQLITE_DBSTATUS_LOOKASIDE_USED",    SQLITE_DBSTATUS_LOOKASIDE_USED   },
     { "SQLITE_DBSTATUS_CACHE_USED",        SQLITE_DBSTATUS_CACHE_USED       },
+    { "SQLITE_DBSTATUS_SCHEMA_USED",       SQLITE_DBSTATUS_SCHEMA_USED      },
+    { "SQLITE_DBSTATUS_STMT_USED",         SQLITE_DBSTATUS_STMT_USED        }
   };
   Tcl_Obj *pResult;
   if( objc!=4 ){
diff --git a/src/vdbe.h b/src/vdbe.h
index c234d51..be77e2f 100644
--- a/src/vdbe.h
+++ b/src/vdbe.h
@@ -83,6 +83,7 @@
   int nCsr;                     /* Number of cursors required */
   int nRef;                     /* Number of pointers to this structure */
   void *token;                  /* id that may be used to recursive triggers */
+  SubProgram *pNext;            /* Next sub-program already visited */
 };
 
 /*
@@ -184,6 +185,7 @@
 int sqlite3VdbeMakeLabel(Vdbe*);
 void sqlite3VdbeRunOnlyOnce(Vdbe*);
 void sqlite3VdbeDelete(Vdbe*);
+void sqlite3VdbeDeleteObject(sqlite3*,Vdbe*);
 void sqlite3VdbeMakeReady(Vdbe*,int,int,int,int,int,int);
 int sqlite3VdbeFinalize(Vdbe*);
 void sqlite3VdbeResolveLabel(Vdbe*, int);
diff --git a/src/vdbeaux.c b/src/vdbeaux.c
index cf53f90..a297ddd 100644
--- a/src/vdbeaux.c
+++ b/src/vdbeaux.c
@@ -573,11 +573,14 @@
   }
 }
 
+static void vdbeFreeOpArray(sqlite3 *, Op *, int);
+
 /*
 ** Delete a P4 value if necessary.
 */
 static void freeP4(sqlite3 *db, int p4type, void *p4){
   if( p4 ){
+    assert( db );
     switch( p4type ){
       case P4_REAL:
       case P4_INT64:
@@ -592,7 +595,7 @@
       case P4_VDBEFUNC: {
         VdbeFunc *pVdbeFunc = (VdbeFunc *)p4;
         freeEphemeralFunction(db, pVdbeFunc->pFunc);
-        sqlite3VdbeDeleteAuxData(pVdbeFunc, 0);
+        if( db->pnBytesFreed==0 ) sqlite3VdbeDeleteAuxData(pVdbeFunc, 0);
         sqlite3DbFree(db, pVdbeFunc);
         break;
       }
@@ -605,11 +608,25 @@
         break;
       }
       case P4_VTAB : {
-        sqlite3VtabUnlock((VTable *)p4);
+        if( db->pnBytesFreed==0 ) sqlite3VtabUnlock((VTable *)p4);
         break;
       }
       case P4_SUBPROGRAM : {
-        sqlite3VdbeProgramDelete(db, (SubProgram *)p4, 1);
+        if( db->pnBytesFreed ){
+	  SubProgram *p = (SubProgram *)p4;
+	  SubProgram *pDone;
+	  for(pDone=db->pSubProgram; pDone; pDone=pDone->pNext){
+	    if( pDone==p ) break;
+	  }
+	  if( !pDone ){
+	    p->pNext = db->pSubProgram;
+	    db->pSubProgram = p;
+            vdbeFreeOpArray(db, p->aOp, p->nOp);
+            sqlite3DbFree(db, p);
+	  }
+	}else{
+	  sqlite3VdbeProgramDelete(db, (SubProgram *)p4, 1);
+	}
         break;
       }
     }
@@ -1003,6 +1020,11 @@
     Mem *pEnd;
     sqlite3 *db = p->db;
     u8 malloc_failed = db->mallocFailed;
+    if( db->pnBytesFreed ){
+      for(pEnd=&p[N]; p<pEnd; p++){
+        sqlite3DbFree(db, p->zMalloc);
+      }
+    }else
     for(pEnd=&p[N]; p<pEnd; p++){
       assert( (&p[1])==pEnd || p[0].db==p[1].db );
 
@@ -2336,6 +2358,24 @@
 }
 
 /*
+** Free all memory associated with the Vdbe passed as the second argument.
+** The difference between this function and sqlite3VdbeDelete() is that
+** VdbeDelete() also unlinks the Vdbe from the list of VMs associated with
+** the database connection.
+*/
+void sqlite3VdbeDeleteObject(sqlite3 *db, Vdbe *p){
+  assert( p->db==0 || p->db==db );
+  releaseMemArray(p->aVar, p->nVar);
+  releaseMemArray(p->aColName, p->nResColumn*COLNAME_N);
+  vdbeFreeOpArray(db, p->aOp, p->nOp);
+  sqlite3DbFree(db, p->aLabel);
+  sqlite3DbFree(db, p->aColName);
+  sqlite3DbFree(db, p->zSql);
+  sqlite3DbFree(db, p->pFree);
+  sqlite3DbFree(db, p);
+}
+
+/*
 ** Delete an entire VDBE.
 */
 void sqlite3VdbeDelete(Vdbe *p){
@@ -2352,16 +2392,9 @@
   if( p->pNext ){
     p->pNext->pPrev = p->pPrev;
   }
-  releaseMemArray(p->aVar, p->nVar);
-  releaseMemArray(p->aColName, p->nResColumn*COLNAME_N);
-  vdbeFreeOpArray(db, p->aOp, p->nOp);
-  sqlite3DbFree(db, p->aLabel);
-  sqlite3DbFree(db, p->aColName);
-  sqlite3DbFree(db, p->zSql);
   p->magic = VDBE_MAGIC_DEAD;
-  sqlite3DbFree(db, p->pFree);
   p->db = 0;
-  sqlite3DbFree(db, p);
+  sqlite3VdbeDeleteObject(db, p);
 }
 
 /*
diff --git a/src/vtab.c b/src/vtab.c
index 5bf8676..e57f4e4 100644
--- a/src/vtab.c
+++ b/src/vtab.c
@@ -222,7 +222,7 @@
 ** database connection.
 */
 void sqlite3VtabClear(sqlite3 *db, Table *p){
-  vtabDisconnectAll(0, p);
+  if( !db || db->pnBytesFreed==0 ) vtabDisconnectAll(0, p);
   if( p->azModuleArg ){
     int i;
     for(i=0; i<p->nModuleArg; i++){