Improvements to the interactive prompt in the CLI:

(1) CLI honors SQLITE_PS1 and SQLITE_PS2 envvars as prompt strings,
(2) CLI prompt strings expand various backslash escapes,
(3) No arbitrary length limits on CLI prompt strings,
(4) New default prompt shows the database filename using the \f
escape,
(5) SQLite core adds the sqlite3_incomplate() API, used by
the \H escape in prompt string rendering,
(6) Enhancements to the ".prompt" dot-command.

FossilOrigin-Name: 55af98054e073d6eee3e85fd7564c83eca935ae205da68f5e8f1b561e7f08c00
This commit is contained in:
drh
2026-04-11 18:38:54 +00:00
10 changed files with 642 additions and 295 deletions
+1
View File
@@ -245,4 +245,5 @@ _fiddle_reset_db
_fiddle_db_handle
_fiddle_db_vfs
_fiddle_export_db
_fiddle_get_prompt
//#/if fiddle
+3 -1
View File
@@ -186,6 +186,7 @@
if(!f._){
if(!this.runMain()) return;
f._ = sqlite3.wasm.xWrap('fiddle_exec', undefined, ['string']);
f.getPrompt = sqlite3.wasm.xWrap('fiddle_get_prompt', 'string:dealloc', []);
}
if(fiddleModule.isDead){
stderr("shell module has exit()ed. Cannot run SQL.");
@@ -207,7 +208,8 @@
wMsg('working','end');
wMsg('wasm-info', {
pointerSize: sqlite3.wasm.ptr.size,
heapSize: sqlite3.wasm.heap8().byteLength
heapSize: sqlite3.wasm.heap8().byteLength,
prompt: f.getPrompt()
});
}
},
+3 -2
View File
@@ -349,9 +349,10 @@
SF.e.wasmInfo.innerText = 'WASM: '+(
4===v.pointerSize ? 32 : 64
)+'-bit'
//+' heap size: '+Number(v.heapSize)
+' heap: '+Number(v.heapSize)
// Heap size is not changing even when loading a huge db?
;
SF.jqTerm?.set_prompt?.(v.prompt);
});
/* querySelectorAll() proxy */
@@ -865,7 +866,7 @@
const jqeTerm = window.jQuery(SF.e.terminal).empty();
SF.jqTerm = jqeTerm.terminal(SF.dbExec.bind(SF),{
prompt: 'sqlite> ',
greetings: false /* note that the docs incorrectly call this 'greeting' */
greetings: false /* the docs incorrectly call this 'greeting' */
});
EAll('.unhide-if-terminal-available').forEach(e=>{
e.classList.remove('hidden');
+15 -14
View File
@@ -1,5 +1,5 @@
C Ensure\sthat\ssqlite3-opfs-async-proxy.js\sis\scopied\sinto\sthe\sfiddle\starget\sdirs.
D 2026-04-10T23:31:29.790
C Improvements\sto\sthe\sinteractive\sprompt\sin\sthe\sCLI:\n(1)\sCLI\shonors\sSQLITE_PS1\sand\sSQLITE_PS2\senvvars\sas\sprompt\sstrings,\n(2)\sCLI\sprompt\sstrings\sexpand\svarious\sbackslash\sescapes,\n(3)\sNo\sarbitrary\slength\slimits\son\sCLI\sprompt\sstrings,\n(4)\sNew\sdefault\sprompt\sshows\sthe\sdatabase\sfilename\susing\sthe\s\f\nescape,\n(5)\sSQLite\score\sadds\sthe\ssqlite3_incomplate()\sAPI,\sused\sby\nthe\s\H\sescape\sin\sprompt\sstring\srendering,\n(6)\sEnhancements\sto\sthe\s".prompt"\sdot-command.
D 2026-04-11T18:38:54.051
F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -582,7 +582,7 @@ F ext/wasm/SQLTester/SQLTester.mjs 6b3c52ed36a5573ca4883176f326332a8d4c0cecf5efd
F ext/wasm/SQLTester/SQLTester.run.mjs 57f2adb33f43f2784abbf8026c1bfd049d8013af1998e7dcb8b50c89ffc332e0
F ext/wasm/SQLTester/index.html 64f3435084c7d6139b08d1f2a713828a73f68de2ae6a3112cbb5980d991ba06f
F ext/wasm/SQLTester/touint8array.c 2d5ece04ec1393a6a60c4bf96385bda5e1a10ad49f3038b96460fc5e5aa7e536
F ext/wasm/api/EXPORTED_FUNCTIONS.c-pp de2ce128aebeff9ef4161cff7a0ff0089be866121bcb8941ad44a38a65f1d436
F ext/wasm/api/EXPORTED_FUNCTIONS.c-pp 189935d0106bca86661efc991ab13e1d5d6e1f55ec34dc7b5055a99321397edd
F ext/wasm/api/README.md a905d5c6bfc3e2df875bd391d6d6b7b48d41b43bdee02ad115b47244781a7e81
F ext/wasm/api/extern-post-js.c-pp.js 80accc53cc6ea1e61c721595f42ba95baa7c7ea636807d9507e69403301f8c54
F ext/wasm/api/extern-pre-js.js cc61c09c7a24a07dbecb4c352453c3985170cec12b4e7e7e7a4d11d43c5c8f41
@@ -621,8 +621,8 @@ F ext/wasm/demo-worker1-promiser.c-pp.js d210aa1e8a74ea6244fe2290de5710bb55b3a9e
F ext/wasm/demo-worker1.html 2c178c1890a2beb5a5fecb1453e796d067a4b8d3d2a04d65ca2eb1ab2c68ef5d
F ext/wasm/demo-worker1.js fdfa90aa9d6b402bfed802cf1595fe4da6cc834ac38c8ff854bf1ee01f5ff9bb
F ext/wasm/example_extra_init.c 2347cd69d19d839ef4e5e77b7855103a7fe3ef2af86f2e8c95839afd8b05862f
F ext/wasm/fiddle/fiddle-worker.js 6c72acac2d381480bc9f5eb538e3f2faf2c1f72dd4fcbd05d3b409818a9a8fd5
F ext/wasm/fiddle/fiddle.js fc0f19303d00014a0f285fefd30953e953be1bd01e757bbd9eda45c9dc2c154b
F ext/wasm/fiddle/fiddle-worker.js 3cdd20c1e84494a1f28bd80265ae2a754764b08c67e146e61f99d864de4bf5f6
F ext/wasm/fiddle/fiddle.js ed0551146eb36a0679833d6fd58ba4f025f27e673a0f19535c6386397721849b
F ext/wasm/fiddle/index.c-pp.html 02f063ef30b8124f311029855c4439e77bc6505d1bf65a163d88c064a63ee9d9
F ext/wasm/index-dist.html db23748044e286773f2768eec287669501703b5d5f72755e8db73607dc54d290
F ext/wasm/index.html 5bf6cf1b0a3c8b9f5f54d77f2219d7ae87a15162055ce308109c49b1dcab4239
@@ -680,7 +680,7 @@ F src/btreeInt.h 9c0f9ea5c9b5f4dcaea18111d43efe95f2ac276cd86d770dce10fd99ccc9388
F src/build.c 8581de0af3b6c448f5d64e2d18a91ac1e7057b3bcb8b8827e1240f80d87486a4
F src/callback.c 3605bbf02bd7ed46c79cd48346db4a32fc51d67624400539c0532f4eead804ad
F src/carray.c 3efe3982d5fb323334c29328a4e189ccaef6b95612a6084ad5fa124fd5db1179
F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e
F src/complete.c 9304071c5f2ddd040cd786ec5d157c6a5592561674816e4a9e74164a0ba40647
F src/date.c 61e92f1f7e2e88e1cd91e91dc69eb2b2854e7877254470f9fabd776bfac922b8
F src/dbpage.c c9ea81c11727f27e02874611e92773e68e2a90a875ef2404b084564c235fd91f
F src/dbstat.c 73362c0df0f40ad5523a6f5501224959d0976757b511299bf892313e79d14f5c
@@ -735,8 +735,8 @@ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
F src/resolve.c 928ff887f2a7c64275182060d94d06fdddbe32226c569781cf7e7edc6f58d7fd
F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97
F src/select.c ffe199f025a0dd74670d2a77232bdea364a4d7b36f32c64a6572d39ba6a11576
F src/shell.c.in 4d00b373ce9485b5f2db2dbebf3a6a3ef3ebe7b77fc7ecb3278cae2cb4a5529b
F src/sqlite.h.in e2915e4a86d5e0783afb5cb72411df38d987c7f3c5aa2d5441b8e74d30b649d8
F src/shell.c.in bed26ce3998b32fde83acbf7f4af3bd9e64902bec6bbfa1a834c9f30497b7231
F src/sqlite.h.in a5605faa9479bbaac16c4ab43eb09ff50632004a8e05084d3fde56063ef73766
F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479
F src/sqlite3ext.h 1b7a0ee438bb5c2896d0609c537e917d8057b3340f6ad004d2de44f03e3d3cca
F src/sqliteInt.h bc1cbc0c23dba35b324ae85a7dbb5fb182321bbd30857fb21f3d0cba049001a5
@@ -1619,6 +1619,7 @@ F test/sharedA.test 64bdd21216dda2c6a3bd3475348ccdc108160f34682c97f2f51c19fc0e21
F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8ee707
F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939
F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304
F test/shell-prompt.sql c1437f778af91ae3dcb705225c09564a84197626f93de4725233c4ab6ac6daf0
F test/shell1.test eda2e527435f139224dda67db6bbd2466597408d4fe5883d647d67fa32d88f7c
F test/shell2.test dc541d2681503e55466a24d35a4cbf8ca5b90b8fcdef37fc4db07373a67d31d3
F test/shell3.test 91efdd545097a61a1f72cf79c9ad5b49da080f3f10282eaf4c3c272cd1012db2
@@ -1629,7 +1630,7 @@ F test/shell7.test 43fd8e511c533bab5232e95c7b4be93b243451709e89582600d4b6e67693d
F test/shell8.test 38c9e4d7e85d2a3ecfacaa9f6cda4f7a81bf4fffb5f3f37f9cd76827c6883192
F test/shell9.test c0e8871061a92151450b3332279a893b516fa73a6c46d4f51a0998407cbf8c89
F test/shellA.test 05cdaafa1f79913654487ce3aefa038d4106245d58f52e02faf506140a76d480
F test/shellB.test 31df04230f6062069bb7c5d0e5c5439ca44448fa9da1a55aa461a4b872fe6bd9
F test/shellB.test a42be39e2332877d8ee239e1ca2a78d0a1feda21d90f3dd50f32f26cc3b433de
F test/shmlock.test 9f1f729a7fe2c46c88b156af819ac9b72c0714ac6f7246638a73c5752b5fd13c
F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3
F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5
@@ -2197,9 +2198,9 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee
F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c
P 8574e5c9da3b167384faafb8ec0d01953578676cc42536bb6f7d0e73408ced36
Q +7c9998de1b839cfbe12c4328ac5176f5fae981e139c5f8888b100752f5e4756b
R da0f6a1d55574e9d7c03a0b245667295
U stephan
Z 0c5cea2ef0a7a704f084f44ad2fea285
P 149200861e4690dab5f2e690b62f355b13a8370dccaab5546caa6842b4ff963a 3b4cc8d3beab8c910dd954bf8093f7ab499e4690b9cf882f981c4415dd7a7540
R 865ef32ca21a6acb62eb499392cf595d
T +closed 3b4cc8d3beab8c910dd954bf8093f7ab499e4690b9cf882f981c4415dd7a7540
U drh
Z adff825d993df698cc251e509641206b
# Remove this line to create a well-formed Fossil manifest.
+1 -1
View File
@@ -1 +1 @@
149200861e4690dab5f2e690b62f355b13a8370dccaab5546caa6842b4ff963a
55af98054e073d6eee3e85fd7564c83eca935ae205da68f5e8f1b561e7f08c00
+91 -10
View File
@@ -49,12 +49,51 @@ extern const char sqlite3IsEbcdicIdChar[];
#endif
/*
** Return TRUE if the given SQL string ends in a semicolon.
** Return zero if the given SQL string is complete - if all comments,
** string and blob literals, and quoted identifiers have been closed and
** if the entire string ends with ";" and possible with ";END;" if the
** string is a CREATE TRIGGER statement. A non-zero return indicates
** that the string is incomplete. Bits of the return value indicate
** what is missing and is needed to close out the statement.
**
** Special handling is require for CREATE TRIGGER statements.
** Whenever the CREATE TRIGGER keywords are seen, the statement
** must end with ";END;".
**
** Let the return code be a value R. R is split up into various
** subfields, at byte boundaries:
**
** R = 0xwwwwwwww00xxyyzz
**
** In other words, zz is the least significant byte, yy is the next
** most significant byte, xx is the third byte, wwwwwwww is a 32-bit
** value from the middle.
**
** zz == SQLITE_OK Input is complete
** zz == SQLITE_ERROR Input is incomplete
** zz == SQLITE_MISUSE Input is a NULL pointer
** zz != 0 New values for zz may be added in the future
**
** yy == 0x01 Need a semicolon at the end
** yy == 0x02 Need "END" and a semicolon
** yy == 0x03 Need semicolon, "END", and semicolon
** yy != 0 New values for yy may be added in the future
**
** xx == '\'' Incomplete string or blob literal
** xx == '"' Incomplete quoted identifier
** xx == '`' Incompelte MySQL-style quoted identifier
** xx == ']' Incomplete SQLServer-style quoted identifer
** xx == '-' Incomplete SQL-style comment
** xx == '/' Incomplete C-style comment
** xx != 0 New values of xx may be added in the future
**
** wwwwwwww Interpret as a signed integer, the number
** of unmatched "(". Negative means there are
** more ")" and "(".
**
** ((R>>24)&0xff)!=0 New uses for the 4th byte may be added
** in the future
**
** This implementation uses a state machine with 8 states:
**
** (0) INVALID We have not yet seen a non-whitespace character.
@@ -101,9 +140,11 @@ extern const char sqlite3IsEbcdicIdChar[];
** to recognize the end of a trigger can be omitted. All we have to do
** is look for a semicolon that is not part of an string or comment.
*/
int sqlite3_complete(const char *zSql){
sqlite3_int64 sqlite3_incomplete(const char *zSql){
u8 state = 0; /* Current state, using numbers defined in header comment */
u8 token; /* Value of the next token */
u8 pending = 0; /* unmatched structure character */
int nParen = 0; /* Nested parentheses */
#ifndef SQLITE_OMIT_TRIGGER
/* A complex statement machine used to detect the end of a CREATE TRIGGER
@@ -133,11 +174,21 @@ int sqlite3_complete(const char *zSql){
/* 2 NORMAL: */ { 1, 2, 2, },
};
#endif /* SQLITE_OMIT_TRIGGER */
/* Mapping state number to yy value for the return */
static const u8 statemap[8] = {
/* 0 INVALID */ 1,
/* 1 START */ 0,
/* 2 NORMAL */ 1,
/* 3 EXPLAIN */ 1,
/* 4 CREATE */ 1,
/* 5 TRIGGER */ 3,
/* 6 SEMI */ 2,
/* 7 END */ 1,
};
#ifdef SQLITE_ENABLE_API_ARMOR
if( zSql==0 ){
(void)SQLITE_MISUSE_BKPT;
return 0;
return SQLITE_MISUSE_BKPT;
}
#endif
@@ -162,7 +213,10 @@ int sqlite3_complete(const char *zSql){
}
zSql += 2;
while( zSql[0] && (zSql[0]!='*' || zSql[1]!='/') ){ zSql++; }
if( zSql[0]==0 ) return 0;
if( zSql[0]==0 ){
pending = '/';
goto incomplete_finish;
}
zSql++;
token = tkWS;
break;
@@ -173,14 +227,20 @@ int sqlite3_complete(const char *zSql){
break;
}
while( *zSql && *zSql!='\n' ){ zSql++; }
if( *zSql==0 ) return state==1;
if( *zSql==0 ){
if( state!=1 ) pending = '-';
goto incomplete_finish;
}
token = tkWS;
break;
}
case '[': { /* Microsoft-style identifiers in [...] */
zSql++;
while( *zSql && *zSql!=']' ){ zSql++; }
if( *zSql==0 ) return 0;
if( *zSql==0 ){
pending = ']';
goto incomplete_finish;
}
token = tkOTHER;
break;
}
@@ -190,7 +250,20 @@ int sqlite3_complete(const char *zSql){
int c = *zSql;
zSql++;
while( *zSql && *zSql!=c ){ zSql++; }
if( *zSql==0 ) return 0;
if( *zSql==0 ){
pending = c;
goto incomplete_finish;
}
token = tkOTHER;
break;
}
case '(': {
nParen++;
token = tkOTHER;
break;
}
case ')': {
nParen--;
token = tkOTHER;
break;
}
@@ -257,7 +330,15 @@ int sqlite3_complete(const char *zSql){
state = trans[state][token];
zSql++;
}
return state==1;
incomplete_finish:
if( state==1 ) nParen = 0;
return (((i64)nParen)<<32) |
((i64)pending<<16) |
((i64)statemap[state]<<8) |
(state!=1);
}
int sqlite3_complete(const char *zSql){
return sqlite3_incomplete(zSql)==0;
}
#ifndef SQLITE_OMIT_UTF16
@@ -279,7 +360,7 @@ int sqlite3_complete16(const void *zSql){
sqlite3ValueSetStr(pVal, -1, zSql, SQLITE_UTF16NATIVE, SQLITE_STATIC);
zSql8 = sqlite3ValueText(pVal, SQLITE_UTF8);
if( zSql8 ){
rc = sqlite3_complete(zSql8);
rc = sqlite3_incomplete(zSql8)==0;
}else{
rc = SQLITE_NOMEM_BKPT;
}
+423 -259
View File
@@ -355,7 +355,6 @@ typedef struct Mode {
#define MFLG_CRLF 0x02 /* Use CR/LF output line endings */
#define MFLG_HDR 0x04 /* .header used to change headers on/off */
/*
** State information about the database connection is contained in an
** instance of the following structure.
@@ -397,10 +396,9 @@ struct ShellState {
char *zDestTable; /* Name of destination table when MODE_Insert */
char *zTempFile; /* Temporary file that might need deleting */
char *zErrPrefix; /* Alternative error message prefix */
char zTestcase[30]; /* Name of current test case */
char outfile[FILENAME_MAX]; /* Filename for *out */
sqlite3_stmt *pStmt; /* Current statement if any. */
FILE *pLog; /* Write log output here */
char *azPrompt[2]; /* Main and continuation prompt strings */
Mode mode; /* Current display mode */
Mode modePrior; /* Backup */
struct SavedMode { /* Ability to define custom mode configurations */
@@ -412,6 +410,7 @@ struct ShellState {
sqlite3 *db; /* Connection pointer */
const char *zDbFilename; /* Filename used to open the connection */
char *zFreeOnClose; /* Free this memory allocation on close */
int mFlgs; /* 0x001: Use zDbFilenaem for prompt */
#if defined(SQLITE_ENABLE_SESSION)
int nSession; /* Number of active sessions */
OpenSession aSession[4]; /* Array of sessions. [0] is in focus. */
@@ -438,6 +437,8 @@ struct ShellState {
const char * zDefaultDbName; /* Default name for db file */
} wasm;
#endif
char zTestcase[30]; /* Name of current test case */
char outfile[FILENAME_MAX]; /* Filename for *out */
};
#ifdef SQLITE_SHELL_FIDDLE
@@ -674,16 +675,6 @@ static volatile int seenInterrupt = 0;
*/
static char *Argv0;
/*
** Prompt strings. Initialized in main. Settable with
** .prompt main continue
*/
#define PROMPT_LEN_MAX 128
/* First line prompt. default: "sqlite> " */
static char mainPrompt[PROMPT_LEN_MAX];
/* Continuation prompt. default: " ...> " */
static char continuePrompt[PROMPT_LEN_MAX];
/*
** Write I/O traces to the following stream.
*/
@@ -797,114 +788,6 @@ static sqlite3_int64 timeOfDay(void){
#endif
}
/* This is variant of the standard-library strncpy() routine with the
** one change that the destination string is always zero-terminated, even
** if there is no zero-terminator in the first n-1 characters of the source
** string.
*/
static char *shell_strncpy(char *dest, const char *src, size_t n){
size_t i;
for(i=0; i<n-1 && src[i]!=0; i++) dest[i] = src[i];
dest[i] = 0;
return dest;
}
/*
** strcpy() workalike to squelch an unwarranted link-time warning
** from OpenBSD.
*/
static void shell_strcpy(char *dest, const char *src){
while( (*(dest++) = *(src++))!=0 ){}
}
/*
** Optionally disable dynamic continuation prompt.
** Unless disabled, the continuation prompt shows open SQL lexemes if any,
** or open parentheses level if non-zero, or continuation prompt as set.
** This facility interacts with the scanner and process_input() where the
** below 5 macros are used.
*/
#ifdef SQLITE_OMIT_DYNAPROMPT
# define CONTINUATION_PROMPT continuePrompt
# define CONTINUE_PROMPT_RESET
# define CONTINUE_PROMPT_AWAITS(p,s)
# define CONTINUE_PROMPT_AWAITC(p,c)
# define CONTINUE_PAREN_INCR(p,n)
# define CONTINUE_PROMPT_PSTATE 0
typedef void *t_NoDynaPrompt;
# define SCAN_TRACKER_REFTYPE t_NoDynaPrompt
#else
# define CONTINUATION_PROMPT dynamicContinuePrompt()
# define CONTINUE_PROMPT_RESET \
do {setLexemeOpen(&dynPrompt,0,0); trackParenLevel(&dynPrompt,0);} while(0)
# define CONTINUE_PROMPT_AWAITS(p,s) \
if(p && stdin_is_interactive) setLexemeOpen(p, s, 0)
# define CONTINUE_PROMPT_AWAITC(p,c) \
if(p && stdin_is_interactive) setLexemeOpen(p, 0, c)
# define CONTINUE_PAREN_INCR(p,n) \
if(p && stdin_is_interactive) (trackParenLevel(p,n))
# define CONTINUE_PROMPT_PSTATE (&dynPrompt)
typedef struct DynaPrompt *t_DynaPromptRef;
# define SCAN_TRACKER_REFTYPE t_DynaPromptRef
static struct DynaPrompt {
char dynamicPrompt[PROMPT_LEN_MAX];
char acAwait[2];
int inParenLevel;
char *zScannerAwaits;
} dynPrompt = { {0}, {0}, 0, 0 };
/* Record parenthesis nesting level change, or force level to 0. */
static void trackParenLevel(struct DynaPrompt *p, int ni){
p->inParenLevel += ni;
if( ni==0 ) p->inParenLevel = 0;
p->zScannerAwaits = 0;
}
/* Record that a lexeme is opened, or closed with args==0. */
static void setLexemeOpen(struct DynaPrompt *p, char *s, char c){
if( s!=0 || c==0 ){
p->zScannerAwaits = s;
p->acAwait[0] = 0;
}else{
p->acAwait[0] = c;
p->zScannerAwaits = p->acAwait;
}
}
/* Upon demand, derive the continuation prompt to display. */
static char *dynamicContinuePrompt(void){
if( continuePrompt[0]==0
|| (dynPrompt.zScannerAwaits==0 && dynPrompt.inParenLevel == 0) ){
return continuePrompt;
}else{
if( dynPrompt.zScannerAwaits ){
size_t ncp = strlen(continuePrompt);
size_t ndp = strlen(dynPrompt.zScannerAwaits);
if( ndp > ncp-3 ) return continuePrompt;
shell_strcpy(dynPrompt.dynamicPrompt, dynPrompt.zScannerAwaits);
while( ndp<3 ) dynPrompt.dynamicPrompt[ndp++] = ' ';
shell_strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3,
PROMPT_LEN_MAX-4);
}else{
if( dynPrompt.inParenLevel>9 ){
shell_strncpy(dynPrompt.dynamicPrompt, "(..", 4);
}else if( dynPrompt.inParenLevel<0 ){
shell_strncpy(dynPrompt.dynamicPrompt, ")x!", 4);
}else{
shell_strncpy(dynPrompt.dynamicPrompt, "(x.", 4);
dynPrompt.dynamicPrompt[2] = (char)('0'+dynPrompt.inParenLevel);
}
shell_strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3,
PROMPT_LEN_MAX-4);
}
}
return dynPrompt.dynamicPrompt;
}
#endif /* !defined(SQLITE_OMIT_DYNAPROMPT) */
/* Indicate out-of-memory and exit. */
static void shell_out_of_memory(void){
eputz("Error: out of memory\n");
@@ -1024,12 +907,252 @@ static char *local_getline(char *zLine, FILE *in){
return zLine;
}
/*
** Return the raw (unexpanded) prompt string. This will be the
** first of the following that exist:
**
** * The prompt string specified by the ".prompt" command.
** * The value of the SQLITE_PS1 (or SQLITE_PS2) environment variable.
** * The default prompt string.
**
** The main prompt is returned if the argument is zero and the
** continuation prompt is returned if the argument is 1.
*/
static const char *prompt_string(ShellState *p, int bContinue){
const char *zPS;
assert( bContinue==0 || bContinue==1 );
if( p->azPrompt[bContinue] ){
return p->azPrompt[bContinue];
}
#ifndef SQLITE_SHELL_FIDDLE
zPS = getenv(bContinue ? "SQLITE_PS2" : "SQLITE_PS1");
if( zPS ) return zPS;
#endif
if( bContinue ){
return "\\B...\\H> ";
}else{
return "SQLite \\f> ";
}
}
/*
** Return the name of the open database file, to be used for prompt
** expansion purposes.
*/
static const char *prompt_filename(ShellState *p){
sqlite3_filename pFN;
const char *zFN = 0;
if( p->pAuxDb->mFlgs & 0x01 ){
zFN = p->pAuxDb->zDbFilename;
}else if( p->db && (pFN = sqlite3_db_filename(p->db,0))!=0 ){
zFN = sqlite3_filename_database(pFN);
}
if( zFN==0 || zFN[0]==0 ){
zFN = "in-memory";
}
return zFN;
}
/*
** Expand escapes in the given input prompt string. Return the
** expanded prompt in memory obtained from sqlite3_malloc(). The
** caller is responsible for freeing the memory.
*/
static char *expand_prompt(
ShellState *p, /* The current shell state */
const char *zPrior, /* Prior input lines not yet processed */
const char *zPrompt /* Prompt string to be expanded */
){
sqlite3_str *pOut = sqlite3_str_new(0);
int i;
char c;
int onoff = 1;
int idxSpace = -1;
for(i=0; zPrompt[i]; i++){
if( zPrompt[i]!='\\' ) continue;
if( i>0 ){
if( onoff ) sqlite3_str_append(pOut, zPrompt, i);
zPrompt += i;
i = 0;
}
/* At this point zPrompt[0] is a \ character and all prior
** characters have already been loaded into pOut. Process the
** escape sequence that zPrompt points to. */
c = zPrompt[1];
if( c==0 ){
/* \ at the end of a line is silently ignored */
break;
}
if( c=='\\' ){
/* \\ maps into a single \ */
zPrompt++;
continue;
}
if( c>='0' && c<='7' ){
/* \nnn becomes a single byte given by octal nnn */
int v = c - '0';
while( i<=2 && zPrompt[i+1]>='0' && zPrompt[i+1]<='7' ){
v = v*8 + zPrompt[++i] - '0';
}
if( onoff ) sqlite3_str_appendchar(pOut, 1, v);
zPrompt += i+1;
i = -1;
continue;
}
if( c=='e' ){
/* \e is \033 "Escape" */
if( onoff ) sqlite3_str_append(pOut, "\033", 1);
zPrompt += 2;
i = -1;
continue;
}
/* The intent of the following codes it to provide alternative
** text displays depending on whether or not the connection is
** currently in a transaction. Example 1: Show a "*" before the ">"
** like psql:
**
** .prompt 'sqlite\x*\;> '
**
** Example 2: Show database filename is blue if not in a transaction,
** or red if within a transaction:
**
** .prompt '\e[1;\x31\:34\;m\f>\e[0m '
*/
if( c==':' ){
/* toggle display on/off */
onoff = !onoff;
zPrompt += 2;
i = -1;
continue;
}
if( c==';' ){
/* Turn display on */
onoff = 1;
zPrompt += 2;
i = -1;
continue;
}
if( c=='x' ){
/* \x turns display off not in a transaction, on if in txn */
onoff = p->db && !sqlite3_get_autocommit(p->db);
zPrompt += 2;
i = -1;
continue;
}
if( c=='f' || c=='F' || c=='~' ){
/* \f becomes the tail of the database filename */
/* \F becomes the full pathname */
/* \~ becomes the full pathname relative to $HOME */
if( onoff ){
const char *zFN = prompt_filename(p);
if( c=='f' ){
#ifdef _WIN32
const char *zTail = strrchr(zFN,'\\');
#else
const char *zTail = strrchr(zFN,'/');
#endif
if( zTail && zTail[1] ) zFN = &zTail[1];
}else if( c=='~' ){
const char *zHOME = getenv("HOME");
size_t nHOME = zHOME ? strlen(zHOME) : 0;
if( nHOME<strlen(zFN)
&& zHOME!=0 && zFN!=0 /* <--- these silence compiler warnings */
&& memcmp(zHOME,zFN,nHOME)==0
){
sqlite3_str_append(pOut,"~",1);
zFN += nHOME;
}
}
sqlite3_str_appendall(pOut, zFN);
}
zPrompt += 2;
i = -1;
continue;
}
if( c=='H' ){
/* \H becomes text needed to terminate current input */
if( onoff ){
sqlite3_int64 R = zPrior ? sqlite3_incomplete(zPrior) : 0;
int cc = (R>>16)&0xff;
int nParen = R>>32;
int eSemi = (R>>8)&0xff;
if( cc==0 ){
/* no-op */
}else if( cc=='-' ){
sqlite3_str_append(pOut,"\\n",3);
}else if( cc=='/' ){
sqlite3_str_append(pOut,"*/",2);
}else{
sqlite3_str_appendchar(pOut, 1, cc);
}
if( nParen>0 ){
sqlite3_str_appendf(pOut, "%.*c",nParen,')');
}
if( eSemi==1 ){
sqlite3_str_append(pOut, ";", 1);
}else if( eSemi==2 ){
sqlite3_str_append(pOut, "END;", 4);
}else if( eSemi==3 ){
sqlite3_str_append(pOut, ";END;", 5);
}
}
zPrompt += 2;
i = -1;
continue;
}
if( c=='B' ){
/* \B is a no-op for the main prompt. For the continuation prompt,
** \B expands to zero or more spaces to make the continuation prompt
** at least as wide as the main prompt. */
if( onoff ) idxSpace = sqlite3_str_length(pOut);
zPrompt += 2;
i = -1;
continue;
}
/* No match to a known escape. Generate an error. */
if( onoff ) sqlite3_str_appendf(pOut,"UNKNOWN(\"\\%c\")",c);
zPrompt += 2;
i = -1;
}
if( i>0 && onoff ){
sqlite3_str_append(pOut, zPrompt, i);
}
/* Expand the \B, if there is one and if this is a continuation prompt */
if( idxSpace>=0 && zPrior!=0 && zPrior[0]!=0 ){
char *zOther = expand_prompt(p, 0, prompt_string(p,0));
size_t wOther = sqlite3_qrf_wcswidth(zOther);
size_t wThis = sqlite3_qrf_wcswidth(sqlite3_str_value(pOut));
sqlite3_free(zOther);
if( wOther>wThis ){
char *z;
int len;
size_t nNew = wOther - wThis;
assert( nNew>0 && nNew<0x7fffffff );
sqlite3_str_appendchar(pOut, (int)nNew, ' ');
len = sqlite3_str_length(pOut);
if( len>idxSpace ){
z = sqlite3_str_value(pOut);
memmove(z+idxSpace+nNew, z+idxSpace, len-nNew-idxSpace);
memset(z+idxSpace, ' ', nNew);
}
}
}
return sqlite3_str_finish(pOut);
}
/*
** Retrieve a single line of input text.
**
** If in==0 then read from standard input and prompt before each line.
** If isContinuation is true, then a continuation prompt is appropriate.
** If isContinuation is zero, then the main prompt should be used.
** If bContinue is true, then a continuation prompt is appropriate.
** If bContinue is zero, then the main prompt should be used.
**
** If zPrior is not NULL then it is a buffer from a prior call to this
** routine that can be reused.
@@ -1039,14 +1162,20 @@ static char *local_getline(char *zLine, FILE *in){
** zPrior argument for reuse.
*/
#ifndef SQLITE_SHELL_FIDDLE
static char *one_input_line(ShellState *p, char *zPrior, int isContinuation){
char *zPrompt;
static char *one_input_line(
ShellState *p, /* Shell state */
char *zPrior, /* Just the previous line */
const char *zAll, /* All statement text accumulated so far */
int bContinue /* True for a continuation prompt */
){
char *zResult;
FILE *in = p->in;
if( in!=0 ){
zResult = local_getline(zPrior, in);
}else{
zPrompt = isContinuation ? CONTINUATION_PROMPT : mainPrompt;
const char *zBase = prompt_string(p, bContinue!=0);
char *zPrompt = expand_prompt(p, zAll, zBase);
shell_check_oom(zPrompt);
#if SHELL_USE_LOCAL_GETLINE
sputz(stdout, zPrompt);
fflush(stdout);
@@ -1067,6 +1196,7 @@ static char *one_input_line(ShellState *p, char *zPrior, int isContinuation){
}
if( zResult && *zResult ) shell_add_history(zResult);
#endif
sqlite3_free(zPrompt);
}
return zResult;
}
@@ -1387,6 +1517,54 @@ static void shellAddSchemaName(
sqlite3_result_value(pCtx, apVal[0]);
}
/*
** SQL function: shell_prompt_test(PROMPT)
** shell_prompt_test(PROMPT,PRIOR)
** shell_prompt_test(PROMPT,PRIOR,FILENAME)
**
** Return the shell prompt, with escapes expanded, for testing purposes.
** The first argument is the raw (unexpanded) prompt string. Or if the
** first argument is NULL, then use whatever prompt string is currently
** configured. If the second argument exists and is not NULL, then the
** second argument is understood to be prior incomplete text and a
** continuation prompt is generated. If a third argument is provided,
** it is assumed to be the full pathname of the database file.
*/
static void shellExpandPrompt(
sqlite3_context *pCtx,
int nVal,
sqlite3_value **apVal
){
ShellState *p = (ShellState*)sqlite3_user_data(pCtx);
const char *zPrompt;
const char *zPrior;
const char *zSavedDbFile;
int mSavedFlgs;
const char *zFName;
char *zRes;
if( nVal<2
|| (zPrior = (const char*)sqlite3_value_text(apVal[1]))==0
|| zPrior[0]==0
){
zPrior = 0;
}
zPrompt = (const char*)sqlite3_value_text(apVal[0]);
if( zPrompt==0 ){
zPrompt = prompt_string(p, zPrior!=0);
}
zSavedDbFile = p->pAuxDb->zDbFilename;
mSavedFlgs = p->pAuxDb->mFlgs;
if( nVal>=3 && (zFName = (const char*)sqlite3_value_text(apVal[2]))!=0 ){
p->pAuxDb->zDbFilename = zFName;
p->pAuxDb->mFlgs |= 0x001;
}
zRes = expand_prompt(p, zPrior, zPrompt);
p->pAuxDb->zDbFilename = zSavedDbFile;
p->pAuxDb->mFlgs = mSavedFlgs;
sqlite3_result_text(pCtx, zRes, -1, SQLITE_TRANSIENT);
sqlite3_free(zRes);
}
/************************* BEGIN PERFORMANCE TIMER *****************************/
#if !defined(_WIN32) && !defined(WIN32) && !defined(__minux)
@@ -3864,7 +4042,10 @@ static const char *(azHelp[]) = {
" --reset Reset the count for each input and interrupt",
" --timeout S Halt after running for S seconds",
#endif
".prompt MAIN CONTINUE Replace the standard prompts",
".prompt MAIN CONTINUE Replace the standard prompts",
" --reset Revert to default prompts",
" --show Show the current prompt strings",
" -- No more options. Subsequent args are prompts",
#ifndef SQLITE_SHELL_FIDDLE
".quit Stop interpreting input stream, exit if primary.",
".read FILE Read input from FILE or command output",
@@ -4476,6 +4657,31 @@ static void shellModuleSchema(
static void open_db(ShellState *p, int openFlags){
if( p->db==0 ){
const char *zDbFilename = p->pAuxDb->zDbFilename;
const char *zHOME; /* Value of HOME environment variable */
char *zToFree = 0; /* Filename with ~/ prefix expansion */
/* If the zDbFilename does not exist and begins with ~/ then replace
** the ~ with the home directory.
*/
if( zDbFilename && zDbFilename[0]=='~'
#ifdef _WIN32
&& (zDbFilename[1]=='/' || zDbFilename[1]=='\\')
#else
&& zDbFilename[1]=='/'
#endif
&& access(zDbFilename,0)!=0
&& (zHOME = getenv("HOME"))!=0 && zHOME[0]!=0
){
size_t nHOME = strlen(zHOME);
size_t nFN = strlen(zDbFilename);
zToFree = malloc(nHOME+nFN+1);
if( zToFree ){
memcpy(zToFree, zHOME, nHOME);
memcpy(zToFree+nHOME, zDbFilename+1, nFN);
zDbFilename = zToFree;
}
}
if( p->openMode==SHELL_OPEN_UNSPEC ){
if( zDbFilename==0 || zDbFilename[0]==0 ){
p->openMode = SHELL_OPEN_NORMAL;
@@ -4609,6 +4815,13 @@ static void open_db(ShellState *p, int openFlags){
sqlite3_create_function(p->db, "edit", 2, SQLITE_UTF8, 0,
editFunc, 0, 0);
#endif
sqlite3_create_function(p->db, "shell_prompt_test", 1, SQLITE_UTF8,
p, shellExpandPrompt, 0, 0);
sqlite3_create_function(p->db, "shell_prompt_test", 2, SQLITE_UTF8,
p, shellExpandPrompt, 0, 0);
sqlite3_create_function(p->db, "shell_prompt_test", 3, SQLITE_UTF8,
p, shellExpandPrompt, 0, 0);
if( p->openMode==SHELL_OPEN_ZIPFILE ){
char *zSql = sqlite3_mprintf(
@@ -4642,6 +4855,7 @@ static void open_db(ShellState *p, int openFlags){
}
}
#endif
free(zToFree);
}
if( p->db!=0 ){
#ifndef SQLITE_OMIT_AUTHORIZATION
@@ -10436,12 +10650,39 @@ static int do_meta_command(const char *zLine, ShellState *p){
#endif /* SQLITE_OMIT_PROGRESS_CALLBACK */
if( c=='p' && cli_strncmp(azArg[0], "prompt", n)==0 ){
if( nArg >= 2) {
shell_strncpy(mainPrompt,azArg[1],(int)ArraySize(mainPrompt)-1);
}
if( nArg >= 3) {
shell_strncpy(continuePrompt,azArg[2],(int)ArraySize(continuePrompt)-1);
}
int i;
int cnt = 0;
int noOpt = 0;
for(i=1; i<nArg; i++){
const char *z = azArg[i];
if( z[0]=='-' && !noOpt ){
if( z[1]=='-' ) z++;
if( strcmp(z,"-reset")==0 ){
free(p->azPrompt[0]);
free(p->azPrompt[1]);
memset(p->azPrompt, 0, sizeof(p->azPrompt));
}else
if( strcmp(z,"-show")==0 ){
cli_printf(stdout,"Main prompt: '%s'\n", prompt_string(p, 0));
cli_printf(stdout,"Continuation: '%s'\n", prompt_string(p, 1));
}else
if( strcmp(z,"-")==0 ){
noOpt = 1;
}else
{
dotCmdError(p, i, "unknown option", 0);
rc = 1;
goto meta_command_exit;
}
}else if( cnt>1 ){
dotCmdError(p, i, "extra argument", 0);
rc = 1;
goto meta_command_exit;
}else{
free(p->azPrompt[cnt]);
p->azPrompt[cnt] = strdup(z);
}
}
}else
#ifndef SQLITE_SHELL_FIDDLE
@@ -12074,104 +12315,28 @@ meta_command_exit:
return rc;
}
/* Line scan result and intermediate states (supporting scan resumption)
*/
#ifndef CHAR_BIT
# define CHAR_BIT 8
#endif
typedef enum {
QSS_HasDark = 1<<CHAR_BIT, QSS_EndingSemi = 2<<CHAR_BIT,
QSS_CharMask = (1<<CHAR_BIT)-1, QSS_ScanMask = 3<<CHAR_BIT,
QSS_Start = 0
} QuickScanState;
#define QSS_SETV(qss, newst) ((newst) | ((qss) & QSS_ScanMask))
#define QSS_INPLAIN(qss) (((qss)&QSS_CharMask)==QSS_Start)
#define QSS_PLAINWHITE(qss) (((qss)&~QSS_EndingSemi)==QSS_Start)
#define QSS_PLAINDARK(qss) (((qss)&~QSS_EndingSemi)==QSS_HasDark)
#define QSS_SEMITERM(qss) (((qss)&~QSS_HasDark)==QSS_EndingSemi)
/*
** Scan line for classification to guide shell's handling.
** The scan is resumable for subsequent lines when prior
** return values are passed as the 2nd argument.
** Test to see if a line consists entirely of whitespace.
*/
static QuickScanState quickscan(char *zLine, QuickScanState qss,
SCAN_TRACKER_REFTYPE pst){
char cin;
char cWait = (char)qss; /* intentional narrowing loss */
if( cWait==0 ){
PlainScan:
while( (cin = *zLine++)!=0 ){
if( IsSpace(cin) )
continue;
switch (cin){
case '-':
if( *zLine!='-' )
break;
while((cin = *++zLine)!=0 )
if( cin=='\n')
goto PlainScan;
return qss;
case ';':
qss |= QSS_EndingSemi;
continue;
case '/':
if( *zLine=='*' ){
++zLine;
cWait = '*';
CONTINUE_PROMPT_AWAITS(pst, "/*");
qss = QSS_SETV(qss, cWait);
goto TermScan;
}
break;
case '[':
cin = ']';
deliberate_fall_through; /* FALLTHRU */
case '`': case '\'': case '"':
cWait = cin;
qss = QSS_HasDark | cWait;
CONTINUE_PROMPT_AWAITC(pst, cin);
goto TermScan;
case '(':
CONTINUE_PAREN_INCR(pst, 1);
break;
case ')':
CONTINUE_PAREN_INCR(pst, -1);
break;
default:
break;
}
qss = (qss & ~QSS_EndingSemi) | QSS_HasDark;
static int line_is_all_whitespace(const char *z){
for(; *z; z++){
if( IsSpace(z[0]) ) continue;
if( *z=='/' && z[1]=='*' ){
z += 2;
while( *z && (*z!='*' || z[1]!='/') ){ z++; }
if( *z==0 ) return 0;
z++;
continue;
}
}else{
TermScan:
while( (cin = *zLine++)!=0 ){
if( cin==cWait ){
switch( cWait ){
case '*':
if( *zLine != '/' )
continue;
++zLine;
CONTINUE_PROMPT_AWAITC(pst, 0);
qss = QSS_SETV(qss, 0);
goto PlainScan;
case '`': case '\'': case '"':
if(*zLine==cWait){
/* Swallow doubled end-delimiter.*/
++zLine;
continue;
}
deliberate_fall_through; /* FALLTHRU */
case ']':
CONTINUE_PROMPT_AWAITC(pst, 0);
qss = QSS_SETV(qss, 0);
goto PlainScan;
default: assert(0);
}
}
if( *z=='-' && z[1]=='-' ){
z += 2;
while( *z && *z!='\n' ){ z++; }
if( *z==0 ) return 1;
continue;
}
return 0;
}
return qss;
return 1;
}
/*
@@ -12179,15 +12344,16 @@ static QuickScanState quickscan(char *zLine, QuickScanState qss,
** than a semi-colon. The SQL Server style "go" command is understood
** as is the Oracle "/".
*/
static int line_is_command_terminator(char *zLine){
static int line_is_command_terminator(const char *zLine){
while( IsSpace(zLine[0]) ){ zLine++; };
if( zLine[0]=='/' )
zLine += 1; /* Oracle */
else if ( ToLower(zLine[0])=='g' && ToLower(zLine[1])=='o' )
zLine += 2; /* SQL Server */
else
return 0;
return quickscan(zLine, QSS_Start, 0)==QSS_Start;
if( zLine[0]=='/' && line_is_all_whitespace(&zLine[1]) ){
return 1; /* Oracle */
}
if( ToLower(zLine[0])=='g' && ToLower(zLine[1])=='o'
&& line_is_all_whitespace(&zLine[2]) ){
return 1; /* SQL Server */
}
return 0;
}
/*
@@ -12375,7 +12541,12 @@ static void echo_group_input(ShellState *p, const char *zDo){
** impl because we need the global shellState and cannot access it from that
** function without moving lots of code around (creating a larger/messier diff).
*/
static char *one_input_line(ShellState *p, char *zPrior, int isContinuation){
static char *one_input_line(
ShellState *p, /* Shell state */
char *zPrior, /* Just the prior line of text */
const char *zAll, /* All accumulated statement text */
int bContinue /* True if this is an accumulation */
){
/* Parse the next line from shellState.wasm.zInput. */
const char *zBegin = shellState.wasm.zPos;
const char *z = zBegin;
@@ -12384,7 +12555,7 @@ static char *one_input_line(ShellState *p, char *zPrior, int isContinuation){
FILE *in = p->in;
UNUSED_PARAMETER(in);
UNUSED_PARAMETER(isContinuation);
UNUSED_PARAMETER(bContinue);
if(!z || !*z){
return 0;
}
@@ -12420,8 +12591,8 @@ static int process_input(ShellState *p, const char *zSrc){
i64 nAlloc = 0; /* Allocated zSql[] space */
int rc; /* Error code */
int errCnt = 0; /* Number of errors seen */
int hasSemi = 0; /* Input line contains a semicolon */
i64 startline = 0; /* Line number for start of current input */
QuickScanState qss = QSS_Start; /* Accumulated line status (so far) */
const char *saved_zInFile; /* Prior value of p->zInFile */
i64 saved_lineno; /* Prior value of p->lineno */
@@ -12436,10 +12607,9 @@ static int process_input(ShellState *p, const char *zSrc){
p->zInFile = zSrc;
saved_lineno = p->lineno;
p->lineno = 0;
CONTINUE_PROMPT_RESET;
while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){
fflush(p->out);
zLine = one_input_line(p, zLine, nSql>0);
zLine = one_input_line(p, zLine, zSql, nSql>0);
if( zLine==0 ){
/* End of input */
if( p->in==0 && stdin_is_interactive ) cli_puts("\n", p->out);
@@ -12450,20 +12620,12 @@ static int process_input(ShellState *p, const char *zSrc){
seenInterrupt = 0;
}
p->lineno++;
if( QSS_INPLAIN(qss)
&& line_is_command_terminator(zLine)
&& line_is_complete(zSql, nSql) ){
memcpy(zLine,";",2);
}
qss = quickscan(zLine, qss, CONTINUE_PROMPT_PSTATE);
if( QSS_PLAINWHITE(qss) && nSql==0 ){
if( nSql==0 && line_is_all_whitespace(zLine) ){
/* Just swallow single-line whitespace */
echo_group_input(p, zLine);
qss = QSS_Start;
continue;
}
if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){
CONTINUE_PROMPT_RESET;
echo_group_input(p, zLine);
if( zLine[0]=='.' ){
rc = do_meta_command(zLine, p);
@@ -12473,10 +12635,12 @@ static int process_input(ShellState *p, const char *zSrc){
errCnt++;
}
}
qss = QSS_Start;
continue;
}
/* No single-line dispositions remain; accumulate line(s). */
if( line_is_command_terminator(zLine) && line_is_complete(zSql, nSql) ){
memcpy(zLine,";",2);
}
hasSemi = strchr(zLine,';')!=0;
nLine = strlen(zLine);
if( nSql+nLine+2>=nAlloc ){
/* Grow buffer by half-again increments when big. */
@@ -12504,10 +12668,10 @@ static int process_input(ShellState *p, const char *zSrc){
nSql = 0;
errCnt++;
break;
}else if( nSql && QSS_SEMITERM(qss) && sqlite3_complete(zSql) ){
}
if( nSql && hasSemi && sqlite3_complete(zSql) ){
echo_group_input(p, zSql);
errCnt += runOneSqlLine(p, zSql, p->zInFile, startline);
CONTINUE_PROMPT_RESET;
nSql = 0;
if( p->nPopOutput ){
output_reset(p);
@@ -12520,18 +12684,15 @@ static int process_input(ShellState *p, const char *zSrc){
p->nPopMode = 0;
}
p->bSafeMode = p->bSafeModePersist;
qss = QSS_Start;
}else if( nSql && QSS_PLAINWHITE(qss) ){
}else if( nSql && line_is_all_whitespace(zSql) ){
echo_group_input(p, zSql);
nSql = 0;
qss = QSS_Start;
}
}
if( nSql ){
/* This may be incomplete. Let the SQL parser deal with that. */
echo_group_input(p, zSql);
errCnt += runOneSqlLine(p, zSql, p->zInFile, startline);
CONTINUE_PROMPT_RESET;
}
free(zSql);
free(zLine);
@@ -12819,8 +12980,6 @@ static void main_init(ShellState *p) {
#endif
sqlite3_config(SQLITE_CONFIG_URI, 1);
sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
sqlite3_snprintf(sizeof(mainPrompt), mainPrompt,"sqlite> ");
sqlite3_snprintf(sizeof(continuePrompt), continuePrompt," ...> ");
}
/*
@@ -13558,7 +13717,6 @@ int SQLITE_CDECL main(int argc, char **argv){
if( rc ){
goto shell_main_exit;
}
}
if( data.nPopOutput && azCmd[i][0]!='.' ){
output_reset(&data);
@@ -13658,6 +13816,8 @@ int SQLITE_CDECL main(int argc, char **argv){
free(data.dot.azArg);
free(data.dot.aiOfst);
free(data.dot.abQuot);
free(data.azPrompt[0]);
free(data.azPrompt[1]);
if( data.nTestRun ){
sqlite3_fprintf(stdout, "%d test%s run with %d error%s\n",
data.nTestRun, data.nTestRun==1 ? "" : "s",
@@ -13817,4 +13977,8 @@ void fiddle_exec(const char * zSql){
shellState.wasm.zInput = shellState.wasm.zPos = 0;
}
}
char *fiddle_get_prompt(void){
return expand_prompt(&shellState, 0, prompt_string(&shellState,0));
}
#endif /* SQLITE_SHELL_FIDDLE */
+20 -8
View File
@@ -2960,8 +2960,9 @@ int sqlite3_is_interrupted(sqlite3*);
** These routines are useful during command-line input to determine if the
** currently entered text seems to form a complete SQL statement or
** if additional input is needed before sending the text into
** SQLite for parsing. ^These routines return 1 if the input string
** appears to be a complete SQL statement. ^A statement is judged to be
** SQLite for parsing. ^The sqlite3_complete(X) and sqlite3_complete16(X)
** routines return 1 if the input string X appears to be a complete SQL
** statement. ^A statement is judged to be
** complete if it ends with a semicolon token and is not a prefix of a
** well-formed CREATE TRIGGER statement. ^Semicolons that are embedded within
** string literals or quoted identifier names or comments are not
@@ -2969,11 +2970,21 @@ int sqlite3_is_interrupted(sqlite3*);
** embedded) and thus do not count as a statement terminator. ^Whitespace
** and comments that follow the final semicolon are ignored.
**
** ^These routines return 0 if the statement is incomplete. ^If a
** memory allocation fails, then SQLITE_NOMEM is returned.
** ^The sqlite3_complete(X) and sqlite3_complete16(X) routines return 0
** if the statement is incomplete. ^If a memory allocation fails, then
** SQLITE_NOMEM is returned.
**
** ^These routines do not parse the SQL statements and thus
** will not detect syntactically incorrect SQL.
** The [sqlite3_incomplete(X)] routine is similar to [sqlite3_complete(X)]
** except that sqlite3_incomplete(X) returns 0 if the input X is complete
** and non-zero if X is incomplete. The non-zero return from
** sqlite3_incomplete(X) contains additional information about what is
** needed to complete the input X. The sqlite3_incomplete(X) interface
** is only available for UTF-8 text.
**
** ^None of these routines do a full parse the SQL statements and thus
** will not detect syntactically incorrect SQL. They only determine if
** input text has properly terminated comments, string literals, and
** quoted identifiers, and if the statement ends with a semicolon.
**
** ^(If SQLite has not been initialized using [sqlite3_initialize()] prior
** to invoking sqlite3_complete16() then sqlite3_initialize() is invoked
@@ -2981,14 +2992,15 @@ int sqlite3_is_interrupted(sqlite3*);
** then the return value from sqlite3_complete16() will be non-zero
** regardless of whether or not the input SQL is complete.)^
**
** The input to [sqlite3_complete()] must be a zero-terminated
** UTF-8 string.
** The X input to [sqlite3_complete(X)] and [sqlite3_incomplete(X)
** must be a zero-terminated UTF-8 string.
**
** The input to [sqlite3_complete16()] must be a zero-terminated
** UTF-16 string in native byte order.
*/
int sqlite3_complete(const char *sql);
int sqlite3_complete16(const void *sql);
sqlite3_int64 sqlite3_incomplete(const char *sql);
/*
** CAPI3REF: Register A Callback To Handle SQLITE_BUSY Errors
+84
View File
@@ -0,0 +1,84 @@
#!sqlite3
#
# 2026-04-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.
#
#***********************************************************************
#
# Tests for the .prompt command and prompt rendering.
#
# ./sqlite3 test/shell-prompt.sql
#
.testcase setup
.open -new test.db
.mode list -quote off -escape ascii
.check ''
.testcase 100
.prompt
.check ''
.testcase 110
.prompt --show
.check <<END
Main prompt: 'SQLite \f> '
Continuation: '\B...\H> '
END
.testcase 1000
SELECT shell_prompt_test(NULL);
.check 'SQLite test.db> ';
.testcase 1001
SELECT shell_prompt_test(NULL,'SELECT');
.check ' ...;> ';
.testcase 1002
SELECT shell_prompt_test(NULL,'SELECT ((("');
.check ' ...")));> ';
.testcase 1003
SELECT shell_prompt_test(NULL,'SELECT ((()[');
.check ' ...]));> ';
.testcase 1004
SELECT shell_prompt_test(NULL,'SELECT ''');
.check " ...';> ";
.testcase 1005
SELECT shell_prompt_test(NULL,'CREATE TRIGGER t1 BEGIN');
.check " ...;END;> ";
.testcase 1006
SELECT shell_prompt_test(NULL,'CREATE TRIGGER t1 BEGIN SELECT ((([');
.check " ...])));END;> ";
.testcase 1007
SELECT shell_prompt_test(NULL,'CREATE TRIGGER t1 BEGIN SELECT ((/*a(((''bc');
.check " ...*/));END;> ";
.testcase 1008
SELECT shell_prompt_test(NULL,'CREATE TRIGGER t1 BEGIN SELECT 1;');
.check " ...END;> ";
.testcase 2000
.prompt 'SQLite\x-txn$\:>\; '
SELECT shell_prompt_test(NULL);
.check 'SQLite> ';
.testcase 2001
BEGIN;
SELECT shell_prompt_test(NULL);
.check 'SQLite-txn$ ';
.testcase 2002
ROLLBACK;
SELECT shell_prompt_test(NULL);
.check 'SQLite> ';
.testcase 2003
.prompt -- '--show '
SELECT shell_prompt_test(NULL);
.check '--show ';
.testcase 2004
.prompt --reset
SELECT shell_prompt_test(NULL);
.check 'SQLite test.db> ';
.testcase
+1
View File
@@ -49,5 +49,6 @@ ifcapable vtab {
do_clitest intck01.sql
}
do_clitest fptest01.sql
do_clitest shell-prompt.sql
finish_test