Index: ext/fts3/fts3.c
==================================================================
--- ext/fts3/fts3.c
+++ ext/fts3/fts3.c
@@ -4484,11 +4484,11 @@
){
char *p = *ppIter;
assert( nDoclist>0 );
assert( *pbEof==0 );
- assert( p || *piDocid==0 );
+ assert_fts3_nc( p || *piDocid==0 );
assert( !p || (p>aDoclist && p<&aDoclist[nDoclist]) );
if( p==0 ){
sqlite3_int64 iDocid = 0;
char *pNext = 0;
Index: src/loadext.c
==================================================================
--- src/loadext.c
+++ src/loadext.c
@@ -483,10 +483,12 @@
/* Version 3.36.1 and later */
sqlite3_changes64,
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
*/
#if SQLITE_OS_WIN
Index: src/main.c
==================================================================
--- src/main.c
+++ src/main.c
@@ -2594,10 +2594,23 @@
}
}
sqlite3_mutex_leave(db->mutex);
return z;
}
+
+/*
+** Return the byte offset of the most recent error
+*/
+int sqlite3_error_offset(sqlite3 *db){
+ int iOffset = -1;
+ if( db && sqlite3SafetyCheckSickOrOk(db) && db->errCode ){
+ 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
** error.
Index: src/printf.c
==================================================================
--- src/printf.c
+++ src/printf.c
@@ -853,10 +853,11 @@
if( (pAccum->printfFlags & SQLITE_PRINTF_INTERNAL)==0 ) return;
pToken = va_arg(ap, Token*);
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;
}
case etSRCITEM: {
@@ -907,10 +908,34 @@
zExtra = 0;
}
}/* 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.
**
** Return the number of bytes of text that StrAccum is able to accept
Index: src/shell.c.in
==================================================================
--- src/shell.c.in
+++ src/shell.c.in
@@ -2517,10 +2517,51 @@
}
if( cQuote ) z[n++] = cQuote;
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
** the result columns, comma-separated, on a line and then add a
** semicolon terminator to the end of that line.
@@ -2539,12 +2580,14 @@
int nResult;
int i;
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;
}
rc = sqlite3_step(pSelect);
nResult = sqlite3_column_count(pSelect);
@@ -2576,16 +2619,20 @@
** Allocate space and save off string indicating current error.
*/
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;
}
#ifdef __linux__
/*
@@ -3516,11 +3563,11 @@
while( zSql[0] && (SQLITE_OK == rc) ){
static const char *zStmtSql;
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 ){
/* this happens for a comment or white-space */
zSql = zLeftover;
@@ -3632,11 +3679,11 @@
if( rc!=SQLITE_NOMEM ) rc = rc2;
if( rc==SQLITE_OK ){
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 */
if( pArg ){
pArg->pStmt = NULL;
@@ -4098,11 +4145,12 @@
" --quiet|-q No output except at interrupts",
" --reset Reset the count for each input and interrupt",
#endif
".prompt MAIN CONTINUE Replace the standard prompts",
".quit Exit this program",
- ".read FILE Read input from FILE",
+ ".read FILE Read input from FILE or command output",
+ " If FILE begins with \"|\", it is a command that generates the input.",
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
".recover Recover as much data as possible from corrupt db.",
" --freelist-corrupt Assume the freelist is corrupt",
" --recovery-db NAME Store recovery metadata in database file NAME",
" --lost-and-found TABLE Alternative name for the lost-and-found table",
Index: src/sqlite.h.in
==================================================================
--- src/sqlite.h.in
+++ src/sqlite.h.in
@@ -3822,17 +3822,18 @@
**
** The values returned by sqlite3_errcode() and/or
** 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:
**
**
** - sqlite3_errcode()
**
- sqlite3_extended_errcode()
**
- sqlite3_errmsg()
**
- sqlite3_errmsg16()
+**
- sqlite3_error_offset()
**
**
** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language
** text that describes the error, as either UTF-8 or UTF-16 respectively.
** ^(Memory to hold the error message string is managed internally.
@@ -3842,10 +3843,17 @@
**
** ^The sqlite3_errstr() interface returns the English-language text
** that describes the [result code], as UTF-8.
** ^(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.
** When that happens, the second error will be reported since these
@@ -3862,10 +3870,11 @@
int sqlite3_errcode(sqlite3 *db);
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
** KEYWORDS: {prepared statement} {prepared statements}
**
Index: src/sqlite3ext.h
==================================================================
--- src/sqlite3ext.h
+++ src/sqlite3ext.h
@@ -342,10 +342,12 @@
sqlite3_int64 (*total_changes64)(sqlite3*);
/* Version 3.37.0 and later */
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*);
};
/*
** This is the function signature used for all extension entry points. It
** is also defined in the file "loadext.c".
@@ -653,10 +655,12 @@
/* Version 3.36.1 and later */
#define sqlite3_changes64 sqlite3_api->changes64
#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)
/* This case when the file really is being compiled as a loadable
** extension */
Index: src/sqliteInt.h
==================================================================
--- src/sqliteInt.h
+++ src/sqliteInt.h
@@ -1525,10 +1525,11 @@
i64 lastRowid; /* ROWID of most recent insert (see above) */
i64 szMmap; /* Default mmap_size setting */
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 */
u8 enc; /* Text encoding */
u8 autoCommit; /* The auto-commit flag. */
@@ -4975,10 +4976,11 @@
char *sqlite3StrAccumFinish(StrAccum*);
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 *);
#ifndef SQLITE_OMIT_SUBQUERY
Index: src/test1.c
==================================================================
--- src/test1.c
+++ src/test1.c
@@ -4364,10 +4364,38 @@
zErr = sqlite3_errmsg(db);
Tcl_SetObjResult(interp, Tcl_NewStringObj(zErr, -1));
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(iByteOffset));
+ return TCL_OK;
+}
+
/*
** Usage: test_errmsg16 DB
**
** Returns the UTF-16 representation of the error message string for the
** most recent sqlite3_* API call. This is a byte array object at the TCL
@@ -8420,10 +8448,11 @@
{ "sqlite3_clear_bindings", test_clear_bindings, 0},
{ "sqlite3_sleep", test_sleep, 0},
{ "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 },
{ "sqlite3_open_v2", test_open_v2 ,0 },
{ "sqlite3_complete16", test_complete16 ,0 },
Index: src/util.c
==================================================================
--- src/util.c
+++ src/util.c
@@ -115,20 +115,25 @@
** that would be appropriate.
*/
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;
+ }
}
/*
** The equivalent of sqlite3Error(db, SQLITE_OK). Clear the error state
** and error message.
*/
void sqlite3ErrorClear(sqlite3 *db){
assert( db!=0 );
db->errCode = SQLITE_OK;
+ db->errByteOffset = -1;
if( db->pErr ) sqlite3ValueSetNull(db->pErr);
}
/*
** Load the sqlite3.iSysErrno field if that is an appropriate thing
@@ -145,21 +150,12 @@
/*
** Set the most recent error code and error string for the sqlite
** 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
** to NULL.
*/
@@ -179,17 +175,10 @@
}
}
/*
** 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
** last thing the sqlite3_prepare() function does is copy the error
** stored by this function into the database handle using sqlite3Error().
@@ -198,13 +187,15 @@
*/
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{
pParse->nErr++;
sqlite3DbFree(db, pParse->zErrMsg);