c11ca1
Subject: [PATCH] In defensive mode, do not allow shadow tables to be renamed 
c11ca1
 using ALTER TABLE and do not allow shadow tables to be dropped.
c11ca1
c11ca1
diff --git a/src/alter.c b/src/alter.c
c11ca1
index 0fa24c0..707472a 100644
c11ca1
--- a/src/alter.c
c11ca1
+++ b/src/alter.c
c11ca1
@@ -28,9 +28,16 @@
c11ca1
 **
c11ca1
 ** Or, if zName is not a system table, zero is returned.
c11ca1
 */
c11ca1
-static int isSystemTable(Parse *pParse, const char *zName){
c11ca1
-  if( 0==sqlite3StrNICmp(zName, "sqlite_", 7) ){
c11ca1
-    sqlite3ErrorMsg(pParse, "table %s may not be altered", zName);
c11ca1
+static int isAlterableTable(Parse *pParse, Table *pTab){
c11ca1
+  if( 0==sqlite3StrNICmp(pTab->zName, "sqlite_", 7) 
c11ca1
+#ifndef SQLITE_OMIT_VIRTUALTABLE
c11ca1
+   || ( (pTab->tabFlags & TF_Shadow) 
c11ca1
+     && (pParse->db->flags & SQLITE_Defensive)
c11ca1
+     && pParse->db->nVdbeExec==0
c11ca1
+   )
c11ca1
+#endif
c11ca1
+  ){
c11ca1
+    sqlite3ErrorMsg(pParse, "table %s may not be altered", pTab->zName);
c11ca1
     return 1;
c11ca1
   }
c11ca1
   return 0;
c11ca1
@@ -129,7 +136,7 @@ void sqlite3AlterRenameTable(
c11ca1
   /* Make sure it is not a system table being altered, or a reserved name
c11ca1
   ** that the table is being renamed to.
c11ca1
   */
c11ca1
-  if( SQLITE_OK!=isSystemTable(pParse, pTab->zName) ){
c11ca1
+  if( SQLITE_OK!=isAlterableTable(pParse, pTab) ){
c11ca1
     goto exit_rename_table;
c11ca1
   }
c11ca1
   if( SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){ goto
c11ca1
@@ -427,7 +434,7 @@ void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){
c11ca1
     sqlite3ErrorMsg(pParse, "Cannot add a column to a view");
c11ca1
     goto exit_begin_add_column;
c11ca1
   }
c11ca1
-  if( SQLITE_OK!=isSystemTable(pParse, pTab->zName) ){
c11ca1
+  if( SQLITE_OK!=isAlterableTable(pParse, pTab) ){
c11ca1
     goto exit_begin_add_column;
c11ca1
   }
c11ca1
 
c11ca1
@@ -529,7 +536,7 @@ void sqlite3AlterRenameColumn(
c11ca1
   if( !pTab ) goto exit_rename_column;
c11ca1
 
c11ca1
   /* Cannot alter a system table */
c11ca1
-  if( SQLITE_OK!=isSystemTable(pParse, pTab->zName) ) goto exit_rename_column;
c11ca1
+  if( SQLITE_OK!=isAlterableTable(pParse, pTab) ) goto exit_rename_column;
c11ca1
   if( SQLITE_OK!=isRealTable(pParse, pTab) ) goto exit_rename_column;
c11ca1
 
c11ca1
   /* Which schema holds the table to be altered */  
c11ca1
diff --git a/src/build.c b/src/build.c
c11ca1
index 1dc2614..3412670 100644
c11ca1
--- a/src/build.c
c11ca1
+++ b/src/build.c
c11ca1
@@ -2661,6 +2661,22 @@ void sqlite3CodeDropTable(Parse *pParse, Table *pTab, int iDb, int isView){
c11ca1
   sqliteViewResetAll(db, iDb);
c11ca1
 }
c11ca1
 
c11ca1
+/*
c11ca1
+** Return true if it is not allowed to drop the given table
c11ca1
+*/
c11ca1
+static int tableMayNotBeDropped(Parse *pParse, Table *pTab){
c11ca1
+  if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 ){
c11ca1
+    if( sqlite3StrNICmp(pTab->zName+7, "stat", 4)==0 ) return 0;
c11ca1
+    if( sqlite3StrNICmp(pTab->zName+7, "parameters", 10)==0 ) return 0;
c11ca1
+    return 1;
c11ca1
+  }
c11ca1
+  if( pTab->tabFlags & TF_Shadow ){
c11ca1
+    sqlite3 *db = pParse->db;
c11ca1
+    if( (db->flags & SQLITE_Defensive)!=0 && db->nVdbeExec==0 ) return 1;
c11ca1
+  }
c11ca1
+  return 0;
c11ca1
+}
c11ca1
+
c11ca1
 /*
c11ca1
 ** This routine is called to do the work of a DROP TABLE statement.
c11ca1
 ** pName is the name of the table to be dropped.
c11ca1
@@ -2730,8 +2746,7 @@ void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, int noErr){
c11ca1
     }
c11ca1
   }
c11ca1
 #endif
c11ca1
-  if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 
c11ca1
-    && sqlite3StrNICmp(pTab->zName, "sqlite_stat", 11)!=0 ){
c11ca1
+  if( tableMayNotBeDropped(pParse, pTab) ){
c11ca1
     sqlite3ErrorMsg(pParse, "table %s may not be dropped", pTab->zName);
c11ca1
     goto exit_drop_table;
c11ca1
   }
c11ca1
diff --git a/test/altertab.test b/test/altertab.test
c11ca1
index a364207..891b081 100644
c11ca1
--- a/test/altertab.test
c11ca1
+++ b/test/altertab.test
c11ca1
@@ -505,5 +505,62 @@ do_execsql_test 15.5 {
c11ca1
   SELECT sql FROM sqlite_master WHERE name = 'y';
c11ca1
 } {{CREATE VIEW y AS SELECT f2 AS f1 FROM x}}
c11ca1
 
c11ca1
+#-------------------------------------------------------------------------
c11ca1
+# Test that it is not possible to rename a shadow table in DEFENSIVE mode.
c11ca1
+#
c11ca1
+ifcapable fts3 {
c11ca1
+  proc vtab_command {method args} {
c11ca1
+    switch -- $method {
c11ca1
+      xConnect {
c11ca1
+        if {[info exists ::vtab_connect_sql]} {
c11ca1
+          execsql $::vtab_connect_sql
c11ca1
+        }
c11ca1
+        return "CREATE TABLE t1(a, b, c)"
c11ca1
+      }
c11ca1
+
c11ca1
+      xBestIndex {
c11ca1
+        set clist [lindex $args 0]
c11ca1
+        if {[llength $clist]!=1} { error "unexpected constraint list" }
c11ca1
+        catch { array unset C }
c11ca1
+        array set C [lindex $clist 0]
c11ca1
+        if {$C(usable)} {
c11ca1
+          return "omit 0 cost 0 rows 1 idxnum 555 idxstr eq!"
c11ca1
+        } else {
c11ca1
+          return "cost 1000000 rows 0 idxnum 0 idxstr scan..."
c11ca1
+        }
c11ca1
+      }
c11ca1
+    }
c11ca1
+
c11ca1
+    return {}
c11ca1
+  }
c11ca1
+
c11ca1
+  register_tcl_module db
c11ca1
+
c11ca1
+  sqlite3_db_config db DEFENSIVE 1
c11ca1
+
c11ca1
+  do_execsql_test 16.0 {
c11ca1
+    CREATE VIRTUAL TABLE y1 USING fts3;
c11ca1
+  }
c11ca1
+
c11ca1
+  do_catchsql_test 16.10 {
c11ca1
+    INSERT INTO y1_segments VALUES(1, X'1234567890');
c11ca1
+  } {1 {table y1_segments may not be modified}}
c11ca1
+
c11ca1
+  do_catchsql_test 16.20 {
c11ca1
+    ALTER TABLE y1_segments RENAME TO abc;
c11ca1
+  } {1 {table y1_segments may not be altered}}
c11ca1
+
c11ca1
+  do_catchsql_test 16.21 {
c11ca1
+    DROP TABLE y1_segments;
c11ca1
+  } {1 {table y1_segments may not be dropped}}
c11ca1
+
c11ca1
+  do_execsql_test 16.30 {
c11ca1
+    ALTER TABLE y1 RENAME TO z1;
c11ca1
+  }
c11ca1
+
c11ca1
+  do_execsql_test 16.40 {
c11ca1
+    SELECT * FROM z1_segments;
c11ca1
+  }
c11ca1
+}
c11ca1
 
c11ca1
 finish_test