]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
When doing DROP TABLE or DROP INDEX, use a heap to ensure that the various
authordrh <>
Fri, 1 Mar 2024 22:42:16 +0000 (22:42 +0000)
committerdrh <>
Fri, 1 Mar 2024 22:42:16 +0000 (22:42 +0000)
btrees are dropped in the right order for autovacuum.

FossilOrigin-Name: e5bf9556ffd1d0880cc982d389e229246d0f3ddce4264905c6a7dd9c55d735e8

manifest
manifest.uuid
src/build.c
src/sqliteInt.h

index 5d9a484110a6f735297318c41554d879c21d84f3..444119030ff978ad617c993f836b74b07657df15 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C RTREE\suses\smulti-DROP\sto\serase\sshadow\stables.
-D 2024-03-01T19:08:45.048
+C When\sdoing\sDROP\sTABLE\sor\sDROP\sINDEX,\suse\sa\sheap\sto\sensure\sthat\sthe\svarious\nbtrees\sare\sdropped\sin\sthe\sright\sorder\sfor\sautovacuum.
+D 2024-03-01T22:42:16.916
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -692,7 +692,7 @@ F src/btmutex.c 79a43670447eacc651519a429f6ece9fd638563cf95b469d6891185ddae2b522
 F src/btree.c 285b493d843e7ba8ef78b6ae7d31238e904901dbc0c484f7904de4cf18fd8802
 F src/btree.h 55066f513eb095db935169dab1dc2f7c7a747ef223c533f5d4ad4dfed346cbd0
 F src/btreeInt.h 98aadb6dcb77b012cab2574d6a728fad56b337fc946839b9898c4b4c969e30b6
-F src/build.c ded762e673c249f8e0a824add7de5e4141e095e3e68497b84ac27c6639fe4970
+F src/build.c bbd0408e72f4b22964c2a92a29ddd0154c509fe78a01036e2cb3ff8721fceff5
 F src/callback.c db3a45e376deff6a16c0058163fe0ae2b73a2945f3f408ca32cf74960b28d490
 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e
 F src/ctime.c 23331529e654be40ca97d171cbbffe9b3d4c71cc53b78fe5501230675952da8b
@@ -755,7 +755,7 @@ F src/shell.c.in 2ec564ed3ff0147036be313efeb47b3dbfb8753d5eb5ea0e90636427c6b3a36
 F src/sqlite.h.in 19a2db3995a699bd7f6dfb423856242bfceb7ec849a93c91d241d19fc28d9f0f
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h 3f046c04ea3595d6bfda99b781926b17e672fd6d27da2ba6d8d8fc39981dcb54
-F src/sqliteInt.h cb537898d97cf63a910508c72b1156b9eab9d1b1f11653a5462f449f1ace55d1
+F src/sqliteInt.h 532224eec9f53245c8450c662c2a9d06a1aec22cb28e5fc4cdfd0f67239e9049
 F src/sqliteLimit.h 6878ab64bdeb8c24a1d762d45635e34b96da21132179023338c93f820eee6728
 F src/status.c cb11f8589a6912af2da3bb1ec509a94dd8ef27df4d4c1a97e0bcf2309ece972b
 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
@@ -2177,8 +2177,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 90de1f73f9aaab321c0f94b41b6d76ea966d8413d5ad864311d462780e1eadcc
-R e31066ea6f239b4e4ba987689c098695
+P cf8a58d679d0cd30bf7c9208780855f0567b5e086422d59e3254b907923e0dd1
+R 91b6533837d9746e996aee053268c336
 U drh
-Z ffc3050afb0d286b6c3634e049e1856b
+Z 805fa77be429c9dc08d49424247b72ac
 # Remove this line to create a well-formed Fossil manifest.
index b76d18862adb86ab580e721bd20d22bb1abd07d7..679284ff1640ae00ae2039cf89368f21fd9e7668 100644 (file)
@@ -1 +1 @@
-cf8a58d679d0cd30bf7c9208780855f0567b5e086422d59e3254b907923e0dd1
\ No newline at end of file
+e5bf9556ffd1d0880cc982d389e229246d0f3ddce4264905c6a7dd9c55d735e8
\ No newline at end of file
index fdbce3ab367dcc74b8cee6465c3b5d69cea5234d..05cb49b72db5d277bed1d35596591eb303bd2a6a 100644 (file)
@@ -3260,84 +3260,127 @@ void sqlite3RootPageMoved(sqlite3 *db, int iDb, Pgno iFrom, Pgno iTo){
 #endif
 
 /*
-** Write code to erase the table with root-page iTable from database iDb.
-** Also write code to modify the sqlite_schema table and internal schema
-** if a root-page of another table is moved by the btree-layer whilst
-** erasing iTable (this can happen with an auto-vacuum database).
+** The RootStack object holds a list (really a Heap) of btree root pages
+** and schema numbers that need to be deleted using OP_Destroy.
+**
+** OP_Destroy opcodes must be issued in order of decreasing root page
+** numbers in order to avoid having auto-vacuum disrupt subsequent 
+** OP_Destroy opcodes.  For that reason, all pending OP_Destroy calls
+** are accumulated in an instance of this object.  Then at the end of
+** code generation, this object is used to generate the OP_Destroy opcodes
+** in decreasing order.
+**
+** The data structure is a max-heap.  Each rootpage/schema-number combo
+** is stored as a 64-bit integer, with the schema-number in the upper 32
+** bits and the page number in the lower 32-bits.  The root of the heap
+** (RootStack.a[0]) is the largest entry in the heap.  The children of
+** heap entry i are i*2+1 and i*2+2.  The heap always stays balanced by
+** ensuring that a parent entry is larger than both children.
+** 
 */
-static void destroyRootPage(Parse *pParse, int iTable, int iDb){
-  Vdbe *v = sqlite3GetVdbe(pParse);
-  int r1 = sqlite3GetTempReg(pParse);
-  if( iTable<2 ) sqlite3ErrorMsg(pParse, "corrupt schema");
-  sqlite3VdbeAddOp3(v, OP_Destroy, iTable, r1, iDb);
-  sqlite3MayAbort(pParse);
-#ifndef SQLITE_OMIT_AUTOVACUUM
-  /* OP_Destroy stores an in integer r1. If this integer
-  ** is non-zero, then it is the root page number of a table moved to
-  ** location iTable. The following code modifies the sqlite_schema table to
-  ** reflect this.
-  **
-  ** The "#NNN" in the SQL is a special constant that means whatever value
-  ** is in register NNN.  See grammar rules associated with the TK_REGISTER
-  ** token for additional information.
-  */
-  sqlite3NestedParse(pParse,
-     "UPDATE %Q." LEGACY_SCHEMA_TABLE
-     " SET rootpage=%d WHERE #%d AND rootpage=#%d",
-     pParse->db->aDb[iDb].zDbSName, iTable, r1, r1);
-#endif
-  sqlite3ReleaseTempReg(pParse, r1);
-}
+typedef struct RootStack RootStack;
+struct RootStack {
+  u32 nAlloc;   /* Slots allocated for a[] */
+  u32 nUsed;    /* Slots used for in a[] */
+  u64 *a;       /* Sorting heap. Each entry has iDb in the upper 32 bits and
+                ** a page number in the lower 32 bits */
+};
 
 /*
-** Write VDBE code to erase table pTab and all associated indices on disk.
-** Code to update the sqlite_schema tables and internal schema definitions
-** in case a root-page belonging to another table is moved by the btree layer
-** is also added (this can happen with an auto-vacuum database).
+** Add a new Pgno and iDb to the RootStack in question.
+**
+** The new entry is inserted at the of the heap (most distant child)
+** and then the heap is rebalanced.
 */
-static void destroyTable(Parse *pParse, Table *pTab){
-  /* If the database may be auto-vacuum capable (if SQLITE_OMIT_AUTOVACUUM
-  ** is not defined), then it is important to call OP_Destroy on the
-  ** table and index root-pages in order, starting with the numerically
-  ** largest root-page number. This guarantees that none of the root-pages
-  ** to be destroyed is relocated by an earlier OP_Destroy. i.e. if the
-  ** following were coded:
-  **
-  ** OP_Destroy 4 0
-  ** ...
-  ** OP_Destroy 5 0
-  **
-  ** and root page 5 happened to be the largest root-page number in the
-  ** database, then root page 5 would be moved to page 4 by the
-  ** "OP_Destroy 4 0" opcode. The subsequent "OP_Destroy 5 0" would hit
-  ** a free-list page.
-  */
-  Pgno iTab = pTab->tnum;
-  Pgno iDestroyed = 0;
-
-  while( 1 ){
-    Index *pIdx;
-    Pgno iLargest = 0;
-
-    if( iDestroyed==0 || iTab<iDestroyed ){
-      iLargest = iTab;
-    }
-    for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
-      Pgno iIdx = pIdx->tnum;
-      assert( pIdx->pSchema==pTab->pSchema );
-      if( (iDestroyed==0 || (iIdx<iDestroyed)) && iIdx>iLargest ){
-        iLargest = iIdx;
-      }
-    }
-    if( iLargest==0 ){
+static void rootStackPush(Parse *pParse, RootStack *p, Pgno pgno, int iDb){
+  u64 iNew;
+  int i, j;
+  if( p->nAlloc<p->nUsed+1 ){
+    p->nAlloc = p->nAlloc*2 + 12;
+    p->a = sqlite3DbRealloc(pParse->db, p->a, sizeof(Pgno)*p->nAlloc);
+    if( p->a==0 ){
+      p->nAlloc = p->nUsed = 0;
       return;
-    }else{
-      int iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
-      assert( iDb>=0 && iDb<pParse->db->nDb );
-      destroyRootPage(pParse, iLargest, iDb);
-      iDestroyed = iLargest;
     }
   }
+  assert( pgno>0 || CORRUPT_DB );
+  assert( iDb>=0 && iDb<pParse->db->nDb );
+  iNew = (((u64)iDb)<<32) | pgno;
+  i = p->nUsed++;
+  p->a[i] = iNew;
+  while( i>=1 && p->a[j = (i-1)/2]<p->a[i] ){
+    u64 tmp = p->a[i];
+    p->a[i] = p->a[j];
+    p->a[j] = tmp;
+    i = j;
+  }
+}
+
+/*
+** Remove the largest entry from the RootStack heap.  Rebalance the
+** heap and then return that largest entry.
+*/
+static u64 rootStackPop(RootStack *p){
+  u64 iMax;
+  int i, j;
+  assert( p->nUsed>0 );
+  assert( p->a!=0 );
+  iMax = p->a[0];
+  p->a[0] = p->a[--p->nUsed];
+  i = 0;
+  while( (j = i*2+1)<p->nUsed ){
+    u64 tmp;
+    if( j+1<p->nUsed && p->a[j+1]>p->a[j] ) j++;
+    if( p->a[i]>p->a[j] ) break;
+    tmp = p->a[i];
+    p->a[i] = p->a[j];
+    p->a[j] = tmp;
+    i = j;
+  }
+  return iMax;
+}
+
+/*
+** Generate OP_Destroy opcodes for every btree named in the given
+** RootStack object.  Issue these OP_Destroy opcodes in order of decreasing
+** root page number.  Then clean up any memory used by the RootStack.
+*/
+static void rootStackCode(Parse *pParse, RootStack *p){
+  int iLastDb = -1;
+  int r1 = sqlite3GetTempReg(pParse);
+  int r2 = sqlite3GetTempReg(pParse);
+  int regReturn = sqlite3GetTempReg(pParse);
+  Vdbe *v = pParse->pVdbe;
+  int addrSub = 0;
+  while( pParse->nErr==0 && p->nUsed>0 ){
+    u64 iNext = rootStackPop(p);
+    Pgno pgno = iNext & 0xffffffff;
+    int iDb = (iNext>>32)&0xffff;
+    if( pgno<2 ) sqlite3ErrorMsg(pParse, "corrupt schema");
+    sqlite3MayAbort(pParse);
+    if( iDb!=iLastDb ){
+      /* Code a subroutine to that will update the schema table when
+      ** a root page number changes.  The old root page is in register r1.
+      ** Root page is moved to the value in register r2. */
+      iLastDb = iDb;
+      sqlite3VdbeAddOp0(v, OP_Goto);
+      addrSub = sqlite3VdbeCurrentAddr(v);
+      sqlite3NestedParse(pParse,
+         "UPDATE %Q." LEGACY_SCHEMA_TABLE
+         " SET rootpage=#%d WHERE rootpage=#%d",
+         pParse->db->aDb[iDb].zDbSName, r2, r1);
+      sqlite3VdbeAddOp1(v, OP_Return, regReturn);
+      sqlite3VdbeJumpHere(v, addrSub-1);
+    }
+    sqlite3VdbeAddOp3(v, OP_Destroy, pgno, r1, iDb);
+    sqlite3VdbeAddOp2(v, OP_IfNot, r1, sqlite3VdbeCurrentAddr(v)+3);
+    VdbeCoverage(v);
+    sqlite3VdbeAddOp2(v, OP_Integer, pgno, r2);
+    sqlite3VdbeAddOp2(v, OP_Gosub, regReturn, addrSub);
+  }
+  sqlite3ReleaseTempReg(pParse, r1);
+  sqlite3ReleaseTempReg(pParse, regReturn);
+  sqlite3DbFree(pParse->db, p->a);
 }
 
 /*
@@ -3367,7 +3410,13 @@ static void sqlite3ClearStatTables(
 /*
 ** Generate code to drop a table.
 */
-void sqlite3CodeDropTable(Parse *pParse, Table *pTab, int iDb, int isView){
+static void sqlite3CodeDropTable(
+  Parse *pParse,         /* Parsing context */
+  RootStack *pStack,     /* List of pending OP_Destroys */
+  Table *pTab,           /* Table to be dropped */
+  int iDb,               /* Schema for pTab */
+  int isView             /* True if pTab is a VIEW */
+){
   Vdbe *v;
   sqlite3 *db = pParse->db;
   Trigger *pTrigger;
@@ -3420,8 +3469,13 @@ void sqlite3CodeDropTable(Parse *pParse, Table *pTab, int iDb, int isView){
       "DELETE FROM %Q." LEGACY_SCHEMA_TABLE
       " WHERE tbl_name=%Q and type!='trigger'",
       pDb->zDbSName, pTab->zName);
-  if( !isView && !IsVirtual(pTab) ){
-    destroyTable(pParse, pTab);
+  if( IsOrdinaryTable(pTab) ){
+    Index *pIdx;
+    rootStackPush(pParse, pStack, pTab->tnum, iDb);
+    for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+      if( pIdx->tnum==pTab->tnum ) continue;
+      rootStackPush(pParse, pStack, pIdx->tnum, iDb);
+    }
   }
 
   /* Remove the table entry from SQLite's internal schema and modify
@@ -3480,7 +3534,9 @@ void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, int noErr){
   sqlite3 *db = pParse->db;
   int iDb;
   int ii, jj;
+  RootStack rootStack;
 
+  memset(&rootStack, 0, sizeof(rootStack));
   (void)sqlite3GetVdbe(pParse);
   sqlite3ReadSchema(pParse);
   assert( pName!=0 || pParse->nErr!=0 );
@@ -3596,22 +3652,14 @@ void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, int noErr){
   /* Generate code to actually delete the tables/views in a second pass. 
   ** Btrees must be deleted largest root page first, to avoid problems
   ** caused by autovacuum page reordering. */
-  while( pParse->nErr==0 ){
-    int iBest = 0;
-    for(ii=jj=0; ii<pName->nSrc; ii++){
-      if( pName->a[ii].regReturn<=0 ) continue;
-      if( pName->a[ii].regReturn>iBest ){
-        jj = ii;
-        iBest = pName->a[ii].regReturn;
-      }
-    }
-    if( iBest==0 ) break;
-    pTab = pName->a[jj].pTab;
-    pName->a[jj].regReturn = 0;
+  for(ii=0; ii<pName->nSrc; ii++){
+    pTab = pName->a[ii].pTab;
+    if( pTab==0 ) continue;
     iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
-    sqlite3CodeDropTable(pParse, pTab, iDb, isView);
+    sqlite3CodeDropTable(pParse, &rootStack, pTab, iDb, isView);
   }
   sqlite3SrcListDelete(db, pName);
+  rootStackCode(pParse, &rootStack);
 }
 
 /*
@@ -4615,7 +4663,9 @@ void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists){
   Vdbe *v;
   int iDb;
   int ii, jj;
+  RootStack rootStack;
 
+  memset(&rootStack, 0, sizeof(rootStack));
   v = sqlite3GetVdbe(pParse);
   sqlite3ReadSchema(pParse);
   assert( pName!=0 || pParse->nErr!=0 );
@@ -4666,11 +4716,8 @@ void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists){
     for(jj=ii-1; jj>=0 && pName->a[jj].u2.pIdx!=pIndex; jj--){}
     if( jj>=0 ) continue;
 
-    /* Record that this index needs to be dropped.  Store the root page
-    ** number in SrcItem.regReturn so that indexes can be dropped largest
-    ** first to avoid problems with autovacuum reordering. */
+    /* Record that this index needs to be dropped. */
     pName->a[ii].u2.pIdx = pIndex;
-    pName->a[ii].regReturn = pIndex->tnum;
 
     /* Generate code to remove the index and from the schema table and
     ** from sqlite_statN tables */
@@ -4681,28 +4728,13 @@ void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists){
        db->aDb[iDb].zDbSName, pIndex->zName
     );
     sqlite3ClearStatTables(pParse, iDb, "idx", pIndex->zName);
-  }
 
-  /* Drop the indexes.  Drop the ones with the largest root page first
-  ** to avoid problems with autovacuum. */
-  while( pParse->nErr==0 ){
-    int iBest = 0;
-    jj = 0;
-    for(ii=0; ii<pName->nSrc; ii++){
-      if( pName->a[ii].regReturn>iBest ){
-        jj = ii;
-        iBest = pName->a[ii].regReturn;
-      }
-    }
-    if( iBest<=0 ) break;
-    pIndex = pName->a[jj].u2.pIdx;
-    pName->a[jj].regReturn = 0;
-    iDb = sqlite3SchemaToIndex(db, pIndex->pSchema);
+    rootStackPush(pParse, &rootStack, pIndex->tnum, iDb);
     sqlite3ChangeCookie(pParse, iDb);
-    destroyRootPage(pParse, pIndex->tnum, iDb);
     sqlite3VdbeAddOp4(v, OP_DropIndex, iDb, 0, 0, pIndex->zName, 0);
   }
   sqlite3SrcListDelete(db, pName);
+  rootStackCode(pParse, &rootStack);
 }
 
 /*
index fd80d2827a9d5ec26ac120cf629d5c820e9de4f1..46254ed44e539e563553c96e54ca95c2ded14905 100644 (file)
@@ -4928,7 +4928,6 @@ void sqlite3CreateView(Parse*,Token*,Token*,Token*,ExprList*,Select*,int,int);
   int sqlite3DbMaskAllZero(yDbMask);
 #endif
 void sqlite3DropTable(Parse*, SrcList*, int, int);
-void sqlite3CodeDropTable(Parse*, Table*, int, int);
 void sqlite3DeleteTable(sqlite3*, Table*);
 void sqlite3DeleteTableGeneric(sqlite3*, void*);
 void sqlite3FreeIndex(sqlite3*, Index*);