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