]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
In the CLI, if a command-line argument names a file and the filename ends
authordrh <>
Sat, 13 Dec 2025 21:11:49 +0000 (21:11 +0000)
committerdrh <>
Sat, 13 Dec 2025 21:11:49 +0000 (21:11 +0000)
with .sql or .txt, then process that file as an SQL script.  Use this
feature for better testing.

FossilOrigin-Name: 19cc0522e2255f21f6fb6af1442c2ba122fdd4deacd9ea2c181ce9e597b88770

manifest
manifest.uuid
src/shell.c.in
test/dblwidth-a.sql
test/modeA.sql [moved from test/modeA.clitest with 99% similarity]
test/shellB.test

index 0436699d8e565be8e4fd95485500db3953e47626..5716b342a62f2592cf4f28724631a60efbe9b696 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Back\sout\sthe\s--compat\soption\sfrom\sthe\sCLI.
-D 2025-12-13T00:13:40.858
+C In\sthe\sCLI,\sif\sa\scommand-line\sargument\snames\sa\sfile\sand\sthe\sfilename\sends\nwith\s.sql\sor\s.txt,\sthen\sprocess\sthat\sfile\sas\san\sSQL\sscript.\s\sUse\sthis\nfeature\sfor\sbetter\stesting.
+D 2025-12-13T21:11:49.724
 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -737,7 +737,7 @@ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
 F src/resolve.c 8d53771eb51a4ab5f970150c3a70969d8db79cd04a8774c2d296bbcf471a0dd0
 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97
 F src/select.c 344518c1bba9c4636bf651b7642304abd2e7075ba35feb4bae42a51e5efe991f
-F src/shell.c.in 000a5c12e353b59b0a9276a7ff577804ba98f663528f818b91f8d25ea639fdee
+F src/shell.c.in e885e048418c366c5aa3252bb83bb0a61297054fdf829bc2283fb4fdd0e9bebb
 F src/sqlite.h.in 706cacea5308b0244fb6cec92e08310fb427a125375c64137cc1f878ae4cf5c0
 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479
 F src/sqlite3ext.h 5d5330f5f8461f5ce74960436ddcfa53ecd09c2b8b23901e22ae38aec3243998
@@ -1038,7 +1038,7 @@ F test/dbfuzz.c fc566102f72c8af84ae8077b4faf7f056c571e6fa7a32e98b66e42b7505f47b6
 F test/dbfuzz001.test 6c9a4622029d69dc38926f115864b055cb2f39badd25ec22cbfb130c8ba8e9c3
 F test/dbfuzz2-seed1.db e6225c6f3d7b63f9c5b6867146a5f329d997ab105bee64644dc2b3a2f2aebaee
 F test/dbfuzz2.c 4b3c12de4d98b1b2d908ab03d217d4619e47c8b23d5e67f8a6f2b1bdee7cae23
-F test/dblwidth-a.sql eb4141518610e52f931a55a984310075e98dc31eee5a28ae806b1e35377be85a
+F test/dblwidth-a.sql 59dd59aa78ce8fd8ab631a3816516831f4e947b143039257e6fe132c3cea4171
 F test/dbpage.test 63fab1eb026bada121107e53436fa749bbf83281dc9dea17af422f7a5c0f289f
 F test/dbpagefault.test ea39de2ca86041a9c6df1135645180a76d0a8da93ac159e2fafe38e39636530b
 F test/dbstatus.test 4a4221a883025ffd39696b3d1b3910b928fb097d77e671351acb35f3aed42759
@@ -1441,7 +1441,7 @@ F test/mmap4.test 2e2b4e32555b58da15176e6fe750f17c9dcf7f93
 F test/mmapcorrupt.test 470fb44fe92e99c1d23701d156f8c17865f5b027063c9119dcfdb842791f4465
 F test/mmapfault.test d4c9eff9cd8c2dc14bc43e71e042f175b0a26fe3
 F test/mmapwarm.test 2272005969cd17a910077bd5082f70bc1fefad9a875afec7fc9af483898ecaf3
-F test/modeA.clitest 2db42f2814ba45d5e886fead881360e689bc27346cfc37e6bb46f63334bebc61
+F test/modeA.sql a804e7db0f672007a54f5b890737de7376ac4d08c6e309347e1c93be5d533c5c w test/modeA.clitest
 F test/multiplex.test d74c034e52805f6de8cc5432cef8c9eb774bb64ec29b83a22effc8ca4dac1f08
 F test/multiplex2.test 580ca5817c7edbe4cc68fa150609c9473393003a
 F test/multiplex3.test fac575e0b1b852025575a6a8357701d80933e98b5d2fe6d35ddaa68f92f6a1f7
@@ -1620,7 +1620,7 @@ F test/shell7.test 43fd8e511c533bab5232e95c7b4be93b243451709e89582600d4b6e67693d
 F test/shell8.test 641cf21a99c59404c24e3062923734951c4099a6b6b6520de00cf7a1249ee871
 F test/shell9.test 8742a5b390cdcef6369f5aa223e415aa4255a4129ef249b177887dc635a87209
 F test/shellA.test 05cdaafa1f79913654487ce3aefa038d4106245d58f52e02faf506140a76d480
-F test/shellB.test de879b1ea7c25daf1a06b2c882b45a5d002e6580c81c57169ce47084cc6afb6b
+F test/shellB.test 41730c85658e1caa7e421454deefb3837fbfcd68be13a2a2f3a1ed92342fb0a4
 F test/shmlock.test 9f1f729a7fe2c46c88b156af819ac9b72c0714ac6f7246638a73c5752b5fd13c
 F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3
 F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5
@@ -2184,8 +2184,8 @@ F tool/version-info.c 33d0390ef484b3b1cb685d59362be891ea162123cea181cb8e6d2cf6dd
 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 1ca31e1a297c0d53d068afb658ce6602887cda0f3eaf42cd629c4d1b7204f0b0
-R cf0d7b71ff1f31fdee2bbe0931020fd5
+P 850d5dbfb0f1eacd1e5213759810ec7e5eba4fcc0b2718dabccd5b269b126f1c
+R 7d316ab9e616e989cd7843c90446decc
 U drh
-Z b8e82ce5183ee7bea000d8d3bf76900c
+Z 1fff9a15000a4bc11ad069c242a3fa0b
 # Remove this line to create a well-formed Fossil manifest.
index 3126c253109072add91d23cbe16c61f932bb872a..9919cbe8561833e1b7d672986393f34dd6934d9b 100644 (file)
@@ -1 +1 @@
-850d5dbfb0f1eacd1e5213759810ec7e5eba4fcc0b2718dabccd5b269b126f1c
+19cc0522e2255f21f6fb6af1442c2ba122fdd4deacd9ea2c181ce9e597b88770
index 86f41dc8659b8b3d9d2ee049c8d2d293951734cd..0a26fbde1820517e361a0bdc120e843cf6faa751 100644 (file)
@@ -4122,6 +4122,52 @@ static int session_filter(void *pCtx, const char *zTab){
 }
 #endif
 
+/*
+** Return the size of the named file in bytes.  Or return a negative
+** number if the file does not exist.
+*/
+static sqlite3_int64 fileSize(const char *zFile){
+#if defined(_WIN32) || defined(WIN32)
+  struct _stat64 x;
+  if( _stat64(zFile, &x)!=0 ) return -1;
+  return (sqlite3_int64)x.st_size;
+#else
+  struct stat x;
+  if( stat(zFile, &x)!=0 ) return -1;
+  return (sqlite3_int64)x.st_size;
+#endif
+}
+
+/*
+** Return true if zFile is an SQLite database.
+**
+** Algorithm:
+**    * If the file does not exist -> return false
+**    * If the size of the file is not a multiple of 512 -> return false
+**    * If sqlite3_open() fails -> return false
+**    * if sqlite3_prepare() or sqlite3_step() fails -> return false
+**    * Otherwise -> return true
+*/
+static int isDatabaseFile(const char *zFile, int openFlags){
+  sqlite3 *db = 0;
+  sqlite3_stmt *pStmt = 0;
+  int rc;
+  sqlite3_int64 sz = fileSize(zFile);
+  if( sz<512 || (sz%512)!=0 ) return 0;
+  if( sqlite3_open_v2(zFile, &db, openFlags, 0)==SQLITE_OK
+   && sqlite3_prepare_v2(db,"SELECT count(*) FROM sqlite_schema",-1,&pStmt,0)
+           ==SQLITE_OK
+   && sqlite3_step(pStmt)==SQLITE_ROW
+  ){
+    rc = 1;
+  }else{
+    rc = 0;
+  }
+  sqlite3_finalize(pStmt);
+  sqlite3_close(db);
+  return rc;
+}
+
 /*
 ** Try to deduce the type of file for zName based on its content.  Return
 ** one of the SHELL_OPEN_* constants.
@@ -4134,20 +4180,12 @@ static int session_filter(void *pCtx, const char *zTab){
 int deduceDatabaseType(const char *zName, int dfltZip, int openFlags){
   FILE *f;
   size_t n;
-  sqlite3 *db = 0;
-  sqlite3_stmt *pStmt = 0;
   int rc = SHELL_OPEN_UNSPEC;
   char zBuf[100];
   if( access(zName,0)!=0 ) goto database_type_by_name;
-  if( sqlite3_open_v2(zName, &db, openFlags, 0)==SQLITE_OK
-   && sqlite3_prepare_v2(db,"SELECT count(*) FROM sqlite_schema",-1,&pStmt,0)
-           ==SQLITE_OK
-   && sqlite3_step(pStmt)==SQLITE_ROW
-  ){
+  if( isDatabaseFile(zName, openFlags) ){
     rc = SHELL_OPEN_NORMAL;
   }
-  sqlite3_finalize(pStmt);
-  sqlite3_close(db);
   if( rc==SHELL_OPEN_NORMAL ) return SHELL_OPEN_NORMAL;
   f = sqlite3_fopen(zName, "rb");
   if( f==0 ) goto database_type_by_name;
@@ -4182,6 +4220,33 @@ database_type_by_name:
   return rc;
 }
 
+/*
+** If the text in z[] is the name of a readable file and that file appears
+** to contain SQL text and/or dot-commands, then return true.  If z[] is
+** not a file, or if the file is unreadable, or if the file is a database
+** or anything else that is not SQL text and dot-commands, then return false.
+**
+** If the bLeaveUninit flag is set, then be sure to leave SQLite in an
+** uninitialized state.  This means invoking sqlite3_shutdown() after any
+** SQLite API is used.
+**
+** Some amount of guesswork is involved in this decision.
+*/
+static int isScriptFile(const char *z, int bLeaveUninit){
+  sqlite3_int64 sz = fileSize(z);
+  if( sz<=0 ) return 0;
+  if( (sz%512)==0 ){
+    int rc = isDatabaseFile(z, SQLITE_OPEN_READONLY);
+    if( bLeaveUninit ){
+      sqlite3_shutdown();
+    }
+    if( rc ) return 0;  /* Is a database */
+  }
+  if( sqlite3_strlike("*.sql",z,0)==0 ) return 1;
+  if( sqlite3_strlike("*.txt",z,0)==0 ) return 1;
+  return 0;
+}
+
 #ifndef SQLITE_OMIT_DESERIALIZE
 /*
 ** Reconstruct an in-memory database using the output from the "dbtotxt"
@@ -12635,7 +12700,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
   }
 #endif
 
-  /* Do an initial pass through the command-line argument to locate
+  /* Do an initial pass through the command-line arguments to locate
   ** the name of the database file, the name of the initialization file,
   ** the size of the alternative malloc heap, options affecting commands
   ** or SQL run from the command line, and the first command to execute.
@@ -12647,7 +12712,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
     char *z;
     z = argv[i];
     if( z[0]!='-' || i>nOptsEnd ){
-      if( data.aAuxDb->zDbFilename==0 ){
+      if( data.aAuxDb->zDbFilename==0 && !isScriptFile(z,1) ){
         data.aAuxDb->zDbFilename = z;
       }else{
         /* Excess arguments are interpreted as SQL (or dot-commands) and
@@ -12882,7 +12947,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
   */
   if( !noInit ) process_sqliterc(&data,zInitFile);
 
-  /* Make a second pass through the command-line argument and set
+  /* Make a second pass through the command-line arguments and set
   ** options.  This second pass is delayed until after the initialization
   ** file is processed so that the command-line arguments will override
   ** settings in the initialization file.
@@ -13107,7 +13172,17 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
     */
     for(i=0; i<nCmd; i++){
       echo_group_input(&data, azCmd[i]);
-      if( azCmd[i][0]=='.' ){
+      if( isScriptFile(azCmd[i],0) ){
+        FILE *inSaved = data.in;
+        i64 savedLineno = data.lineno;
+        int res = 1;
+        if( (data.in = openChrSource(azCmd[i]))!=0 ){
+          res = process_input(&data, azCmd[i]);
+        }
+        data.in = inSaved;
+        data.lineno = savedLineno;
+        if( res ) i = nCmd;
+      }else if( azCmd[i][0]=='.' ){
         char *zErrCtx = malloc( 64 );
         shell_check_oom(zErrCtx);
         sqlite3_snprintf(64,zErrCtx,"argv[%i]:",aiCmd[i]);
index 38c219698d02f9d7f3b30e9d1cfc8ad62f998355..bcd60359f1549db6640b49f7febb9edba696ba83 100644 (file)
@@ -1,20 +1,50 @@
+#!sqlite3
 /*
 ** Run this script using "sqlite3" to confirm that the command-line
 ** shell properly handles the output of double-width characters.
 **
 ** https://sqlite.org/forum/forumpost/008ac80276
 */
+.testcase 100
 .mode box
 CREATE TABLE data(word TEXT, description TEXT);
 INSERT INTO data VALUES('〈οὐκέτι〉','Greek without dblwidth <...>');
-.print .mode box
 SELECT * FROM data;
+.check <<END
+╭────────────┬──────────────────────────────╮
+│    word    │         description          │
+╞════════════╪══════════════════════════════╡
+│ 〈οὐκέτι〉 │ Greek without dblwidth <...> │
+╰────────────┴──────────────────────────────╯
+END
+
+.testcase 200
 .mode table
-.print .mode table
 SELECT * FROM data;
+.check <<END
++------------+------------------------------+
+|    word    |         description          |
++------------+------------------------------+
+| 〈οὐκέτι〉 | Greek without dblwidth <...> |
++------------+------------------------------+
+END
+
+.testcase 300
 .mode qbox
-.print .mode qbox
 SELECT * FROM data;
+.check <<END
+╭──────────────┬────────────────────────────────╮
+│     word     │          description           │
+╞══════════════╪════════════════════════════════╡
+│ '〈οὐκέτι〉' │ 'Greek without dblwidth <...>' │
+╰──────────────┴────────────────────────────────╯
+END
+
+.testcase 400
 .mode column
-.print .mode column
 SELECT * FROM data;
+.check <<END
+   word             description
+----------  ----------------------------
+〈οὐκέτι〉  Greek without dblwidth <...>
+END
similarity index 99%
rename from test/modeA.clitest
rename to test/modeA.sql
index 2dcbf0c45cdda671a36388c2c9c606d2c2bc1eef..7a9c476029d215815dd09a324691528cd601b89e 100644 (file)
@@ -1,3 +1,5 @@
+#!sqlite3
+#
 # 2025-11-12
 #
 # The author disclaims copyright to this source code.  In place of
index f71c02e2a4cae0a2b76978e1ce4df5e6efd65d77..d0717a6e54a115dc8b9a76562bf04e3f83740f5e 100644 (file)
@@ -39,6 +39,7 @@ proc do_clitest {name} {
   do_test shellB-$name $script {error count: 0}
 }
 
-do_clitest modeA.clitest
+do_clitest modeA.sql
+do_clitest dblwidth-a.sql
 
 finish_test