Add the sqlite3_error_offset() interface. Use it in the CLI to provide

better context for error messages.

FossilOrigin-Name: b518ce77439852759bc0901071f36d622b1314c9bf3d29c279dfcc405188b975
This commit is contained in:
drh
2021-12-24 20:22:13 +00:00
parent c320e062a3
commit f62641e91c
11 changed files with 167 additions and 42 deletions
+2
View File
@@ -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
+13
View File
@@ -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
+25
View File
@@ -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.
+54 -7
View File
@@ -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 */
+10 -1
View File
@@ -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:
**
** <ul>
** <li> sqlite3_errcode()
** <li> sqlite3_extended_errcode()
** <li> sqlite3_errmsg()
** <li> sqlite3_errmsg16()
** <li> sqlite3_error_offset()
** </ul>
**
** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language
@@ -3845,6 +3846,13 @@ void sqlite3_free_filename(char*);
** ^(Memory to hold the error message string is managed internally
** and must not be freed by the application)^.
**
** ^If the most recent error references a specific token in the input
** SQL, the sqlite3_error_offset() interface returns the byte offset
** of the start of that token. ^The byte offset returned by
** sqlite3_error_offset() assumes that the input SQL is UTF8.
** ^If the most error does not reference a specific token in the input
** SQL, then the sqlite3_error_offset() function returns -1.
**
** When the serialized [threading mode] is in use, it might be the
** case that a second error occurs on a separate thread in between
** the time of the first error and the call to these interfaces.
@@ -3864,6 +3872,7 @@ int sqlite3_extended_errcode(sqlite3 *db);
const char *sqlite3_errmsg(sqlite3*);
const void *sqlite3_errmsg16(sqlite3*);
const char *sqlite3_errstr(int);
int sqlite3_error_offset(sqlite3 *db);
/*
** CAPI3REF: Prepared Statement Object
+4
View File
@@ -344,6 +344,8 @@ struct sqlite3_api_routines {
int (*autovacuum_pages)(sqlite3*,
unsigned int(*)(void*,const char*,unsigned int,unsigned int,unsigned int),
void*, void(*)(void*));
/* Version 3.38.0 and later */
int (*error_offset)(sqlite3*);
};
/*
@@ -655,6 +657,8 @@ typedef int (*sqlite3_loadext_entry)(
#define sqlite3_total_changes64 sqlite3_api->total_changes64
/* Version 3.37.0 and later */
#define sqlite3_autovacuum_pages sqlite3_api->autovacuum_pages
/* Version 3.38.0 and later */
#define sqlite3_error_offset sqlite3_api->error_offset
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
+2
View File
@@ -1527,6 +1527,7 @@ struct sqlite3 {
u32 nSchemaLock; /* Do not reset the schema when non-zero */
unsigned int openFlags; /* Flags passed to sqlite3_vfs.xOpen() */
int errCode; /* Most recent error code (SQLITE_*) */
int errByteOffset; /* Byte offset of error in SQL statement */
int errMask; /* & result codes with this before returning */
int iSysErrno; /* Errno value from last system error */
u32 dbOptFlags; /* Flags to enable/disable optimizations */
@@ -4977,6 +4978,7 @@ void sqlite3StrAccumSetError(StrAccum*, u8);
void sqlite3ResultStrAccum(sqlite3_context*,StrAccum*);
void sqlite3SelectDestInit(SelectDest*,int,int);
Expr *sqlite3CreateColumnExpr(sqlite3 *, SrcList *, int, int);
void sqlite3RecordErrorByteOffset(sqlite3*,const char*);
void sqlite3BackupRestart(sqlite3_backup *);
void sqlite3BackupUpdate(sqlite3_backup *, Pgno, const u8 *);
+29
View File
@@ -4366,6 +4366,34 @@ static int SQLITE_TCLAPI test_errmsg(
return TCL_OK;
}
/*
** Usage: sqlite3_error_offset DB
**
** Return the byte offset into the input UTF8 SQL for the most recent
** error, or -1 of the error does not refer to a specific token.
*/
static int SQLITE_TCLAPI test_error_offset(
void * clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
sqlite3 *db;
int iByteOffset;
if( objc!=2 ){
Tcl_AppendResult(interp, "wrong # args: should be \"",
Tcl_GetString(objv[0]), " DB", 0);
return TCL_ERROR;
}
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
iByteOffset = sqlite3_error_offset(db);
Tcl_SetObjResult(interp, Tcl_NewIntObj(zErr, -1));
return TCL_OK;
}
/*
** Usage: test_errmsg16 DB
**
@@ -8422,6 +8450,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
{ "sqlite3_errcode", test_errcode ,0 },
{ "sqlite3_extended_errcode", test_ex_errcode ,0 },
{ "sqlite3_errmsg", test_errmsg ,0 },
{ "sqlite3_error_offset", test_error_offset ,0 },
{ "sqlite3_errmsg16", test_errmsg16 ,0 },
{ "sqlite3_open", test_open ,0 },
{ "sqlite3_open16", test_open16 ,0 },
+10 -19
View File
@@ -117,7 +117,11 @@ static SQLITE_NOINLINE void sqlite3ErrorFinish(sqlite3 *db, int err_code){
void sqlite3Error(sqlite3 *db, int err_code){
assert( db!=0 );
db->errCode = err_code;
if( err_code || db->pErr ) sqlite3ErrorFinish(db, err_code);
if( err_code || db->pErr ){
sqlite3ErrorFinish(db, err_code);
}else{
db->errByteOffset = -1;
}
}
/*
@@ -127,6 +131,7 @@ void sqlite3Error(sqlite3 *db, int err_code){
void sqlite3ErrorClear(sqlite3 *db){
assert( db!=0 );
db->errCode = SQLITE_OK;
db->errByteOffset = -1;
if( db->pErr ) sqlite3ValueSetNull(db->pErr);
}
@@ -147,17 +152,8 @@ void sqlite3SystemError(sqlite3 *db, int rc){
** handle "db". The error code is set to "err_code".
**
** If it is not NULL, string zFormat specifies the format of the
** error string in the style of the printf functions: The following
** format characters are allowed:
**
** %s Insert a string
** %z A string that should be freed after use
** %d Insert an integer
** %T Insert a token
** %S Insert the first element of a SrcList
**
** zFormat and any string tokens that follow it are assumed to be
** encoded in UTF-8.
** error string. zFormat and any string tokens that follow it are
** assumed to be encoded in UTF-8.
**
** To clear the most recent error for sqlite handle "db", sqlite3Error
** should be called with err_code set to SQLITE_OK and zFormat set
@@ -181,13 +177,6 @@ void sqlite3ErrorWithMsg(sqlite3 *db, int err_code, const char *zFormat, ...){
/*
** Add an error message to pParse->zErrMsg and increment pParse->nErr.
** The following formatting characters are allowed:
**
** %s Insert a string
** %z A string that should be freed after use
** %d Insert an integer
** %T Insert a token
** %S Insert the first element of a SrcList
**
** This function should be used to report any error that occurs while
** compiling an SQL statement (i.e. within sqlite3_prepare()). The
@@ -200,9 +189,11 @@ void sqlite3ErrorMsg(Parse *pParse, const char *zFormat, ...){
char *zMsg;
va_list ap;
sqlite3 *db = pParse->db;
db->errByteOffset = -2;
va_start(ap, zFormat);
zMsg = sqlite3VMPrintf(db, zFormat, ap);
va_end(ap);
if( db->errByteOffset<-1 ) db->errByteOffset = -1;
if( db->suppressErr ){
sqlite3DbFree(db, zMsg);
}else{