Blob Blame History Raw
Subject: [PATCH] In defensive mode, do not allow shadow tables to be renamed 
 using ALTER TABLE and do not allow shadow tables to be dropped.

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