From: drh <> Date: Fri, 10 Apr 2026 13:43:48 +0000 (+0000) Subject: Prototype sqlite3_incomplete() - a variant of sqlite3_complete() that returns X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=693b15ac83ae826e146cded2fbd3ca6a38f1d841;p=thirdparty%2Fsqlite.git Prototype sqlite3_incomplete() - a variant of sqlite3_complete() that returns additional information about what is needed to complete the statement. FossilOrigin-Name: a698fbfb8bb256ee0d9802c01562c11492ca2800a45360b268ac6cf397a7fad7 --- diff --git a/manifest b/manifest index a9c024d830..1af3a09a0f 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Improvements\sto\sthe\sway\s\x\sworks\sso\sthat\sit\scan\sbe\sused\sto\schange\nthe\sprompt\scolor.\s\sThis\sreally\smesses\swith\slinenoise,\sthough. -D 2026-04-10T00:55:40.521 +C Prototype\ssqlite3_incomplete()\s-\sa\svariant\sof\ssqlite3_complete()\sthat\sreturns\nadditional\sinformation\sabout\swhat\sis\sneeded\sto\scomplete\sthe\sstatement. +D 2026-04-10T13:43:48.738 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -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 @@ -736,7 +736,7 @@ F src/resolve.c 928ff887f2a7c64275182060d94d06fdddbe32226c569781cf7e7edc6f58d7fd F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 F src/select.c ffe199f025a0dd74670d2a77232bdea364a4d7b36f32c64a6572d39ba6a11576 F src/shell.c.in 2bbbb04153354fe3d11677d69ce7b6d5bfe8113e2d3693791915590eaa4f1bce -F src/sqlite.h.in e2915e4a86d5e0783afb5cb72411df38d987c7f3c5aa2d5441b8e74d30b649d8 +F src/sqlite.h.in a5605faa9479bbaac16c4ab43eb09ff50632004a8e05084d3fde56063ef73766 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 F src/sqlite3ext.h 1b7a0ee438bb5c2896d0609c537e917d8057b3340f6ad004d2de44f03e3d3cca F src/sqliteInt.h bc1cbc0c23dba35b324ae85a7dbb5fb182321bbd30857fb21f3d0cba049001a5 @@ -2197,8 +2197,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P 9cf28959671920b4909521bb1db6f0cbbc85448fbf3594c43b4000505b4e055b -R 1af695b623ae09a49f6867ddfb6fa639 +P bbee56f970ade40734c8a278f46d8a268b305fe349c3739c977fa9141d2c3b6a +R 12cd07af606e611ea7f41e67ab10d130 U drh -Z 856f286521a626031b862b8cbac557c5 +Z 547b407658ddec4e488d7004be7d0be4 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index e75a3aba4b..4c993035c5 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -bbee56f970ade40734c8a278f46d8a268b305fe349c3739c977fa9141d2c3b6a +a698fbfb8bb256ee0d9802c01562c11492ca2800a45360b268ac6cf397a7fad7 diff --git a/src/complete.c b/src/complete.c index bb2c03098e..08496366a8 100644 --- a/src/complete.c +++ b/src/complete.c @@ -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; } diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 9323ce5eea..b4ed739a61 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -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