Skip to content

Commit e452bf4

Browse files
dandan
authored andcommitted
Automatically turn off DEFENSIVE mode in the shell tool when executing scripts generated by the ".dump" command against an empty database. Add a warning to the top of generated ".dump" scripts that populate virtual tables.
FossilOrigin-Name: cd016f26bb61549a304f2148035e050f76a8f4a35cdb7131bba2f5fc5d09f49e
2 parents 3e2ffbd + 62f0c4d commit e452bf4

5 files changed

Lines changed: 254 additions & 21 deletions

File tree

manifest

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
C Minor\schange\sto\sos_unix.c\sto\sfacilitate\s100%\sMC/DC\stesting.
2-
D 2024-01-08T15:23:45.210
1+
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.
2+
D 2024-01-08T19:55:40.348
33
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
44
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
55
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -738,7 +738,7 @@ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
738738
F src/resolve.c e25f51a473a5f30a0d978e4df2aaa98aeec84eac29ecae1ad4708a6c3e669345
739739
F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97
740740
F src/select.c f1a81ff4f8e9e76c224e2ab3a4baa799add0db22158c7fcede65d8cc4a6fa2da
741-
F src/shell.c.in 85f8d52fa4f7773823736dd39d0a268fd739207fcae95883c9ec8ce4af59f7df
741+
F src/shell.c.in 3d19abd924ed1cec9c9908d5a10cb1580b8ca30df24c26bfe80efa0c00f664d8
742742
F src/sqlite.h.in 61a60b4ea04db8ead15e1579b20b64cb56e9f55d52c5f9f9694de630110593a3
743743
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
744744
F src/sqlite3ext.h 3f046c04ea3595d6bfda99b781926b17e672fd6d27da2ba6d8d8fc39981dcb54
@@ -1588,6 +1588,7 @@ F test/shell5.test c8b6c54f26ec537f8558273d7ed293ca3725ef42e6b12b8f151718628bd14
15881588
F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3
15891589
F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f
15901590
F test/shell8.test 3fd093d481aaa94dc77fb73f1044c1f19c7efe3477a395cc4f7450133bc54915
1591+
F test/shell9.test fe41f890a89f57bdde7351d7a828a1d4b503ebe239d413137369f20faf7f8865
15911592
F test/shmlock.test 3dbf017d34ab0c60abe6a44e447d3552154bd0c87b41eaf5ceacd408dd13fda5
15921593
F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3
15931594
F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5
@@ -1665,7 +1666,7 @@ F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30
16651666
F test/temptable2.test 76821347810ecc88203e6ef0dd6897b6036ac788e9dd3e6b04fd4d1631311a16
16661667
F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637
16671668
F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc
1668-
F test/tester.tcl 68454ef88508c196d19e8694daa27bff7107a91857799eaa12f417188ae53ede
1669+
F test/tester.tcl 6f6e53981b4bdd42ef088f52e23236bc1ba0ca41ed395cbd7f33cbcff7d74d3c
16691670
F test/testrunner.tcl 8e2a5c7550b78d3283eee6103104ae2bcf56aa1df892dbd1608f27b93ebf4de8
16701671
F test/testrunner_data.tcl 7ffd951527bbc614e723fd8d123b6834321878530696adecfdf6035100bac64e
16711672
F test/thread001.test a0985c117eab62c0c65526e9fa5d1360dd1cac5b03bde223902763274ce21899
@@ -2156,8 +2157,9 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
21562157
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
21572158
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
21582159
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
2159-
P a8e9af1356f5fb2ec460f932dfbe89283bb4e3cf9fa677d1acdbe77ffa11dd04
2160-
R 37733634dd468dc6ea6d1e6aa3b50fba
2161-
U drh
2162-
Z 78db706fe3fe0ce3a2f3eeab5f6bda34
2160+
P 0dfa7b4da134db281c3c4eddb4569c53a450f955f0af2f410e13db801aff4ea2 c82da712113d5dcd63b764dbc68842026989627abc840acb4a33f3a4972b832a
2161+
R 546d683a554613be241caf7c62f4af75
2162+
T +closed c82da712113d5dcd63b764dbc68842026989627abc840acb4a33f3a4972b832a
2163+
U dan
2164+
Z 4d6951c4af3d99936a8684cb8259f448
21632165
# Remove this line to create a well-formed Fossil manifest.

manifest.uuid

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0dfa7b4da134db281c3c4eddb4569c53a450f955f0af2f410e13db801aff4ea2
1+
cd016f26bb61549a304f2148035e050f76a8f4a35cdb7131bba2f5fc5d09f49e

src/shell.c.in

Lines changed: 107 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,6 +1292,7 @@ struct ShellState {
12921292
u8 eTraceType; /* SHELL_TRACE_* value for type of trace */
12931293
u8 bSafeMode; /* True to prohibit unsafe operations */
12941294
u8 bSafeModePersist; /* The long-term value of bSafeMode */
1295+
u8 eRestoreState; /* See comments above doAutoDetectRestore() */
12951296
ColModeOpts cmOpts; /* Option values affecting columnar mode output */
12961297
unsigned statsOn; /* True to display memory stats before each finalize */
12971298
unsigned mEqpLines; /* Mask of vertical lines in the EQP output graph */
@@ -6731,7 +6732,6 @@ static int lintDotCommand(
67316732
return SQLITE_ERROR;
67326733
}
67336734

6734-
#if !defined SQLITE_OMIT_VIRTUALTABLE
67356735
static void shellPrepare(
67366736
sqlite3 *db,
67376737
int *pRc,
@@ -6750,12 +6750,8 @@ static void shellPrepare(
67506750

67516751
/*
67526752
** Create a prepared statement using printf-style arguments for the SQL.
6753-
**
6754-
** This routine is could be marked "static". But it is not always used,
6755-
** depending on compile-time options. By omitting the "static", we avoid
6756-
** nuisance compiler warnings about "defined but not used".
67576753
*/
6758-
void shellPreparePrintf(
6754+
static void shellPreparePrintf(
67596755
sqlite3 *db,
67606756
int *pRc,
67616757
sqlite3_stmt **ppStmt,
@@ -6778,13 +6774,10 @@ void shellPreparePrintf(
67786774
}
67796775
}
67806776

6781-
/* Finalize the prepared statement created using shellPreparePrintf().
6782-
**
6783-
** This routine is could be marked "static". But it is not always used,
6784-
** depending on compile-time options. By omitting the "static", we avoid
6785-
** nuisance compiler warnings about "defined but not used".
6777+
/*
6778+
** Finalize the prepared statement created using shellPreparePrintf().
67866779
*/
6787-
void shellFinalize(
6780+
static void shellFinalize(
67886781
int *pRc,
67896782
sqlite3_stmt *pStmt
67906783
){
@@ -6800,6 +6793,7 @@ void shellFinalize(
68006793
}
68016794
}
68026795

6796+
#if !defined SQLITE_OMIT_VIRTUALTABLE
68036797
/* Reset the prepared statement created using shellPreparePrintf().
68046798
**
68056799
** This routine is could be marked "static". But it is not always used,
@@ -7866,6 +7860,30 @@ FROM (\
78667860
}
78677861
}
78687862

7863+
/*
7864+
** Check if the sqlite_schema table contains one or more virtual tables. If
7865+
** parameter zLike is not NULL, then it is an SQL expression that the
7866+
** sqlite_schema row must also match. If one or more such rows are found,
7867+
** print the following warning to the output:
7868+
**
7869+
** WARNING: Script requires that SQLITE_DBCONFIG_DEFENSIVE be disabled
7870+
*/
7871+
static int outputDumpWarning(ShellState *p, const char *zLike){
7872+
int rc = SQLITE_OK;
7873+
sqlite3_stmt *pStmt = 0;
7874+
shellPreparePrintf(p->db, &rc, &pStmt,
7875+
"SELECT 1 FROM sqlite_schema o WHERE "
7876+
"sql LIKE 'CREATE VIRTUAL TABLE%%' AND %s", zLike ? zLike : "true"
7877+
);
7878+
if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
7879+
oputz("/* WARNING: "
7880+
"Script requires that SQLITE_DBCONFIG_DEFENSIVE be disabled */\n"
7881+
);
7882+
}
7883+
shellFinalize(&rc, pStmt);
7884+
return rc;
7885+
}
7886+
78697887
/*
78707888
** If an input line begins with "." then invoke this routine to
78717889
** process that line.
@@ -8328,6 +8346,7 @@ static int do_meta_command(char *zLine, ShellState *p){
83288346

83298347
open_db(p, 0);
83308348

8349+
outputDumpWarning(p, zLike);
83318350
if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
83328351
/* When playing back a "dump", the content might appear in an order
83338352
** which causes immediate foreign key constraints to be violated.
@@ -11391,6 +11410,80 @@ static int line_is_complete(char *zSql, int nSql){
1139111410
return rc;
1139211411
}
1139311412

11413+
/*
11414+
** This function is called after processing each line of SQL in the
11415+
** runOneSqlLine() function. Its purpose is to detect scenarios where
11416+
** defensive mode should be automatically turned off. Specifically, when
11417+
**
11418+
** 1. The first line of input is "PRAGMA foreign_keys=OFF;",
11419+
** 2. The second line of input is "BEGIN TRANSACTION;",
11420+
** 3. The database is empty, and
11421+
** 4. The shell is not running in --safe mode.
11422+
**
11423+
** The implementation uses the ShellState.eRestoreState to maintain state:
11424+
**
11425+
** 0: Have not seen any SQL.
11426+
** 1: Have seen "PRAGMA foreign_keys=OFF;".
11427+
** 2: Currently assuming we are parsing ".dump" restore, defensive mode
11428+
** should be disabled following the current transaction.
11429+
** 3: Nothing left to do.
11430+
*/
11431+
static int doAutoDetectRestore(ShellState *p, const char *zSql){
11432+
int rc = SQLITE_OK;
11433+
11434+
switch( p->eRestoreState ){
11435+
case 0: {
11436+
int bDefense = 0; /* True if in defensive mode */
11437+
const char *zExpect = "PRAGMA foreign_keys=OFF;";
11438+
assert( strlen(zExpect)==24 );
11439+
sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, -1, &bDefense);
11440+
if( p->bSafeMode==0 && bDefense && memcmp(zSql, zExpect, 25)==0 ){
11441+
p->eRestoreState = 1;
11442+
}else{
11443+
p->eRestoreState = 3;
11444+
}
11445+
break;
11446+
};
11447+
11448+
case 1: {
11449+
const char *zExpect = "BEGIN TRANSACTION;";
11450+
assert( strlen(zExpect)==18 );
11451+
if( memcmp(zSql, zExpect, 19)==0 ){
11452+
/* Now check if the database is empty. */
11453+
const char *zQuery = "SELECT 1 FROM sqlite_schema LIMIT 1";
11454+
sqlite3_stmt *pStmt = 0;
11455+
int bEmpty = 1;
11456+
11457+
shellPrepare(p->db, &rc, zQuery, &pStmt);
11458+
if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
11459+
bEmpty = 0;
11460+
}
11461+
shellFinalize(&rc, pStmt);
11462+
if( bEmpty && rc==SQLITE_OK ){
11463+
sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, 0, 0);
11464+
}else{
11465+
p->eRestoreState = 3;
11466+
}
11467+
}
11468+
break;
11469+
}
11470+
11471+
case 2: {
11472+
if( sqlite3_get_autocommit(p->db) ){
11473+
sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, 0, 0);
11474+
p->eRestoreState = 3;
11475+
}
11476+
break;
11477+
}
11478+
11479+
default: /* Nothing to do */
11480+
assert( p->eRestoreState==3 );
11481+
break;
11482+
}
11483+
11484+
return rc;
11485+
}
11486+
1139411487
/*
1139511488
** Run a single line of SQL. Return the number of errors.
1139611489
*/
@@ -11438,6 +11531,8 @@ static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){
1143811531
sqlite3_changes64(p->db), sqlite3_total_changes64(p->db));
1143911532
oputf("%s\n", zLineBuf);
1144011533
}
11534+
11535+
if( doAutoDetectRestore(p, zSql) ) return 1;
1144111536
return 0;
1144211537
}
1144311538

test/shell9.test

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# 2024 Jan 8
2+
#
3+
# The author disclaims copyright to this source code. In place of
4+
# a legal notice, here is a blessing:
5+
#
6+
# May you do good and not evil.
7+
# May you find forgiveness for yourself and forgive others.
8+
# May you share freely, never taking more than you give.
9+
#
10+
#***********************************************************************
11+
#
12+
# The focus of this file is testing the CLI shell tool. Specifically,
13+
# testing that it is possible to run a ".dump" script that creates
14+
# virtual tables without explicitly disabling defensive mode.
15+
#
16+
17+
# Test plan:
18+
#
19+
# shell1-1.*: Basic command line option handling.
20+
# shell1-2.*: Basic "dot" command token parsing.
21+
# shell1-3.*: Basic test that "dot" command can be called.
22+
# shell1-{4-8}.*: Test various "dot" commands's functionality.
23+
# shell1-9.*: Basic test that "dot" commands and SQL intermix ok.
24+
#
25+
set testdir [file dirname $argv0]
26+
source $testdir/tester.tcl
27+
set CLI [test_cli_invocation]
28+
29+
set ::testprefix shell9
30+
31+
ifcapable !fts5 {
32+
finish_test
33+
return
34+
}
35+
36+
#----------------------------------------------------------------------------
37+
# Test cases shell9-1.* verify that scripts output by .dump may be parsed
38+
# by the shell tool without explicitly disabling DEFENSIVE mode, unless
39+
# the shell is in safe mode.
40+
#
41+
do_execsql_test 1.0 {
42+
CREATE VIRTUAL TABLE t1 USING fts5(a, b, c);
43+
INSERT INTO t1 VALUES('one', 'two', 'three');
44+
}
45+
db close
46+
47+
# Create .dump file in "testdump.txt".
48+
#
49+
set out [open testdump.txt w]
50+
puts $out [lindex [catchcmd test.db .dump] 1]
51+
close $out
52+
53+
# Check testdump.txt can be processed if the initial db is empty.
54+
#
55+
do_test 1.1.1 {
56+
forcedelete test.db
57+
catchcmd test.db ".read testdump.txt"
58+
} {0 {}}
59+
sqlite3 db test.db
60+
do_execsql_test 1.1.2 {
61+
SELECT * FROM t1;
62+
} {one two three}
63+
64+
# Check testdump.txt cannot be processed if the initial db is not empty.
65+
#
66+
reset_db
67+
do_execsql_test 1.2.1 {
68+
CREATE TABLE t4(hello);
69+
}
70+
db close
71+
do_test 1.2.2 {
72+
catchcmd test.db ".read testdump.txt"
73+
} {1 {Parse error near line 5: table sqlite_master may not be modified}}
74+
75+
# Check testdump.txt cannot be processed if the db is in safe mode
76+
#
77+
do_test 1.3.1 {
78+
forcedelete test.db
79+
catchsafecmd test.db ".read testdump.txt"
80+
} {1 {line 1: cannot run .read in safe mode}}
81+
do_test 1.3.2 {
82+
set fd [open testdump.txt]
83+
set script [read $fd]
84+
close $fd
85+
forcedelete test.db
86+
catchsafecmd test.db $script
87+
} {1 {Parse error near line 5: table sqlite_master may not be modified}}
88+
do_test 1.3.3 {
89+
# Quick check that the above would have worked but for safe mode.
90+
forcedelete test.db
91+
catchcmd test.db $script
92+
} {0 {}}
93+
94+
#----------------------------------------------------------------------------
95+
# Test cases shell9-2.* verify that a warning is printed at the top of
96+
# .dump scripts that contain virtual tables.
97+
#
98+
proc contains_warning {text} {
99+
return [string match "*WARNING: Script requires that*" $text]
100+
}
101+
102+
reset_db
103+
do_execsql_test 2.0.1 {
104+
CREATE TABLE t1(x);
105+
CREATE TABLE t2(y);
106+
INSERT INTO t1 VALUES('one');
107+
INSERT INTO t2 VALUES('two');
108+
}
109+
do_test 2.0.2 {
110+
contains_warning [catchcmd test.db .dump]
111+
} 0
112+
113+
do_execsql_test 2.1.1 {
114+
CREATE virtual TABLE r1 USING fts5(x);
115+
}
116+
do_test 2.1.2 {
117+
contains_warning [catchcmd test.db .dump]
118+
} 1
119+
120+
do_test 2.2.1 {
121+
contains_warning [catchcmd test.db ".dump t1"]
122+
} 0
123+
do_test 2.2.2 {
124+
contains_warning [catchcmd test.db ".dump r1"]
125+
} 1
126+
127+
finish_test

test/tester.tcl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,15 @@ proc catchcmd {db {cmd ""}} {
884884
set rc [catch { eval $line } msg]
885885
list $rc $msg
886886
}
887+
proc catchsafecmd {db {cmd ""}} {
888+
global CLI
889+
set out [open cmds.txt w]
890+
puts $out $cmd
891+
close $out
892+
set line "exec $CLI -safe $db < cmds.txt"
893+
set rc [catch { eval $line } msg]
894+
list $rc $msg
895+
}
887896

888897
proc catchcmdex {db {cmd ""}} {
889898
global CLI

0 commit comments

Comments
 (0)