From f62641e91cc0aee1a24ea4a3c81289e4e821c4e5 Mon Sep 17 00:00:00 2001 From: drh <> Date: Fri, 24 Dec 2021 20:22:13 +0000 Subject: [PATCH] Add the sqlite3_error_offset() interface. Use it in the CLI to provide better context for error messages. FossilOrigin-Name: b518ce77439852759bc0901071f36d622b1314c9bf3d29c279dfcc405188b975 --- manifest | 31 +++++++++++++----------- manifest.uuid | 2 +- src/loadext.c | 2 ++ src/main.c | 13 +++++++++++ src/printf.c | 25 ++++++++++++++++++++ src/shell.c.in | 61 ++++++++++++++++++++++++++++++++++++++++++------ src/sqlite.h.in | 11 ++++++++- src/sqlite3ext.h | 4 ++++ src/sqliteInt.h | 2 ++ src/test1.c | 29 +++++++++++++++++++++++ src/util.c | 29 ++++++++--------------- 11 files changed, 167 insertions(+), 42 deletions(-) diff --git a/manifest b/manifest index a6c925887d..086b4676d3 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sa\sminor\sproblem\sin\sthe\sCLI\sintroduced\sby\s[d156123885abe6bf],\sapparently. -D 2021-12-24T19:44:11.241 +C Add\sthe\ssqlite3_error_offset()\sinterface.\s\sUse\sit\sin\sthe\sCLI\sto\sprovide\nbetter\scontext\sfor\serror\smessages. +D 2021-12-24T20:22:13.535 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -513,8 +513,8 @@ F src/hwtime.h cb1d7e3e1ed94b7aa6fde95ae2c2daccc3df826be26fc9ed7fd90d1750ae6144 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 F src/insert.c e0293a6f686e18cb2c9dd0619a731518e0109d7e1f1db1932974659e7843cfd1 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa -F src/loadext.c e1dcff1c916bf6834e150b492eddda5d9792453182d2ad64294d2266b6e93c4c -F src/main.c 674a0fdfc2808e1d5a78b2eefe2ec3f93428cf82f0f6c013d577df1a1caa5940 +F src/loadext.c 95db1fe62c5973f1c5d9c53f6083e21a73ece14cdd47eeca0639691332e85c4d +F src/main.c d7719ea81cc8ea81253cb2af0603d089407ea70dd5742f53a81ee3c168186344 F src/malloc.c ef796bcc0e81d845d59a469f1cf235056caf9024172fd524e32136e65593647b F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de @@ -546,21 +546,21 @@ F src/pcache1.c 54881292a9a5db202b2c0ac541c5e3ef9a5e8c4f1c1383adb2601d5499a60e65 F src/pragma.c c536665ce8431c8b1efbf7e0a5c01852f49f7bf28f1954f8118b2d28e4a3797f F src/pragma.h 87330ed2fbfa2a1274de93ca0ab850fba336189228cb256089202c3b52766fad F src/prepare.c 40961a1170a4c4151a90dae29dd00fc6c155f1af8246abeeeb8f0a10b3fb9719 -F src/printf.c 9565aeb5af5376fd23c993b8da1ac37008fad65435a703316eef9f41229f702d +F src/printf.c 975f1f5417f2526365b6e6d7f22332e3e11806dad844701d92846292b654ba9a F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c F src/resolve.c 6144db65157ff96fe27e3e6f784ab331b3de35db233cd0a6e93278a5d23fcf06 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 F src/select.c a7a3d9f54eb24821ec5f67f2e5589b68a5d42d46fc5849d7376886777d93a85a -F src/shell.c.in 1b567fc5463470fb22f3e45a5dbf05ee2ce9dd9066b2973c4a2bdd7daa74bb5f -F src/sqlite.h.in 5124aa99d3eb9b6a4e5030cb035076ed060d2dac020cd8fc8fba3022725d54af +F src/shell.c.in c2f60a11f4a0720cd952d14ec20331543fb185a982e10a2385c5715e129369fd +F src/sqlite.h.in a5e0d6bd47e67aabf1475986d36bdcc7bfa9e06566790ebf8e3aa7fa551c9f99 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 -F src/sqlite3ext.h 8ff2fd2c166150b2e48639f5e506fb44e29f1a3f65031710b9e89d1c126ac839 -F src/sqliteInt.h e7dd69a85c53461c937d50c97f8efd9761b14f9bcfb63450809e85c100c08538 +F src/sqlite3ext.h 01eb85e4f2759a5ee79c183f4b2877889d4ffdc49d27ae74529c9579e3c8c0ef +F src/sqliteInt.h 418dd1a178abcc40b86b39cee972083822b17e84888fe56ed7a3b00b473d8bb5 F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657 F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 F src/tclsqlite.c 48f291e1a7e672a7204884d4c164a8ed3a522ff087c361ada2991f5d54e987f6 -F src/test1.c 37b323d74aef3156394478bd09dff855332077e537b37c9a7e455c03cb04da09 +F src/test1.c c24fe3a13ff78f5cbc29ac7049123e03ee1f33c6601646a45766c5f8d5db7f92 F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5 F src/test3.c 61798bb0d38b915067a8c8e03f5a534b431181f802659a6616f9b4ff7d872644 F src/test4.c 7c4420e01c577b5c4add2cb03119743b1a357543d347773b9e717195ea967159 @@ -620,7 +620,7 @@ F src/trigger.c 2ef56f0b7b75349a5557d0604b475126329c2e1a02432e7d49c4c710613e8254 F src/update.c d6f5c7b9e072660757ac7d58175aca11c07cb95ebbb297ae7f38853700f52328 F src/upsert.c 8789047a8f0a601ea42fa0256d1ba3190c13746b6ba940fe2d25643a7e991937 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 -F src/util.c 569349b0bddcbfbc661856f446adb92e1b0a47b3cbef548da9fc5aa639d7964c +F src/util.c 89e51820bcb468ff3877a8d942f5cc807208087f021227e0927693e928a195bc F src/vacuum.c 6c38ddc52f0619865c91dae9c441d4d48bf3040d7dc1bc5b22da1e45547ed0b3 F src/vdbe.c b63594839cbf770a29e2b3b81570971a3d2c1c995c0586aade09bb548142113a F src/vdbe.h 25dabb25c7e157b84e59260cfb5b466c3ac103ede9f36f4db371332c47601abe @@ -1934,8 +1934,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 8af8c153f8c3fe16db1c2280571e005838c4ea3c48929ad7660a1686e49ed255 -R b3820a24a997ecbc0d96c676f9253e0d +P 37e6e10f4364f556c7503c80408fc62895cdccdd0372fb2b63aaca02c3a1ee30 +R dc8a0d1713a6a184c2032656b8115b23 +T *branch * improved-error-context +T *sym-improved-error-context * +T -sym-trunk * U drh -Z 32815daf40ab0d51da0a9e14b1eae912 +Z 3c3faf346f54edef0799304c35a23ad2 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index b59febd5a3..4479c72102 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -37e6e10f4364f556c7503c80408fc62895cdccdd0372fb2b63aaca02c3a1ee30 \ No newline at end of file +b518ce77439852759bc0901071f36d622b1314c9bf3d29c279dfcc405188b975 \ No newline at end of file diff --git a/src/loadext.c b/src/loadext.c index 4edefec0c9..681e12c4e7 100644 --- a/src/loadext.c +++ b/src/loadext.c @@ -485,6 +485,8 @@ static const sqlite3_api_routines sqlite3Apis = { sqlite3_total_changes64, /* Version 3.37.0 and later */ sqlite3_autovacuum_pages, + /* Version 3.38.0 and later */ + sqlite3_error_offset, }; /* True if x is the directory separator character diff --git a/src/main.c b/src/main.c index 1b7853f609..07c67d33d7 100644 --- a/src/main.c +++ b/src/main.c @@ -2597,6 +2597,19 @@ const char *sqlite3_errmsg(sqlite3 *db){ return z; } +/* +** Return the byte offset of the most recent error +*/ +int sqlite3_error_offset(sqlite3 *db){ + int iOffset = -1; + if( db && sqlite3SafetyCheckSickOrOk(db) ){ + sqlite3_mutex_enter(db->mutex); + iOffset = db->errByteOffset; + sqlite3_mutex_leave(db->mutex); + } + return iOffset; +} + #ifndef SQLITE_OMIT_UTF16 /* ** Return UTF-16 encoded English language explanation of the most recent diff --git a/src/printf.c b/src/printf.c index 6128e5cb4c..9fea2d6ef0 100644 --- a/src/printf.c +++ b/src/printf.c @@ -855,6 +855,7 @@ void sqlite3_str_vappendf( assert( bArgList==0 ); if( pToken && pToken->n ){ sqlite3_str_append(pAccum, (const char*)pToken->z, pToken->n); + sqlite3RecordErrorByteOffset(pAccum->db, pToken->z); } length = width = 0; break; @@ -909,6 +910,30 @@ void sqlite3_str_vappendf( }/* End for loop over the format string */ } /* End of function */ + +/* +** The z string points to the first character of a token that is +** associated with an error. If db does not already have an error +** byte offset recorded, try to compute the error byte offset for +** z and set the error byte offset in db. +*/ +void sqlite3RecordErrorByteOffset(sqlite3 *db, const char *z){ + const Parse *pParse; + const char *zText; + const char *zEnd; + assert( z!=0 ); + if( NEVER(db==0) ) return; + if( db->errByteOffset!=(-2) ) return; + pParse = db->pParse; + if( NEVER(pParse==0) ) return; + zText =pParse->zTail; + if( NEVER(zText==0) ) return; + zEnd = &zText[strlen(zText)]; + if( SQLITE_WITHIN(z,zText,zEnd) ){ + db->errByteOffset = (int)(z-zText); + } +} + /* ** Enlarge the memory allocation on a StrAccum object so that it is ** able to accept at least N more bytes of text. diff --git a/src/shell.c.in b/src/shell.c.in index c6f623f5b7..c8d7692560 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -2519,6 +2519,47 @@ static void set_table_name(ShellState *p, const char *zName){ z[n] = 0; } +/* +** Maybe construct two lines of text that point out the position of a +** syntax error. Return a pointer to the text, in memory obtained from +** sqlite3_malloc(). Or, if the most recent error does not involve a +** specific token that we can point to, return an empty string. +** +** In all cases, the memory returned is obtained from sqlite3_malloc64() +** and should be released by the caller invoking sqlite3_free(). +*/ +static char *shell_error_context(const char *zSql, sqlite3 *db){ + int iOffset; + size_t len; + char *zCode; + char *zMsg; + int i; + if( db==0 + || zSql==0 + || (iOffset = sqlite3_error_offset(db))<0 + ){ + return sqlite3_mprintf(""); + } + while( iOffset>50 ){ + iOffset--; + zSql++; + while( (zSql[0]&0xc0)==0x80 ){ zSql++; iOffset--; } + } + len = strlen(zSql); + if( len>78 ){ + len = 78; + while( (zSql[len]&0xc0)==0x80 ) len--; + } + zCode = sqlite3_mprintf("%.*s", len, zSql); + for(i=0; zCode[i]; i++){ if( IsSpace(zSql[i]) ) zCode[i] = ' '; } + if( iOffset<25 ){ + zMsg = sqlite3_mprintf("\n %z\n %*s^--- error here", zCode, iOffset, ""); + }else{ + zMsg = sqlite3_mprintf("\n %z\n %*serror here ---^", zCode, iOffset-14, ""); + } + return zMsg; +} + /* ** Execute a query statement that will generate SQL output. Print @@ -2541,8 +2582,10 @@ static int run_table_dump_query( const char *z; rc = sqlite3_prepare_v2(p->db, zSelect, -1, &pSelect, 0); if( rc!=SQLITE_OK || !pSelect ){ - utf8_printf(p->out, "/**** ERROR: (%d) %s *****/\n", rc, - sqlite3_errmsg(p->db)); + char *zContext = shell_error_context(zSelect, p->db); + utf8_printf(p->out, "/**** ERROR: (%d) %s *****/\n%s", rc, + sqlite3_errmsg(p->db), zContext); + sqlite3_free(zContext); if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++; return rc; } @@ -2578,12 +2621,16 @@ static int run_table_dump_query( static char *save_err_msg( sqlite3 *db, /* Database to query */ const char *zWhen, /* Qualifier (format) wrapper */ - int rc /* Error code returned from API */ + int rc, /* Error code returned from API */ + const char *zSql /* SQL string, or NULL */ ){ char *zErr; - if( zWhen==0 ) zWhen = "%s (%d)"; - zErr = sqlite3_mprintf(zWhen, sqlite3_errmsg(db), rc); + char *zContext; + if( zWhen==0 ) zWhen = "%s (%d)%s"; + zContext = shell_error_context(zSql, db); + zErr = sqlite3_mprintf(zWhen, sqlite3_errmsg(db), rc, zContext); shell_check_oom(zErr); + sqlite3_free(zContext); return zErr; } @@ -3518,7 +3565,7 @@ static int shell_exec( rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover); if( SQLITE_OK != rc ){ if( pzErrMsg ){ - *pzErrMsg = save_err_msg(db, "in prepare, %s (%d)", rc); + *pzErrMsg = save_err_msg(db, "in prepare, %s (%d)%s", rc, zSql); } }else{ if( !pStmt ){ @@ -3634,7 +3681,7 @@ static int shell_exec( zSql = zLeftover; while( IsSpace(zSql[0]) ) zSql++; }else if( pzErrMsg ){ - *pzErrMsg = save_err_msg(db, "stepping, %s (%d)", rc); + *pzErrMsg = save_err_msg(db, "stepping, %s (%d)", rc, 0); } /* clear saved stmt handle */ diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 3c92503a11..3aad3690a2 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -3824,13 +3824,14 @@ void sqlite3_free_filename(char*); ** sqlite3_extended_errcode() might change with each API call. ** Except, there are some interfaces that are guaranteed to never ** change the value of the error code. The error-code preserving -** interfaces are: +** interfaces include the following: ** **