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: ** ** ** ** ^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);