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:
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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{
|
||||
|
||||
Reference in New Issue
Block a user