]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Automatically turn off DEFENSIVE mode in the shell tool when executing scripts genera...
authordan <Dan Kennedy>
Mon, 8 Jan 2024 18:46:34 +0000 (18:46 +0000)
committerdan <Dan Kennedy>
Mon, 8 Jan 2024 18:46:34 +0000 (18:46 +0000)
FossilOrigin-Name: 6e9e96b7e7afb9420110f4b93d10b945c9eadfde5e9c81e59ae9ee8167e75707

manifest
manifest.uuid
src/shell.c.in
test/shell9.test [new file with mode: 0644]
test/tester.tcl

index f23ffb83cbca898d2a4d179e82f6b6b59b657377..d0a37e2c4b29bec1d7ca86aa4ebb54106390d260 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Minor\schange\sto\sos_unix.c\sto\sfacilitate\s100%\sMC/DC\stesting.
-D 2024-01-08T15:23:45.210
+C Automatically\sturn\soff\sDEFENSIVE\smode\sin\sthe\sshell\stool\swhen\sexecuting\sscripts\sgenerated\sby\sthe\s".dump"\scommand\sagainst\san\sempty\sdatabase.\sAdd\sa\swarning\sto\sthe\stop\sof\sgenerated\s".dump"\sscripts\sthat\spopulate\svirtual\stables.
+D 2024-01-08T18:46:34.303
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -738,7 +738,7 @@ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
 F src/resolve.c e25f51a473a5f30a0d978e4df2aaa98aeec84eac29ecae1ad4708a6c3e669345
 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97
 F src/select.c f1a81ff4f8e9e76c224e2ab3a4baa799add0db22158c7fcede65d8cc4a6fa2da
-F src/shell.c.in 85f8d52fa4f7773823736dd39d0a268fd739207fcae95883c9ec8ce4af59f7df
+F src/shell.c.in 3d19abd924ed1cec9c9908d5a10cb1580b8ca30df24c26bfe80efa0c00f664d8
 F src/sqlite.h.in 61a60b4ea04db8ead15e1579b20b64cb56e9f55d52c5f9f9694de630110593a3
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h 3f046c04ea3595d6bfda99b781926b17e672fd6d27da2ba6d8d8fc39981dcb54
@@ -1588,6 +1588,7 @@ F test/shell5.test c8b6c54f26ec537f8558273d7ed293ca3725ef42e6b12b8f151718628bd14
 F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3
 F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f
 F test/shell8.test 3fd093d481aaa94dc77fb73f1044c1f19c7efe3477a395cc4f7450133bc54915
+F test/shell9.test e540b457297efcd737a84505c23367ed6eb5d2f239a5e1901d89518a9f794c67
 F test/shmlock.test 3dbf017d34ab0c60abe6a44e447d3552154bd0c87b41eaf5ceacd408dd13fda5
 F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3
 F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5
@@ -1665,7 +1666,7 @@ F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30
 F test/temptable2.test 76821347810ecc88203e6ef0dd6897b6036ac788e9dd3e6b04fd4d1631311a16
 F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637
 F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc
-F test/tester.tcl 68454ef88508c196d19e8694daa27bff7107a91857799eaa12f417188ae53ede
+F test/tester.tcl 6f6e53981b4bdd42ef088f52e23236bc1ba0ca41ed395cbd7f33cbcff7d74d3c
 F test/testrunner.tcl 8e2a5c7550b78d3283eee6103104ae2bcf56aa1df892dbd1608f27b93ebf4de8
 F test/testrunner_data.tcl 7ffd951527bbc614e723fd8d123b6834321878530696adecfdf6035100bac64e
 F test/thread001.test a0985c117eab62c0c65526e9fa5d1360dd1cac5b03bde223902763274ce21899
@@ -2156,8 +2157,11 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P a8e9af1356f5fb2ec460f932dfbe89283bb4e3cf9fa677d1acdbe77ffa11dd04
-R 37733634dd468dc6ea6d1e6aa3b50fba
-U drh
-Z 78db706fe3fe0ce3a2f3eeab5f6bda34
+P 0dfa7b4da134db281c3c4eddb4569c53a450f955f0af2f410e13db801aff4ea2
+R 42cc9dd174017b0dfd22a357c6d362d7
+T *branch * shell-dump-fix
+T *sym-shell-dump-fix *
+T -sym-trunk *
+U dan
+Z e611ff595823ddf49b515f1d4f66f784
 # Remove this line to create a well-formed Fossil manifest.
index e20eecb71343557e061ea72e373b47010c34e416..289ed4a4fb43a52484138a2a68d5f1143ce417be 100644 (file)
@@ -1 +1 @@
-0dfa7b4da134db281c3c4eddb4569c53a450f955f0af2f410e13db801aff4ea2
\ No newline at end of file
+6e9e96b7e7afb9420110f4b93d10b945c9eadfde5e9c81e59ae9ee8167e75707
\ No newline at end of file
index 8f83890525467327c6ca12d720fd3a82f9613e66..19574dc79c9566f7dc863a6bce87299c84dbbef2 100644 (file)
@@ -1292,6 +1292,7 @@ struct ShellState {
   u8 eTraceType;         /* SHELL_TRACE_* value for type of trace */
   u8 bSafeMode;          /* True to prohibit unsafe operations */
   u8 bSafeModePersist;   /* The long-term value of bSafeMode */
+  u8 eRestoreState;      /* See comments above doAutoDetectRestore() */
   ColModeOpts cmOpts;    /* Option values affecting columnar mode output */
   unsigned statsOn;      /* True to display memory stats before each finalize */
   unsigned mEqpLines;    /* Mask of vertical lines in the EQP output graph */
@@ -6731,7 +6732,6 @@ static int lintDotCommand(
   return SQLITE_ERROR;
 }
 
-#if !defined SQLITE_OMIT_VIRTUALTABLE
 static void shellPrepare(
   sqlite3 *db,
   int *pRc,
@@ -6750,12 +6750,8 @@ static void shellPrepare(
 
 /*
 ** Create a prepared statement using printf-style arguments for the SQL.
-**
-** This routine is could be marked "static".  But it is not always used,
-** depending on compile-time options.  By omitting the "static", we avoid
-** nuisance compiler warnings about "defined but not used".
 */
-void shellPreparePrintf(
+static void shellPreparePrintf(
   sqlite3 *db,
   int *pRc,
   sqlite3_stmt **ppStmt,
@@ -6778,13 +6774,10 @@ void shellPreparePrintf(
   }
 }
 
-/* Finalize the prepared statement created using shellPreparePrintf().
-**
-** This routine is could be marked "static".  But it is not always used,
-** depending on compile-time options.  By omitting the "static", we avoid
-** nuisance compiler warnings about "defined but not used".
+/* 
+** Finalize the prepared statement created using shellPreparePrintf().
 */
-void shellFinalize(
+static void shellFinalize(
   int *pRc,
   sqlite3_stmt *pStmt
 ){
@@ -6800,6 +6793,7 @@ void shellFinalize(
   }
 }
 
+#if !defined SQLITE_OMIT_VIRTUALTABLE
 /* Reset the prepared statement created using shellPreparePrintf().
 **
 ** This routine is could be marked "static".  But it is not always used,
@@ -7866,6 +7860,30 @@ FROM (\
   }
 }
 
+/*
+** Check if the sqlite_schema table contains one or more virtual tables. If
+** parameter zLike is not NULL, then it is an SQL expression that the
+** sqlite_schema row must also match. If one or more such rows are found,
+** print the following warning to the output:
+**
+** WARNING: Script requires that SQLITE_DBCONFIG_DEFENSIVE be disabled
+*/
+static int outputDumpWarning(ShellState *p, const char *zLike){
+  int rc = SQLITE_OK;
+  sqlite3_stmt *pStmt = 0;
+  shellPreparePrintf(p->db, &rc, &pStmt,
+    "SELECT 1 FROM sqlite_schema o WHERE "
+    "sql LIKE 'CREATE VIRTUAL TABLE%%' AND %s", zLike ? zLike : "true"
+  );
+  if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
+    oputz("/* WARNING: "
+          "Script requires that SQLITE_DBCONFIG_DEFENSIVE be disabled */\n"
+    );
+  }
+  shellFinalize(&rc, pStmt);
+  return rc;
+}
+
 /*
 ** If an input line begins with "." then invoke this routine to
 ** process that line.
@@ -8328,6 +8346,7 @@ static int do_meta_command(char *zLine, ShellState *p){
 
     open_db(p, 0);
 
+    outputDumpWarning(p, zLike);
     if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
       /* When playing back a "dump", the content might appear in an order
       ** which causes immediate foreign key constraints to be violated.
@@ -11391,6 +11410,80 @@ static int line_is_complete(char *zSql, int nSql){
   return rc;
 }
 
+/*
+** This function is called after processing each line of SQL in the
+** runOneSqlLine() function. Its purpose is to detect scenarios where
+** defensive mode should be automatically turned off. Specifically, when
+**
+**   1. The first line of input is "PRAGMA foreign_keys=OFF;",
+**   2. The second line of input is "BEGIN TRANSACTION;",
+**   3. The database is empty, and
+**   4. The shell is not running in --safe mode.
+** 
+** The implementation uses the ShellState.eRestoreState to maintain state:
+**
+**    0: Have not seen any SQL.
+**    1: Have seen "PRAGMA foreign_keys=OFF;".
+**    2: Currently assuming we are parsing ".dump" restore, defensive mode
+**       should be disabled following the current transaction.
+**    3: Nothing left to do.
+*/
+static int doAutoDetectRestore(ShellState *p, const char *zSql){
+  int rc = SQLITE_OK;
+
+  switch( p->eRestoreState ){
+    case 0: {
+      int bDefense = 0;           /* True if in defensive mode */ 
+      const char *zExpect = "PRAGMA foreign_keys=OFF;";
+      assert( strlen(zExpect)==24 );
+      sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, -1, &bDefense);
+      if( p->bSafeMode==0 && bDefense && memcmp(zSql, zExpect, 25)==0 ){
+        p->eRestoreState = 1;
+      }else{
+        p->eRestoreState = 3;
+      }
+      break;
+    };
+
+    case 1: {
+      const char *zExpect = "BEGIN TRANSACTION;";
+      assert( strlen(zExpect)==18 );
+      if( memcmp(zSql, zExpect, 19)==0 ){
+        /* Now check if the database is empty. */
+        const char *zQuery = "SELECT 1 FROM sqlite_schema LIMIT 1";
+        sqlite3_stmt *pStmt = 0;
+        int bEmpty = 1;
+
+        shellPrepare(p->db, &rc, zQuery, &pStmt);
+        if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
+          bEmpty = 0;
+        }
+        shellFinalize(&rc, pStmt);
+        if( bEmpty && rc==SQLITE_OK ){
+          sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, 0, 0);
+        }else{
+          p->eRestoreState = 3;
+        }
+      }
+      break;
+    }
+
+    case 2: {
+      if( sqlite3_get_autocommit(p->db) ){
+        sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, 0, 0);
+        p->eRestoreState = 3;
+      }
+      break;
+    }
+
+    default: /* Nothing to do */
+      assert( p->eRestoreState==3 );
+      break;
+  }
+
+  return rc;
+}
+
 /*
 ** Run a single line of SQL.  Return the number of errors.
 */
@@ -11438,6 +11531,8 @@ static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){
             sqlite3_changes64(p->db), sqlite3_total_changes64(p->db));
     oputf("%s\n", zLineBuf);
   }
+
+  if( doAutoDetectRestore(p, zSql) ) return 1;
   return 0;
 }
 
diff --git a/test/shell9.test b/test/shell9.test
new file mode 100644 (file)
index 0000000..798a989
--- /dev/null
@@ -0,0 +1,127 @@
+# 2009 Nov 11
+#
+# The author disclaims copyright to this source code.  In place of
+# a legal notice, here is a blessing:
+#
+#    May you do good and not evil.
+#    May you find forgiveness for yourself and forgive others.
+#    May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# The focus of this file is testing the CLI shell tool. Specifically, 
+# testing that it is possible to run a ".dump" script that creates
+# virtual tables without explicitly disabling defensive mode.
+#
+
+# Test plan:
+#
+#   shell1-1.*: Basic command line option handling.
+#   shell1-2.*: Basic "dot" command token parsing.
+#   shell1-3.*: Basic test that "dot" command can be called.
+#   shell1-{4-8}.*: Test various "dot" commands's functionality.
+#   shell1-9.*: Basic test that "dot" commands and SQL intermix ok.
+#
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set CLI [test_cli_invocation]
+
+set ::testprefix shell9
+
+ifcapable !fts5 {
+  finish_test
+  return
+}
+
+#----------------------------------------------------------------------------
+# Test cases shell9-1.* verify that scripts output by .dump may be parsed
+# by the shell tool without explicitly disabling DEFENSIVE mode, unless
+# the shell is in safe mode.
+#
+do_execsql_test 1.0 {
+  CREATE VIRTUAL TABLE t1 USING fts5(a, b, c);
+  INSERT INTO t1 VALUES('one', 'two', 'three');
+}
+db close
+
+# Create .dump file in "testdump.txt".
+#
+set out [open testdump.txt w]
+puts $out [lindex [catchcmd test.db .dump] 1]
+close $out
+
+# Check testdump.txt can be processed if the initial db is empty.
+#
+do_test 1.1.1 {
+  forcedelete test.db
+  catchcmd test.db ".read testdump.txt"
+} {0 {}}
+sqlite3 db test.db
+do_execsql_test 1.1.2 {
+  SELECT * FROM t1;
+} {one two three}
+
+# Check testdump.txt cannot be processed if the initial db is not empty.
+#
+reset_db
+do_execsql_test 1.2.1 {
+  CREATE TABLE t4(hello);
+}
+db close
+do_test 1.2.2 {
+  catchcmd test.db ".read testdump.txt"
+} {1 {Parse error near line 5: table sqlite_master may not be modified}}
+
+# Check testdump.txt cannot be processed if the db is in safe mode
+#
+do_test 1.3.1 {
+  forcedelete test.db
+  catchsafecmd test.db ".read testdump.txt"
+} {1 {line 1: cannot run .read in safe mode}}
+do_test 1.3.2 {
+  set fd [open testdump.txt]
+  set script [read $fd]
+  close $fd
+  forcedelete test.db
+  catchsafecmd test.db $script
+} {1 {Parse error near line 5: table sqlite_master may not be modified}}
+do_test 1.3.3 {
+  # Quick check that the above would have worked but for safe mode.
+  forcedelete test.db
+  catchcmd test.db $script
+} {0 {}}
+
+#----------------------------------------------------------------------------
+# Test cases shell9-2.* verify that a warning is printed at the top of
+# .dump scripts that contain virtual tables.
+#
+proc contains_warning {text} {
+  return [string match "*WARNING: Script requires that*" $text]
+}
+
+reset_db
+do_execsql_test 2.0.1 {
+  CREATE TABLE t1(x);
+  CREATE TABLE t2(y);
+  INSERT INTO t1 VALUES('one');
+  INSERT INTO t2 VALUES('two');
+}
+do_test 2.0.2 {
+  contains_warning [catchcmd test.db .dump]
+} 0
+
+do_execsql_test 2.1.1 {
+  CREATE virtual TABLE r1 USING fts5(x);
+}
+do_test 2.1.2 {
+  contains_warning [catchcmd test.db .dump]
+} 1
+
+do_test 2.2.1 {
+  contains_warning [catchcmd test.db ".dump t1"]
+} 0
+do_test 2.2.2 {
+  contains_warning [catchcmd test.db ".dump r1"]
+} 1
+
+finish_test
index 021830aa95c70878f1baf9aed359696fbba11434..5754d7037edc57971f72877db428f3cdfc67165c 100644 (file)
@@ -884,6 +884,15 @@ proc catchcmd {db {cmd ""}} {
   set rc [catch { eval $line } msg]
   list $rc $msg
 }
+proc catchsafecmd {db {cmd ""}} {
+  global CLI
+  set out [open cmds.txt w]
+  puts $out $cmd
+  close $out
+  set line "exec $CLI -safe $db < cmds.txt"
+  set rc [catch { eval $line } msg]
+  list $rc $msg
+}
 
 proc catchcmdex {db {cmd ""}} {
   global CLI