diff --git a/ext/consio/console_io.c b/ext/consio/console_io.c index 35016b0c4a..5d38feef37 100755 --- a/ext/consio/console_io.c +++ b/ext/consio/console_io.c @@ -9,8 +9,9 @@ ** May you share freely, never taking more than you give. ** ******************************************************************************** -** This file implements various interfaces used for console I/O by the -** SQLite project command-line tools, as explained in console_io.h . +** This file implements various interfaces used for console and stream I/O +** by the SQLite project command-line tools, as explained in console_io.h . +** Functions prefixed by "SQLITE_INTERNAL_LINKAGE" behave as described there. */ #ifndef SQLITE_CDECL @@ -22,11 +23,12 @@ # include # include # include +# include # include "console_io.h" # include "sqlite3.h" #endif -#if defined(_WIN32) || defined(WIN32) +#if (defined(_WIN32) || defined(WIN32)) && !SQLITE_OS_WINRT # ifndef SHELL_NO_SYSINC # include # include @@ -36,7 +38,6 @@ # endif # ifdef SHELL_LEGACY_CONSOLE_IO # define SHELL_CON_TRANSLATE 2 /* Use UTF-8/MBCS translation for console I/O */ -extern char *sqlite3_win32_utf8_to_mbcs_v2(const char *, int); # else # define SHELL_CON_TRANSLATE 1 /* Use WCHAR Windows APIs for console I/O */ # endif @@ -51,7 +52,7 @@ extern char *sqlite3_win32_utf8_to_mbcs_v2(const char *, int); static HANDLE handleOfFile(FILE *pf){ int fileDesc = _fileno(pf); union { intptr_t osfh; HANDLE fh; } fid = { - (fileDesc!=-2)? _get_osfhandle(fileDesc) : (intptr_t)INVALID_HANDLE_VALUE + (fileDesc>=0)? _get_osfhandle(fileDesc) : (intptr_t)INVALID_HANDLE_VALUE }; return fid.fh; } @@ -59,35 +60,66 @@ static HANDLE handleOfFile(FILE *pf){ typedef struct PerStreamTags { #if SHELL_CON_TRANSLATE - DWORD consMode; HANDLE hx; + DWORD consMode; +#else + short reachesConsole; #endif FILE *pf; } PerStreamTags; -static short fileOfConsole(FILE *pf, PerStreamTags *ppst){ +/* Define NULL-like value for things which can validly be 0. */ +#define SHELL_INVALID_FILE_PTR ((FILE *)~0) #if SHELL_CON_TRANSLATE - short rv = 0; - DWORD dwj; - HANDLE fh = handleOfFile(pf); - if( INVALID_HANDLE_VALUE != fh ){ - rv = (GetFileType(fh) == FILE_TYPE_CHAR && GetConsoleMode(fh,&dwj)); - if( rv ){ - ppst->hx = fh; - ppst->pf = pf; - GetConsoleMode(fh, &ppst->consMode); - } - } - return rv; +# define SHELL_INVALID_CONS_MODE 0xFFFF0000 +#endif + +#if SHELL_CON_TRANSLATE +# define CI_INITIALIZER \ + { INVALID_HANDLE_VALUE, SHELL_INVALID_CONS_MODE, SHELL_INVALID_FILE_PTR } #else - return (short)isatty(fileno(pf)); +# define CI_INITIALIZER { 0, SHELL_INVALID_FILE_PTR } +#endif + +/* Quickly say whether a known output is going to the console. */ +static short pstReachesConsole(PerStreamTags *ppst){ +#if SHELL_CON_TRANSLATE + return (ppst->hx != INVALID_HANDLE_VALUE); +#else + return (ppst->reachesConsole != 0); #endif } -#define SHELL_INVALID_FILE_PTR ((FILE *)sizeof(FILE*)) -#define SHELL_INVALID_CONS_MODE 0xFFFF0000 +#if SHELL_CON_TRANSLATE +static void restoreConsoleArb(PerStreamTags *ppst){ + if( pstReachesConsole(ppst) ) SetConsoleMode(ppst->hx, ppst->consMode); +} +#else +# define restoreConsoleArb(ppst) +#endif + +/* Say whether FILE* appears to be a console, collect associated info. */ +static short streamOfConsole(FILE *pf, /* out */ PerStreamTags *ppst){ +#if SHELL_CON_TRANSLATE + short rv = 0; + DWORD dwCM = SHELL_INVALID_CONS_MODE; + HANDLE fh = handleOfFile(pf); + ppst->pf = pf; + if( INVALID_HANDLE_VALUE != fh ){ + rv = (GetFileType(fh) == FILE_TYPE_CHAR && GetConsoleMode(fh,&dwCM)); + } + ppst->hx = (rv)? fh : INVALID_HANDLE_VALUE; + ppst->consMode = dwCM; + return rv; +#else + ppst->pf = pf; + ppst->reachesConsole = ( (short)isatty(fileno(pf)) ); + return ppst->reachesConsole; +#endif +} #if SHELL_CON_TRANSLATE +/* Define console modes for use with the Windows Console API. */ # define SHELL_CONI_MODE \ (ENABLE_ECHO_INPUT | ENABLE_INSERT_MODE | ENABLE_LINE_INPUT | 0x80 \ | ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS | ENABLE_PROCESSED_INPUT) @@ -96,109 +128,159 @@ static short fileOfConsole(FILE *pf, PerStreamTags *ppst){ #endif typedef struct ConsoleInfo { - /* int iInitialFmode[3]; - ** Above only needed for legacy console I/O for callable CLI. - ** Because that state cannot be obtained from each FILE *, - ** there will be no exact restoration of console state for - ** the CLI when built with SHELL_LEGACY_CONSOLE_IO defined. - */ - PerStreamTags pst[3]; -#if SHELL_CON_TRANSLATE - unsigned char haveInput; - unsigned char outputIx; - unsigned char stdinEof; -#endif - ConsoleStdConsStreams cscs; + PerStreamTags pstSetup[3]; + PerStreamTags pstDesignated[3]; + StreamsAreConsole sacSetup; + StreamsAreConsole sacDesignated; } ConsoleInfo; -#if SHELL_CON_TRANSLATE -# define CI_INITIALIZER \ - {SHELL_INVALID_CONS_MODE, INVALID_HANDLE_VALUE, SHELL_INVALID_FILE_PTR } -#else -# define CI_INITIALIZER { SHELL_INVALID_FILE_PTR } -#endif +static short isValidStreamInfo(PerStreamTags *ppst){ + return (ppst->pf != SHELL_INVALID_FILE_PTR); +} static ConsoleInfo consoleInfo = { - { /* pst */ CI_INITIALIZER, CI_INITIALIZER, CI_INITIALIZER }, -#if SHELL_CON_TRANSLATE - 0, 0, 1, /* haveInput, outputIx, stdinEof */ -#endif - CSCS_NoConsole + { /* pstSetup */ CI_INITIALIZER, CI_INITIALIZER, CI_INITIALIZER }, + { /* pstDesignated[] */ CI_INITIALIZER, CI_INITIALIZER, CI_INITIALIZER }, + SAC_NoConsole, SAC_NoConsole /* sacSetup, sacDesignated */ }; +#undef SHELL_INVALID_FILE_PTR #undef CI_INITIALIZER -SQLITE_INTERNAL_LINKAGE ConsoleStdConsStreams +SQLITE_INTERNAL_LINKAGE FILE* invalidFileStream = (FILE *)~0; + +static void maybeSetupAsConsole(PerStreamTags *ppst, short odir){ +#if SHELL_CON_TRANSLATE + if( pstReachesConsole(ppst) ){ + DWORD cm = odir? SHELL_CONO_MODE : SHELL_CONI_MODE; + SetConsoleMode(ppst->hx, cm); +# if SHELL_CON_TRANSLATE == 2 + _setmode(_fileno(ppst->pf), _O_TEXT); +# endif + } +#else + (void)ppst; + (void)odir; +#endif +} + +SQLITE_INTERNAL_LINKAGE void consoleRenewSetup(void){ +#if SHELL_CON_TRANSLATE + int ix = 0; + while( ix < 6 ){ + PerStreamTags *ppst = (ix<3)? + &consoleInfo.pstSetup[ix] : &consoleInfo.pstDesignated[ix-3]; + maybeSetupAsConsole(ppst, (ix % 3)>0); + ++ix; + } +#endif +} + +SQLITE_INTERNAL_LINKAGE StreamsAreConsole consoleClassifySetup( FILE *pfIn, FILE *pfOut, FILE *pfErr ){ - ConsoleStdConsStreams rv = CSCS_NoConsole; - FILE *apf[3] = { pfIn, pfOut, pfErr }; + StreamsAreConsole rv = SAC_NoConsole; + FILE* apf[3] = { pfIn, pfOut, pfErr }; int ix; for( ix = 2; ix >= 0; --ix ){ - PerStreamTags *ppst = &consoleInfo.pst[ix]; - if( fileOfConsole(apf[ix], ppst) ){ -#if SHELL_CON_TRANSLATE - DWORD cm = (ix==0)? SHELL_CONI_MODE : SHELL_CONO_MODE; - if( ix==0 ){ - consoleInfo.haveInput = 1; - consoleInfo.stdinEof = 0; - }else{ - consoleInfo.outputIx |= ix; - } - SetConsoleMode(ppst->hx, cm); -#endif - rv |= (CSCS_InConsole< 0 ) fflush(apf[ix]); #if SHELL_CON_TRANSLATE == 2 _setmode(_fileno(apf[ix]), _O_TEXT); #endif } - consoleInfo.cscs = rv; + consoleInfo.sacSetup = rv; + consoleRenewSetup(); return rv; } SQLITE_INTERNAL_LINKAGE void SQLITE_CDECL consoleRestore( void ){ - if( consoleInfo.cscs ){ +#if SHELL_CON_TRANSLATE + static ConsoleInfo *pci = &consoleInfo; + if( pci->sacSetup ){ int ix; for( ix=0; ix<3; ++ix ){ - if( consoleInfo.cscs & (CSCS_InConsole<sacSetup & (SAC_InConsole<pstSetup[ix]; +# if SHELL_CON_TRANSLATE == 2 static int tmode = _O_TEXT; - /* Consider: Read these modes in consoleClassifySetup somehow. + /* Consider: Read this mode in consoleClassifySetup somehow. ** A _get_fmode() call almost works. But not with gcc, yet. ** This has to be done to make the CLI a callable function ** when legacy console I/O is done. (This may never happen.) */ - _setmode(_fileno(consoleInfo.pst[ix].pf), tmode); -#endif -#if SHELL_CON_TRANSLATE + _setmode(_fileno(pci->pstSetup[ix].pf), tmode); +# endif SetConsoleMode(ppst->hx, ppst->consMode); - ppst->hx = INVALID_HANDLE_VALUE; -#endif - ppst->pf = SHELL_INVALID_FILE_PTR; } - consoleInfo.cscs = CSCS_NoConsole; -#if SHELL_CON_TRANSLATE - consoleInfo.stdinEof = consoleInfo.haveInput = consoleInfo.outputIx= 0; -#endif } } +#endif } -static short isConOut(FILE *pf){ - if( pf==consoleInfo.pst[1].pf ) return 1; - else if( pf==consoleInfo.pst[2].pf ) return 2; - else return 0; +/* Say whether given FILE* is among those known, via either +** consoleClassifySetup() or set{Output,Error}Stream, as +** readable, and return an associated PerStreamTags pointer +** if so. Otherwise, return 0. +*/ +static PerStreamTags * isKnownReadable(FILE *pf){ + static PerStreamTags *apst[] = { + &consoleInfo.pstDesignated[0], &consoleInfo.pstSetup[0], 0 + }; + int ix = 0; + do { + if( apst[ix]->pf == pf ) break; + } while( apst[++ix] != 0 ); + return apst[ix]; +} + +/* Say whether given FILE* is among those known, via either +** consoleClassifySetup() or set{Output,Error}Stream, as +** writable, and return an associated PerStreamTags pointer +** if so. Otherwise, return 0. +*/ +static PerStreamTags * isKnownWritable(FILE *pf){ + static PerStreamTags *apst[] = { + &consoleInfo.pstDesignated[1], &consoleInfo.pstDesignated[2], + &consoleInfo.pstSetup[1], &consoleInfo.pstSetup[2], 0 + }; + int ix = 0; + do { + if( apst[ix]->pf == pf ) break; + } while( apst[++ix] != 0 ); + return apst[ix]; +} + +static FILE *designateEmitStream(FILE *pf, unsigned chix){ + FILE *rv = consoleInfo.pstDesignated[chix].pf; + if( pf == invalidFileStream ) return rv; + else{ + /* Setting a possibly new output stream. */ + PerStreamTags *ppst = isKnownWritable(pf); + if( ppst != 0 ){ + PerStreamTags pst = *ppst; + consoleInfo.pstDesignated[chix] = pst; + }else streamOfConsole(pf, &consoleInfo.pstDesignated[chix]); + } + return rv; +} + +SQLITE_INTERNAL_LINKAGE FILE *setOutputStream(FILE *pf){ + return designateEmitStream(pf, 1); +} +SQLITE_INTERNAL_LINKAGE FILE *setErrorStream(FILE *pf){ + return designateEmitStream(pf, 2); } #if SHELL_CON_TRANSLATE static void setModeFlushQ(FILE *pf, short bFlush, int mode){ - short ico = isConOut(pf); - if( ico>1 || bFlush ) fflush(pf); + if( bFlush ) fflush(pf); _setmode(_fileno(pf), mode); } #else -# define setModeFlushQ(f, b, m) if(isConOut(f)>0||b) fflush(f) +# define setModeFlushQ(f, b, m) if(b) fflush(f) #endif SQLITE_INTERNAL_LINKAGE void setBinaryMode(FILE *pf, short bFlush){ @@ -210,15 +292,15 @@ SQLITE_INTERNAL_LINKAGE void setTextMode(FILE *pf, short bFlush){ #undef setModeFlushQ #if SHELL_CON_TRANSLATE -/* Write plain 0-terminated output to stream known as console. */ -static int conioZstrOut(int rch, const char *z){ +/* Write plain 0-terminated output to stream known as reaching console. */ +static int conioZstrOut(PerStreamTags *ppst, const char *z){ int rv = 0; if( z!=NULL && *z!=0 ){ int nc; int nwc; # if SHELL_CON_TRANSLATE == 2 UINT cocp = GetConsoleOutputCP(); - FILE *pfO = consoleInfo.pst[rch].pf; + FILE *pfO = ppst->pf; if( cocp == CP_UTF8 ){ /* This is not legacy action. But it can work better, ** when the console putatively can handle UTF-8. */ @@ -246,7 +328,7 @@ static int conioZstrOut(int rch, const char *z){ } # elif SHELL_CON_TRANSLATE == 1 /* Translation from UTF-8 to UTF-16, then WCHARs out. */ - if( WriteConsoleW(consoleInfo.pst[rch].hx, zw,nwc, 0, NULL) ){ + if( WriteConsoleW(ppst->hx, zw,nwc, 0, NULL) ){ rv = nc; } # endif @@ -258,42 +340,115 @@ static int conioZstrOut(int rch, const char *z){ return rv; } -/* For fprintfUtf8() and printfUtf8() when stream is known as console. */ -static int conioVmPrintf(int rch, const char *zFormat, va_list ap){ +/* For {f,o,e}PrintfUtf8() when stream is known to reach console. */ +static int conioVmPrintf(PerStreamTags *ppst, const char *zFormat, va_list ap){ char *z = sqlite3_vmprintf(zFormat, ap); - int rv = conioZstrOut(rch, z); + int rv = conioZstrOut(ppst, z); sqlite3_free(z); return rv; } -#endif +#endif /* SHELL_CON_TRANSLATE */ -SQLITE_INTERNAL_LINKAGE int printfUtf8(const char *zFormat, ...){ + +static PerStreamTags * getDesignatedEmitStream(FILE *pf, unsigned chix, + PerStreamTags *ppst){ + PerStreamTags *rv = isKnownWritable(pf); + short isValid = (rv!=0)? isValidStreamInfo(rv) : 0; + if( rv != 0 && isValid ) return rv; + streamOfConsole(pf, ppst); + return ppst; +} + +/* Get stream info, either for designated output or error stream when +** chix equals 1 or 2, or for an arbitrary stream when chix == 0. +** In either case, ppst references a caller-owned PerStreamTags +** struct which may be filled in if none of the known writable +** streams is being held by consoleInfo. The ppf parameter is an +** output when chix!=0 and an input when chix==0. + */ +static PerStreamTags * +getEmitStreamInfo(unsigned chix, PerStreamTags *ppst, + /* in/out */ FILE **ppf){ + PerStreamTags *ppstTry; + FILE *pfEmit; + if( chix > 0 ){ + ppstTry = &consoleInfo.pstDesignated[chix]; + if( !isValidStreamInfo(ppstTry) ){ + ppstTry = &consoleInfo.pstSetup[chix]; + pfEmit = ppst->pf; + }else pfEmit = ppstTry->pf; + if( !isValidStreamInfo(ppst) ){ + pfEmit = (chix > 1)? stderr : stdout; + ppstTry = ppst; + streamOfConsole(pfEmit, ppstTry); + } + *ppf = pfEmit; + }else{ + ppstTry = isKnownWritable(*ppf); + if( ppstTry != 0 ) return ppstTry; + streamOfConsole(*ppf, ppst); + return ppst; + } + return ppstTry; +} + +SQLITE_INTERNAL_LINKAGE int oPrintfUtf8(const char *zFormat, ...){ va_list ap; int rv; + FILE *pfOut; + PerStreamTags pst; /* Needed only for heretofore unknown streams. */ + PerStreamTags *ppst = getEmitStreamInfo(1, &pst, &pfOut); + va_start(ap, zFormat); #if SHELL_CON_TRANSLATE - if( SHELL_INVALID_FILE_PTR != consoleInfo.pst[1].pf ){ - rv = conioVmPrintf(1, zFormat, ap); + if( pstReachesConsole(ppst) ){ + rv = conioVmPrintf(ppst, zFormat, ap); }else{ #endif - rv = vfprintf(stdout, zFormat, ap); + rv = vfprintf(pfOut, zFormat, ap); #if SHELL_CON_TRANSLATE } #endif va_end(ap); return rv; } -#undef SHELL_INVALID_FILE_PTR -SQLITE_INTERNAL_LINKAGE int fprintfUtf8(FILE *pfO, const char *zFormat, ...){ +SQLITE_INTERNAL_LINKAGE int ePrintfUtf8(const char *zFormat, ...){ va_list ap; int rv; + FILE *pfErr; + PerStreamTags pst; /* Needed only for heretofore unknown streams. */ + PerStreamTags *ppst = getEmitStreamInfo(2, &pst, &pfErr); + va_start(ap, zFormat); #if SHELL_CON_TRANSLATE - short rch = isConOut(pfO); - if( rch > 0 ){ - rv = conioVmPrintf(rch, zFormat, ap); - }else { + if( pstReachesConsole(ppst) ){ + rv = conioVmPrintf(ppst, zFormat, ap); + }else{ +#endif + rv = vfprintf(pfErr, zFormat, ap); +#if SHELL_CON_TRANSLATE + } +#endif + va_end(ap); + return rv; +} + +SQLITE_INTERNAL_LINKAGE int fPrintfUtf8(FILE *pfO, const char *zFormat, ...){ + va_list ap; + int rv; +#if SHELL_CON_TRANSLATE + PerStreamTags pst; /* Needed only for heretofore unknown streams. */ + PerStreamTags *ppst = getEmitStreamInfo(0, &pst, &pfO); +#endif + + va_start(ap, zFormat); +#if SHELL_CON_TRANSLATE + if( pstReachesConsole(ppst) ){ + maybeSetupAsConsole(ppst, 1); + rv = conioVmPrintf(ppst, zFormat, ap); + if( 0 == isKnownWritable(ppst->pf) ) restoreConsoleArb(ppst); + }else{ #endif rv = vfprintf(pfO, zFormat, ap); #if SHELL_CON_TRANSLATE @@ -303,11 +458,16 @@ SQLITE_INTERNAL_LINKAGE int fprintfUtf8(FILE *pfO, const char *zFormat, ...){ return rv; } -SQLITE_INTERNAL_LINKAGE int fputsUtf8(const char *z, FILE *pfO){ +SQLITE_INTERNAL_LINKAGE int fPutsUtf8(const char *z, FILE *pfO){ #if SHELL_CON_TRANSLATE - short rch = isConOut(pfO); - if( rch > 0 ){ - return conioZstrOut(rch, z); + PerStreamTags pst; /* Needed only for heretofore unknown streams. */ + PerStreamTags *ppst = getEmitStreamInfo(0, &pst, &pfO); + if( pstReachesConsole(ppst) ){ + int rv; + maybeSetupAsConsole(ppst, 1); + rv = conioZstrOut(ppst, z); + if( 0 == isKnownWritable(ppst->pf) ) restoreConsoleArb(ppst); + return rv; }else { #endif return (fputs(z, pfO)<0)? 0 : (int)strlen(z); @@ -316,6 +476,34 @@ SQLITE_INTERNAL_LINKAGE int fputsUtf8(const char *z, FILE *pfO){ #endif } +SQLITE_INTERNAL_LINKAGE int ePutsUtf8(const char *z){ + FILE *pfErr; + PerStreamTags pst; /* Needed only for heretofore unknown streams. */ + PerStreamTags *ppst = getEmitStreamInfo(2, &pst, &pfErr); +#if SHELL_CON_TRANSLATE + if( pstReachesConsole(ppst) ) return conioZstrOut(ppst, z); + else { +#endif + return (fputs(z, pfErr)<0)? 0 : (int)strlen(z); +#if SHELL_CON_TRANSLATE + } +#endif +} + +SQLITE_INTERNAL_LINKAGE int oPutsUtf8(const char *z){ + FILE *pfOut; + PerStreamTags pst; /* Needed only for heretofore unknown streams. */ + PerStreamTags *ppst = getEmitStreamInfo(1, &pst, &pfOut); +#if SHELL_CON_TRANSLATE + if( pstReachesConsole(ppst) ) return conioZstrOut(ppst, z); + else { +#endif + return (fputs(z, pfOut)<0)? 0 : (int)strlen(z); +#if SHELL_CON_TRANSLATE + } +#endif +} + #if SHELL_CON_TRANSLATE==2 static int mbcsToUtf8InPlaceIfValid(char *pc, int nci, int nco, UINT codePage){ WCHAR wcOneCode[2]; @@ -329,26 +517,25 @@ static int mbcsToUtf8InPlaceIfValid(char *pc, int nci, int nco, UINT codePage){ } #endif -SQLITE_INTERNAL_LINKAGE char* fgetsUtf8(char *cBuf, int ncMax, FILE *pfIn){ +SQLITE_INTERNAL_LINKAGE char* fGetsUtf8(char *cBuf, int ncMax, FILE *pfIn){ if( pfIn==0 ) pfIn = stdin; #if SHELL_CON_TRANSLATE - if( pfIn == consoleInfo.pst[0].pf ){ + if( pfIn == consoleInfo.pstSetup[0].pf ){ # if SHELL_CON_TRANSLATE==1 # define SHELL_GULP 150 /* Count of WCHARS to be gulped at a time */ WCHAR wcBuf[SHELL_GULP+1]; int lend = 0, noc = 0; - if( consoleInfo.stdinEof ) return 0; if( ncMax > 0 ) cBuf[0] = 0; while( noc < ncMax-8-1 && !lend ){ /* There is room for at least 2 more characters and a 0-terminator. */ int na = (ncMax > SHELL_GULP*4+1 + noc)? SHELL_GULP : (ncMax-1 - noc)/4; # undef SHELL_GULP DWORD nbr = 0; - BOOL bRC = ReadConsoleW(consoleInfo.pst[0].hx, wcBuf, na, &nbr, 0); + BOOL bRC = ReadConsoleW(consoleInfo.pstSetup[0].hx, wcBuf, na, &nbr, 0); if( bRC && nbr>0 && (wcBuf[nbr-1]&0xF800)==0xD800 ){ /* Last WHAR read is first of a UTF-16 surrogate pair. Grab its mate. */ DWORD nbrx; - bRC &= ReadConsoleW(consoleInfo.pst[0].hx, wcBuf+nbr, 1, &nbrx, 0); + bRC &= ReadConsoleW(consoleInfo.pstSetup[0].hx, wcBuf+nbr, 1, &nbrx, 0); if( bRC ) nbr += nbrx; } if( !bRC || (noc==0 && nbr==0) ) return 0; @@ -359,23 +546,20 @@ SQLITE_INTERNAL_LINKAGE char* fgetsUtf8(char *cBuf, int ncMax, FILE *pfIn){ nmb = WideCharToMultiByte(CP_UTF8, 0, wcBuf,nbr,cBuf+noc,nmb,0,0); noc += nmb; /* Fixup line-ends as coded by Windows for CR (or "Enter".) - ** Note that this is done without regard for any setModeText() + ** This is done without regard for any setMode{Text,Binary}() ** call that might have been done on the interactive input. */ if( noc > 0 ){ if( cBuf[noc-1]=='\n' ){ lend = 1; - if( noc > 1 && cBuf[noc-2]=='\r' ){ - cBuf[noc-2] = '\n'; - --noc; - } + if( noc > 1 && cBuf[noc-2]=='\r' ) cBuf[--noc-1] = '\n'; } } /* Check for ^Z (anywhere in line) too, to act as EOF. */ while( iseg < noc ){ - if( cBuf[iseg]==0x1a ){ - consoleInfo.stdinEof = 1; + if( cBuf[iseg]=='\x1a' ){ noc = iseg; /* Chop ^Z and anything following. */ + lend = 1; /* Counts as end of line too. */ break; } ++iseg; @@ -384,9 +568,10 @@ SQLITE_INTERNAL_LINKAGE char* fgetsUtf8(char *cBuf, int ncMax, FILE *pfIn){ }else break; } /* If got nothing, (after ^Z chop), must be at end-of-file. */ - if( noc == 0 ) return 0; - cBuf[noc] = 0; - return cBuf; + if( noc > 0 ){ + cBuf[noc] = 0; + return cBuf; + }else return 0; # elif SHELL_CON_TRANSLATE==2 /* This is not done efficiently because it may never be used. ** Also, it is interactive input so it need not be fast. */ @@ -432,7 +617,10 @@ SQLITE_INTERNAL_LINKAGE char* fgetsUtf8(char *cBuf, int ncMax, FILE *pfIn){ /* Treat ct as bona fide MBCS trailing byte, if valid. */ cBuf[nco+ntb] = ct; nug = mbcsToUtf8InPlaceIfValid(cBuf+nco, 1+ntb, ncMax-nco-1, cicp); - nco += nug; + if( nug > 0 ){ + nco += nug; + break; + } } if( ct < 0 ) break; } diff --git a/ext/consio/console_io.h b/ext/consio/console_io.h index e799c71e74..2d6178fed3 100644 --- a/ext/consio/console_io.h +++ b/ext/consio/console_io.h @@ -9,16 +9,16 @@ ** May you share freely, never taking more than you give. ** ******************************************************************************** -** This file exposes various interfaces used for console I/O by the -** SQLite project command-line tools. These interfaces are used at -** either source conglomeration time, compilation time, or run time. +** This file exposes various interfaces used for console and other I/O +** by the SQLite project command-line tools. These interfaces are used +** at either source conglomeration time, compilation time, or run time. ** This source provides for either inclusion into conglomerated, -** "single-source" forms or separate compilation then linking. (TBD) +** "single-source" forms or separate compilation then linking. ** ** Platform dependencies are "hidden" here by various stratagems so -** that, provided certain conditions are met, the programs using -** this source or object code compiled from it need no explicit -** conditional compilation in their source for their console I/O. +** that, provided certain conditions are met, the programs using this +** source or object code compiled from it need no explicit conditional +** compilation in their source for their console and stream I/O. ** ** The symbols and functionality exposed here are not a public API. ** This code may change in tandem with other project code as needed. @@ -36,34 +36,44 @@ #endif /* Define enum for use with following function. */ -typedef enum ConsoleStdConsStreams { - CSCS_NoConsole = 0, - CSCS_InConsole = 1, CSCS_OutConsole = 2, CSCS_ErrConsole = 4, - CSCS_AnyConsole = 0x7 -} ConsoleStdConsStreams; +typedef enum StreamsAreConsole { + SAC_NoConsole = 0, + SAC_InConsole = 1, SAC_OutConsole = 2, SAC_ErrConsole = 4, + SAC_AnyConsole = 0x7 +} StreamsAreConsole; /* ** Classify the three standard I/O streams according to whether ** they are connected to a console attached to the process. ** -** Returns the bit-wise OR of CSCS_{In,Out,Err}Console values, -** or CSCS_NoConsole if none of the streams reaches a console. +** Returns the bit-wise OR of SAC_{In,Out,Err}Console values, +** or SAC_NoConsole if none of the streams reaches a console. ** ** This function should be called before any I/O is done with ** the given streams. As a side-effect, the given inputs are ** recorded so that later I/O operations on them may be done ** differently than the C library FILE* I/O would be done, -** iff the stream is used for the I/O functions that follow. +** iff the stream is used for the I/O functions that follow, +** and to support the ones that use an implicit stream. ** ** On some platforms, stream or console mode alteration (aka ** "Setup") may be made which is undone by consoleRestore(). +*/ +SQLITE_INTERNAL_LINKAGE StreamsAreConsole +consoleClassifySetup( FILE *pfIn, FILE *pfOut, FILE *pfErr ); +/* A usual call for convenience: */ +#define SQLITE_STD_CONSOLE_INIT() consoleClassifySetup(stdin,stdout,stderr) + +/* +** After an initial call to consoleClassifySetup(...), renew +** the same setup it effected. (A call not after is an error.) +** This will restore state altered by consoleRestore(); ** ** Applications which run an inferior (child) process which ** inherits the same I/O streams may call this function after ** such a process exits to guard against console mode changes. */ -SQLITE_INTERNAL_LINKAGE ConsoleStdConsStreams -consoleClassifySetup( FILE *pfIn, FILE *pfOut, FILE *pfErr ); +SQLITE_INTERNAL_LINKAGE void consoleRenewSetup(void); /* ** Undo any side-effects left by consoleClassifySetup(...). @@ -71,8 +81,8 @@ consoleClassifySetup( FILE *pfIn, FILE *pfOut, FILE *pfErr ); ** This should be called after consoleClassifySetup() and ** before the process terminates normally. It is suitable ** for use with the atexit() C library procedure. After -** this call, no I/O should be done with the console -** until consoleClassifySetup(...) is called again. +** this call, no console I/O should be done until one of +** console{Classify or Renew}Setup(...) is called again. ** ** Applications which run an inferior (child) process that ** inherits the same I/O streams might call this procedure @@ -82,24 +92,49 @@ consoleClassifySetup( FILE *pfIn, FILE *pfOut, FILE *pfErr ); SQLITE_INTERNAL_LINKAGE void SQLITE_CDECL consoleRestore( void ); /* -** Render output like fprintf(). If the output is going to the +** Set stream to be used for the functions below which write +** to "the designated X stream", where X is Output or Error. +** Returns the previous value. +** +** Alternatively, pass the special value, invalidFileStream, +** to get the designated stream value without setting it. +** +** Before the designated streams are set, they default to +** those passed to consoleClassifySetup(...), and before +** that is called they default to stdout and stderr. +** +** It is error to close a stream so designated, then, without +** designating another, use the corresponding {o,e}Emit(...). +*/ +SQLITE_INTERNAL_LINKAGE FILE *invalidFileStream; +SQLITE_INTERNAL_LINKAGE FILE *setOutputStream(FILE *pf); +SQLITE_INTERNAL_LINKAGE FILE *setErrorStream(FILE *pf); + +/* +** Emit output like fprintf(). If the output is going to the ** console and translation from UTF-8 is necessary, perform ** the needed translation. Otherwise, write formatted output ** to the provided stream almost as-is, possibly with newline ** translation as specified by set{Binary,Text}Mode(). */ -SQLITE_INTERNAL_LINKAGE int fprintfUtf8(FILE *pfO, const char *zFormat, ...); -/* Like fprintfUtf8 except stream is always the recorded output. */ -SQLITE_INTERNAL_LINKAGE int printfUtf8(const char *zFormat, ...); +SQLITE_INTERNAL_LINKAGE int fPrintfUtf8(FILE *pfO, const char *zFormat, ...); +/* Like fPrintfUtf8 except stream is always the designated output. */ +SQLITE_INTERNAL_LINKAGE int oPrintfUtf8(const char *zFormat, ...); +/* Like fPrintfUtf8 except stream is always the designated error. */ +SQLITE_INTERNAL_LINKAGE int ePrintfUtf8(const char *zFormat, ...); /* -** Render output like fputs(). If the output is going to the +** Emit output like fputs(). If the output is going to the ** console and translation from UTF-8 is necessary, perform ** the needed translation. Otherwise, write given text to the ** provided stream almost as-is, possibly with newline ** translation as specified by set{Binary,Text}Mode(). */ -SQLITE_INTERNAL_LINKAGE int fputsUtf8(const char *z, FILE *pfO); +SQLITE_INTERNAL_LINKAGE int fPutsUtf8(const char *z, FILE *pfO); +/* Like fPutsUtf8 except stream is always the designated output. */ +SQLITE_INTERNAL_LINKAGE int oPutsUtf8(const char *z); +/* Like fPutsUtf8 except stream is always the designated error. */ +SQLITE_INTERNAL_LINKAGE int ePutsUtf8(const char *z); /* ** Collect input like fgets(...) with special provisions for input @@ -108,7 +143,9 @@ SQLITE_INTERNAL_LINKAGE int fputsUtf8(const char *z, FILE *pfO); ** translation may be done as set by set{Binary,Text}Mode(). As a ** convenience, pfIn==NULL is treated as stdin. */ -SQLITE_INTERNAL_LINKAGE char* fgetsUtf8(char *cBuf, int ncMax, FILE *pfIn); +SQLITE_INTERNAL_LINKAGE char* fGetsUtf8(char *cBuf, int ncMax, FILE *pfIn); +/* Like fGetsUtf8 except stream is always the designated input. */ +SQLITE_INTERNAL_LINKAGE char* iGetsUtf8(char *cBuf, int ncMax); /* ** Set given stream for binary mode, where newline translation is @@ -126,10 +163,12 @@ SQLITE_INTERNAL_LINKAGE char* fgetsUtf8(char *cBuf, int ncMax, FILE *pfIn); SQLITE_INTERNAL_LINKAGE void setBinaryMode(FILE *, short bFlush); SQLITE_INTERNAL_LINKAGE void setTextMode(FILE *, short bFlush); +#if 0 /* For use with line editor. (not yet used) */ typedef struct Prompts { int numPrompts; const char **azPrompts; } Prompts; +#endif /* ** Macros for use of a line editor. diff --git a/ext/fts5/fts5_hash.c b/ext/fts5/fts5_hash.c index 7e50c36608..391791c7ac 100644 --- a/ext/fts5/fts5_hash.c +++ b/ext/fts5/fts5_hash.c @@ -432,10 +432,8 @@ static Fts5HashEntry *fts5HashEntryMerge( } /* -** Extract all tokens from hash table iHash and link them into a list -** in sorted order. The hash table is cleared before returning. It is -** the responsibility of the caller to free the elements of the returned -** list. +** Link all tokens from hash table iHash into a list in sorted order. The +** tokens are not removed from the hash table. */ static int fts5HashEntrySort( Fts5Hash *pHash, diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index 4e6afb2815..c7c02cf6fe 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -2719,6 +2719,14 @@ static void fts5SegIterHashInit( pLeaf->p = (u8*)pList; } } + + /* The call to sqlite3Fts5HashScanInit() causes the hash table to + ** fill the size field of all existing position lists. This means they + ** can no longer be appended to. Since the only scenario in which they + ** can be appended to is if the previous operation on this table was + ** a DELETE, by clearing the Fts5Index.bDelete flag we can avoid this + ** possibility altogether. */ + p->bDelete = 0; }else{ p->rc = sqlite3Fts5HashQuery(p->pHash, sizeof(Fts5Data), (const char*)pTerm, nTerm, (void**)&pLeaf, &nList @@ -6204,7 +6212,7 @@ int sqlite3Fts5IndexBeginWrite(Fts5Index *p, int bDelete, i64 iRowid){ /* Flush the hash table to disk if required */ if( iRowidiWriteRowid || (iRowid==p->iWriteRowid && p->bDelete==0) - || (p->nPendingData > p->pConfig->nHashSize) + || (p->nPendingData > p->pConfig->nHashSize) ){ fts5IndexFlush(p); } diff --git a/ext/fts5/test/fts5prefix2.test b/ext/fts5/test/fts5prefix2.test index bf16e81a73..29744c86bf 100644 --- a/ext/fts5/test/fts5prefix2.test +++ b/ext/fts5/test/fts5prefix2.test @@ -52,6 +52,36 @@ do_execsql_test 2.1 { SELECT * FROM t2('to*'); } {top to tommy} +#------------------------------------------------------------------------- + +foreach {tn newrowid} { + 1 122 + 2 123 + 3 124 +} { + reset_db + do_execsql_test 3.$tn.0 { + CREATE VIRTUAL TABLE t12 USING fts5(x); + INSERT INTO t12(rowid, x) VALUES(123, 'wwww'); + } + do_execsql_test 3.$tn.1 { + BEGIN; + DELETE FROM t12 WHERE rowid=123; + SELECT * FROM t12('wwww*'); + INSERT INTO t12(rowid, x) VALUES($newrowid, 'wwww'); + SELECT * FROM t12('wwww*'); + END; + } {wwww} + do_execsql_test 3.$tn.2 { + INSERT INTO t12(t12) VALUES('integrity-check'); + } + do_execsql_test 3.$tn.3 { + SELECT rowid FROM t12('wwww*'); + } $newrowid +} + +finish_test + finish_test diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 155e4e7f63..25dc1596a4 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -35,6 +35,8 @@ $(dir.bld.c): javac.flags ?= -Xlint:unchecked -Xlint:deprecation java.flags ?= +javac.flags += -encoding utf8 +# -------------^^^^^^^^^^^^^^ required for Windows builds jnicheck ?= 1 ifeq (1,$(jnicheck)) java.flags += -Xcheck:jni @@ -110,6 +112,7 @@ JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/annotation/%,\ WindowFunction.java \ XDestroyCallback.java \ sqlite3.java \ + sqlite3_blob.java \ sqlite3_context.java \ sqlite3_stmt.java \ sqlite3_value.java \ @@ -160,12 +163,13 @@ endif CLASS_FILES := define CLASSFILE_DEPS all: $(1).class +$(1).class: $(1).java CLASS_FILES += $(1).class endef $(foreach B,$(basename \ $(JAVA_FILES.main) $(JAVA_FILES.unittest) $(JAVA_FILES.tester)),\ $(eval $(call CLASSFILE_DEPS,$(B)))) -$(CLASS_FILES): $(JAVA_FILES) $(MAKEFILE) +$(CLASS_FILES): $(MAKEFILE) $(bin.javac) $(javac.flags) -h $(dir.bld.c) -cp $(classpath) $(JAVA_FILES) #.PHONY: classfiles @@ -227,7 +231,8 @@ SQLITE_OPT += -DSQLITE_ENABLE_RTREE \ -DSQLITE_ENABLE_OFFSET_SQL_FUNC \ -DSQLITE_ENABLE_PREUPDATE_HOOK \ -DSQLITE_ENABLE_NORMALIZE \ - -DSQLITE_ENABLE_SQLLOG + -DSQLITE_ENABLE_SQLLOG \ + -DSQLITE_ENABLE_COLUMN_METADATA endif ifeq (1,$(opt.debug)) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 6d54391e18..fafb2ab5fd 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -185,6 +185,8 @@ ** ** This use of intptr_t is the _only_ reason we require ** which, in turn, requires building with -std=c99 (or later). +** +** See also: the notes for LongPtrGet_T. */ #define S3JniCast_L2P(JLongAsPtr) (void*)((intptr_t)(JLongAsPtr)) #define S3JniCast_P2L(PTR) (jlong)((intptr_t)(PTR)) @@ -201,8 +203,8 @@ ** ** https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#jni_interface_functions_and_pointers */ -#define JniArgsEnvObj JNIEnv * const env, jobject jSelf -#define JniArgsEnvClass JNIEnv * const env, jclass jKlazz +#define JniArgsEnvObj JNIEnv * env, jobject jSelf +#define JniArgsEnvClass JNIEnv * env, jclass jKlazz /* ** Helpers to account for -Xcheck:jni warnings about not having ** checked for exceptions. @@ -651,6 +653,17 @@ struct S3JniGlobalType { jmethodID ctorLong1 /* the Long(long) constructor */; jmethodID ctorStringBA /* the String(byte[],Charset) constructor */; jmethodID stringGetBytes /* the String.getBytes(Charset) method */; + + /* + ByteBuffer may or may not be supported via JNI on any given + platform: + + https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#nio_support + + We only store a ref to the following if JNI support for + ByteBuffer is available (which we determine during static init). + */ + jclass cByteBuffer /* global ref to java.nio.ByteBuffer */; } g; /* ** The list of Java-side auto-extensions @@ -1474,7 +1487,7 @@ static void * NativePointerHolder__get(JNIEnv * env, jobject jNph, #define PtrGet_sqlite3_stmt(OBJ) PtrGet_T(sqlite3_stmt, OBJ) #define PtrGet_sqlite3_value(OBJ) PtrGet_T(sqlite3_value, OBJ) /* -** S3JniLongPtr_T(X,Y) expects X to be an unqualified sqlite3 struct +** LongPtrGet_T(X,Y) expects X to be an unqualified sqlite3 struct ** type name and Y to be a native pointer to such an object in the ** form of a jlong value. The jlong is simply cast to (X*). This ** approach is, as of 2023-09-27, supplanting the former approach. We @@ -1482,13 +1495,22 @@ static void * NativePointerHolder__get(JNIEnv * env, jobject jNph, ** the C side, because it's reportedly significantly faster. The ** intptr_t part here is necessary for compatibility with (at least) ** ARM32. +** +** 2023-11-09: testing has not revealed any measurable performance +** difference between the approach of passing type T to C compared to +** passing pointer-to-T to C, and adding support for the latter +** everywhere requires sigificantly more code. As of this writing, the +** older/simpler approach is being applied except for (A) where the +** newer approach has already been applied and (B) hot-spot APIs where +** a difference of microseconds (i.e. below our testing measurement +** threshold) might add up. */ -#define S3JniLongPtr_T(T,JLongAsPtr) (T*)((intptr_t)(JLongAsPtr)) -#define S3JniLongPtr_sqlite3(JLongAsPtr) S3JniLongPtr_T(sqlite3,JLongAsPtr) -#define S3JniLongPtr_sqlite3_backup(JLongAsPtr) S3JniLongPtr_T(sqlite3_backup,JLongAsPtr) -#define S3JniLongPtr_sqlite3_blob(JLongAsPtr) S3JniLongPtr_T(sqlite3_blob,JLongAsPtr) -#define S3JniLongPtr_sqlite3_stmt(JLongAsPtr) S3JniLongPtr_T(sqlite3_stmt,JLongAsPtr) -#define S3JniLongPtr_sqlite3_value(JLongAsPtr) S3JniLongPtr_T(sqlite3_value,JLongAsPtr) +#define LongPtrGet_T(T,JLongAsPtr) (T*)((intptr_t)(JLongAsPtr)) +#define LongPtrGet_sqlite3(JLongAsPtr) LongPtrGet_T(sqlite3,JLongAsPtr) +#define LongPtrGet_sqlite3_backup(JLongAsPtr) LongPtrGet_T(sqlite3_backup,JLongAsPtr) +#define LongPtrGet_sqlite3_blob(JLongAsPtr) LongPtrGet_T(sqlite3_blob,JLongAsPtr) +#define LongPtrGet_sqlite3_stmt(JLongAsPtr) LongPtrGet_T(sqlite3_stmt,JLongAsPtr) +#define LongPtrGet_sqlite3_value(JLongAsPtr) LongPtrGet_T(sqlite3_value,JLongAsPtr) /* ** Extracts the new S3JniDb instance from the free-list, or allocates ** one if needed, associates it with pDb, and returns. Returns NULL @@ -1547,7 +1569,7 @@ static void S3JniDb_xDestroy(void *p){ #define S3JniDb_from_c(sqlite3Ptr) \ ((sqlite3Ptr) ? S3JniDb_from_clientdata(sqlite3Ptr) : 0) #define S3JniDb_from_jlong(sqlite3PtrAsLong) \ - S3JniDb_from_c(S3JniLongPtr_T(sqlite3,sqlite3PtrAsLong)) + S3JniDb_from_c(LongPtrGet_T(sqlite3,sqlite3PtrAsLong)) /* ** Unref any Java-side state in (S3JniAutoExtension*) AX and zero out @@ -2052,12 +2074,12 @@ static void udf_xInverse(sqlite3_context* cx, int argc, /** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*)). */ #define WRAP_INT_STMT(JniNameSuffix,CName) \ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpStmt){ \ - return (jint)CName(S3JniLongPtr_sqlite3_stmt(jpStmt)); \ + return (jint)CName(LongPtrGet_sqlite3_stmt(jpStmt)); \ } /** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*,int)). */ #define WRAP_INT_STMT_INT(JniNameSuffix,CName) \ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpStmt, jint n){ \ - return (jint)CName(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)n); \ + return (jint)CName(LongPtrGet_sqlite3_stmt(jpStmt), (int)n); \ } /** Create a trivial JNI wrapper for (boolean CName(sqlite3_stmt*)). */ #define WRAP_BOOL_STMT(JniNameSuffix,CName) \ @@ -2068,41 +2090,41 @@ static void udf_xInverse(sqlite3_context* cx, int argc, #define WRAP_STR_STMT_INT(JniNameSuffix,CName) \ JniDecl(jstring,JniNameSuffix)(JniArgsEnvClass, jlong jpStmt, jint ndx){ \ return s3jni_utf8_to_jstring( \ - CName(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx), \ + CName(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx), \ -1); \ } /** Create a trivial JNI wrapper for (boolean CName(sqlite3*)). */ #define WRAP_BOOL_DB(JniNameSuffix,CName) \ JniDecl(jboolean,JniNameSuffix)(JniArgsEnvClass, jlong jpDb){ \ - return CName(S3JniLongPtr_sqlite3(jpDb)) ? JNI_TRUE : JNI_FALSE; \ + return CName(LongPtrGet_sqlite3(jpDb)) ? JNI_TRUE : JNI_FALSE; \ } /** Create a trivial JNI wrapper for (int CName(sqlite3*)). */ #define WRAP_INT_DB(JniNameSuffix,CName) \ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpDb){ \ - return (jint)CName(S3JniLongPtr_sqlite3(jpDb)); \ + return (jint)CName(LongPtrGet_sqlite3(jpDb)); \ } /** Create a trivial JNI wrapper for (int64 CName(sqlite3*)). */ #define WRAP_INT64_DB(JniNameSuffix,CName) \ JniDecl(jlong,JniNameSuffix)(JniArgsEnvClass, jlong jpDb){ \ - return (jlong)CName(S3JniLongPtr_sqlite3(jpDb)); \ + return (jlong)CName(LongPtrGet_sqlite3(jpDb)); \ } /** Create a trivial JNI wrapper for (jstring CName(sqlite3*,int)). */ #define WRAP_STR_DB_INT(JniNameSuffix,CName) \ JniDecl(jstring,JniNameSuffix)(JniArgsEnvClass, jlong jpDb, jint ndx){ \ return s3jni_utf8_to_jstring( \ - CName(S3JniLongPtr_sqlite3(jpDb), (int)ndx), \ + CName(LongPtrGet_sqlite3(jpDb), (int)ndx), \ -1); \ } /** Create a trivial JNI wrapper for (int CName(sqlite3_value*)). */ #define WRAP_INT_SVALUE(JniNameSuffix,CName,DfltOnNull) \ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpSValue){ \ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSValue); \ + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSValue); \ return (jint)(sv ? CName(sv): DfltOnNull); \ } /** Create a trivial JNI wrapper for (boolean CName(sqlite3_value*)). */ #define WRAP_BOOL_SVALUE(JniNameSuffix,CName,DfltOnNull) \ JniDecl(jboolean,JniNameSuffix)(JniArgsEnvClass, jlong jpSValue){ \ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSValue); \ + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSValue); \ return (jint)(sv ? CName(sv) : DfltOnNull) \ ? JNI_TRUE : JNI_FALSE; \ } @@ -2115,9 +2137,11 @@ WRAP_INT_STMT_INT(1column_1bytes16, sqlite3_column_bytes16) WRAP_INT_STMT(1column_1count, sqlite3_column_count) WRAP_STR_STMT_INT(1column_1decltype, sqlite3_column_decltype) WRAP_STR_STMT_INT(1column_1name, sqlite3_column_name) +#ifdef SQLITE_ENABLE_COLUMN_METADATA WRAP_STR_STMT_INT(1column_1database_1name, sqlite3_column_database_name) WRAP_STR_STMT_INT(1column_1origin_1name, sqlite3_column_origin_name) WRAP_STR_STMT_INT(1column_1table_1name, sqlite3_column_table_name) +#endif WRAP_INT_STMT_INT(1column_1type, sqlite3_column_type) WRAP_INT_STMT(1data_1count, sqlite3_data_count) WRAP_STR_DB_INT(1db_1name, sqlite3_db_name) @@ -2315,7 +2339,7 @@ S3JniApi(sqlite3_backup_finish(),jint,1backup_1finish)( ){ int rc = 0; if( jpBack!=0 ){ - rc = sqlite3_backup_finish( S3JniLongPtr_sqlite3_backup(jpBack) ); + rc = sqlite3_backup_finish( LongPtrGet_sqlite3_backup(jpBack) ); } return rc; } @@ -2324,8 +2348,8 @@ S3JniApi(sqlite3_backup_init(),jobject,1backup_1init)( JniArgsEnvClass, jlong jpDbDest, jstring jTDest, jlong jpDbSrc, jstring jTSrc ){ - sqlite3 * const pDest = S3JniLongPtr_sqlite3(jpDbDest); - sqlite3 * const pSrc = S3JniLongPtr_sqlite3(jpDbSrc); + sqlite3 * const pDest = LongPtrGet_sqlite3(jpDbDest); + sqlite3 * const pSrc = LongPtrGet_sqlite3(jpDbSrc); char * const zDest = s3jni_jstring_to_utf8(jTDest, 0); char * const zSrc = s3jni_jstring_to_utf8(jTSrc, 0); jobject rv = 0; @@ -2348,19 +2372,19 @@ S3JniApi(sqlite3_backup_init(),jobject,1backup_1init)( S3JniApi(sqlite3_backup_pagecount(),jint,1backup_1pagecount)( JniArgsEnvClass, jlong jpBack ){ - return sqlite3_backup_pagecount(S3JniLongPtr_sqlite3_backup(jpBack)); + return sqlite3_backup_pagecount(LongPtrGet_sqlite3_backup(jpBack)); } S3JniApi(sqlite3_backup_remaining(),jint,1backup_1remaining)( JniArgsEnvClass, jlong jpBack ){ - return sqlite3_backup_remaining(S3JniLongPtr_sqlite3_backup(jpBack)); + return sqlite3_backup_remaining(LongPtrGet_sqlite3_backup(jpBack)); } S3JniApi(sqlite3_backup_step(),jint,1backup_1step)( JniArgsEnvClass, jlong jpBack, jint nPage ){ - return sqlite3_backup_step(S3JniLongPtr_sqlite3_backup(jpBack), (int)nPage); + return sqlite3_backup_step(LongPtrGet_sqlite3_backup(jpBack), (int)nPage); } S3JniApi(sqlite3_bind_blob(),jint,1bind_1blob)( @@ -2373,13 +2397,13 @@ S3JniApi(sqlite3_bind_blob(),jint,1bind_1blob)( if( nMax>nBA ){ nMax = nBA; } - rc = sqlite3_bind_blob(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, + rc = sqlite3_bind_blob(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, pBuf, (int)nMax, SQLITE_TRANSIENT); s3jni_jbyteArray_release(baData, pBuf); }else{ rc = baData ? SQLITE_NOMEM - : sqlite3_bind_null( S3JniLongPtr_sqlite3_stmt(jpStmt), ndx ); + : sqlite3_bind_null( LongPtrGet_sqlite3_stmt(jpStmt), ndx ); } return (jint)rc; } @@ -2387,20 +2411,20 @@ S3JniApi(sqlite3_bind_blob(),jint,1bind_1blob)( S3JniApi(sqlite3_bind_double(),jint,1bind_1double)( JniArgsEnvClass, jlong jpStmt, jint ndx, jdouble val ){ - return (jint)sqlite3_bind_double(S3JniLongPtr_sqlite3_stmt(jpStmt), + return (jint)sqlite3_bind_double(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, (double)val); } S3JniApi(sqlite3_bind_int(),jint,1bind_1int)( JniArgsEnvClass, jlong jpStmt, jint ndx, jint val ){ - return (jint)sqlite3_bind_int(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, (int)val); + return (jint)sqlite3_bind_int(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)val); } S3JniApi(sqlite3_bind_int64(),jint,1bind_1int64)( JniArgsEnvClass, jlong jpStmt, jint ndx, jlong val ){ - return (jint)sqlite3_bind_int64(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)val); + return (jint)sqlite3_bind_int64(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)val); } /* @@ -2409,7 +2433,7 @@ S3JniApi(sqlite3_bind_int64(),jint,1bind_1int64)( S3JniApi(sqlite3_bind_java_object(),jint,1bind_1java_1object)( JniArgsEnvClass, jlong jpStmt, jint ndx, jobject val ){ - sqlite3_stmt * const pStmt = S3JniLongPtr_sqlite3_stmt(jpStmt); + sqlite3_stmt * const pStmt = LongPtrGet_sqlite3_stmt(jpStmt); int rc = SQLITE_MISUSE; if(pStmt){ @@ -2429,13 +2453,13 @@ S3JniApi(sqlite3_bind_java_object(),jint,1bind_1java_1object)( S3JniApi(sqlite3_bind_null(),jint,1bind_1null)( JniArgsEnvClass, jlong jpStmt, jint ndx ){ - return (jint)sqlite3_bind_null(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx); + return (jint)sqlite3_bind_null(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx); } S3JniApi(sqlite3_bind_parameter_count(),jint,1bind_1parameter_1count)( JniArgsEnvClass, jlong jpStmt ){ - return (jint)sqlite3_bind_parameter_count(S3JniLongPtr_sqlite3_stmt(jpStmt)); + return (jint)sqlite3_bind_parameter_count(LongPtrGet_sqlite3_stmt(jpStmt)); } S3JniApi(sqlite3_bind_parameter_index(),jint,1bind_1parameter_1index)( @@ -2444,7 +2468,7 @@ S3JniApi(sqlite3_bind_parameter_index(),jint,1bind_1parameter_1index)( int rc = 0; jbyte * const pBuf = s3jni_jbyteArray_bytes(jName); if( pBuf ){ - rc = sqlite3_bind_parameter_index(S3JniLongPtr_sqlite3_stmt(jpStmt), + rc = sqlite3_bind_parameter_index(LongPtrGet_sqlite3_stmt(jpStmt), (const char *)pBuf); s3jni_jbyteArray_release(jName, pBuf); } @@ -2455,7 +2479,7 @@ S3JniApi(sqlite3_bind_parameter_name(),jstring,1bind_1parameter_1name)( JniArgsEnvClass, jlong jpStmt, jint ndx ){ const char *z = - sqlite3_bind_parameter_name(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx); + sqlite3_bind_parameter_name(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx); return z ? s3jni_utf8_to_jstring(z, -1) : 0; } @@ -2477,14 +2501,14 @@ static int s3jni__bind_text(int is16, JNIEnv *env, jlong jpStmt, jint ndx, such cases, we do not expose the byte-limit arguments in the public API. */ rc = is16 - ? sqlite3_bind_text16(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, + ? sqlite3_bind_text16(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, pBuf, (int)nMax, SQLITE_TRANSIENT) - : sqlite3_bind_text(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, + : sqlite3_bind_text(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, (const char *)pBuf, (int)nMax, SQLITE_TRANSIENT); }else{ rc = baData - ? sqlite3_bind_null(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx) + ? sqlite3_bind_null(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx) : SQLITE_NOMEM; } s3jni_jbyteArray_release(baData, pBuf); @@ -2508,9 +2532,9 @@ S3JniApi(sqlite3_bind_value(),jint,1bind_1value)( JniArgsEnvClass, jlong jpStmt, jint ndx, jlong jpValue ){ int rc = 0; - sqlite3_stmt * pStmt = S3JniLongPtr_sqlite3_stmt(jpStmt); + sqlite3_stmt * pStmt = LongPtrGet_sqlite3_stmt(jpStmt); if( pStmt ){ - sqlite3_value *v = S3JniLongPtr_sqlite3_value(jpValue); + sqlite3_value *v = LongPtrGet_sqlite3_value(jpValue); if( v ){ rc = sqlite3_bind_value(pStmt, (int)ndx, v); }else{ @@ -2525,27 +2549,27 @@ S3JniApi(sqlite3_bind_value(),jint,1bind_1value)( S3JniApi(sqlite3_bind_zeroblob(),jint,1bind_1zeroblob)( JniArgsEnvClass, jlong jpStmt, jint ndx, jint n ){ - return (jint)sqlite3_bind_zeroblob(S3JniLongPtr_sqlite3_stmt(jpStmt), + return (jint)sqlite3_bind_zeroblob(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)n); } S3JniApi(sqlite3_bind_zeroblob64(),jint,1bind_1zeroblob64)( JniArgsEnvClass, jlong jpStmt, jint ndx, jlong n ){ - return (jint)sqlite3_bind_zeroblob64(S3JniLongPtr_sqlite3_stmt(jpStmt), + return (jint)sqlite3_bind_zeroblob64(LongPtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_uint64)n); } S3JniApi(sqlite3_blob_bytes(),jint,1blob_1bytes)( JniArgsEnvClass, jlong jpBlob ){ - return sqlite3_blob_bytes(S3JniLongPtr_sqlite3_blob(jpBlob)); + return sqlite3_blob_bytes(LongPtrGet_sqlite3_blob(jpBlob)); } S3JniApi(sqlite3_blob_close(),jint,1blob_1close)( JniArgsEnvClass, jlong jpBlob ){ - sqlite3_blob * const b = S3JniLongPtr_sqlite3_blob(jpBlob); + sqlite3_blob * const b = LongPtrGet_sqlite3_blob(jpBlob); return b ? (jint)sqlite3_blob_close(b) : SQLITE_MISUSE; } @@ -2553,7 +2577,7 @@ S3JniApi(sqlite3_blob_open(),jint,1blob_1open)( JniArgsEnvClass, jlong jpDb, jstring jDbName, jstring jTbl, jstring jCol, jlong jRowId, jint flags, jobject jOut ){ - sqlite3 * const db = S3JniLongPtr_sqlite3(jpDb); + sqlite3 * const db = LongPtrGet_sqlite3(jpDb); sqlite3_blob * pBlob = 0; char * zDbName = 0, * zTableName = 0, * zColumnName = 0; int rc; @@ -2587,7 +2611,7 @@ S3JniApi(sqlite3_blob_read(),jint,1blob_1read)( int rc = jTgt ? (pBa ? SQLITE_MISUSE : SQLITE_NOMEM) : SQLITE_MISUSE; if( pBa ){ jsize const nTgt = (*env)->GetArrayLength(env, jTgt); - rc = sqlite3_blob_read(S3JniLongPtr_sqlite3_blob(jpBlob), pBa, + rc = sqlite3_blob_read(LongPtrGet_sqlite3_blob(jpBlob), pBa, (int)nTgt, (int)iOffset); if( 0==rc ){ s3jni_jbyteArray_commit(jTgt, pBa); @@ -2601,14 +2625,14 @@ S3JniApi(sqlite3_blob_read(),jint,1blob_1read)( S3JniApi(sqlite3_blob_reopen(),jint,1blob_1reopen)( JniArgsEnvClass, jlong jpBlob, jlong iNewRowId ){ - return (jint)sqlite3_blob_reopen(S3JniLongPtr_sqlite3_blob(jpBlob), + return (jint)sqlite3_blob_reopen(LongPtrGet_sqlite3_blob(jpBlob), (sqlite3_int64)iNewRowId); } S3JniApi(sqlite3_blob_write(),jint,1blob_1write)( JniArgsEnvClass, jlong jpBlob, jbyteArray jBa, jint iOffset ){ - sqlite3_blob * const b = S3JniLongPtr_sqlite3_blob(jpBlob); + sqlite3_blob * const b = LongPtrGet_sqlite3_blob(jpBlob); jbyte * const pBuf = b ? s3jni_jbyteArray_bytes(jBa) : 0; const jsize nBA = pBuf ? (*env)->GetArrayLength(env, jBa) : 0; int rc = SQLITE_MISUSE; @@ -2817,7 +2841,7 @@ S3JniApi(sqlite3_collation_needed(),jint,1collation_1needed)( }else{ jclass const klazz = (*env)->GetObjectClass(env, jHook); jmethodID const xCallback = (*env)->GetMethodID( - env, klazz, "call", "(Lorg/sqlite/jni/capi/sqlite3;ILjava/lang/String;)I" + env, klazz, "call", "(Lorg/sqlite/jni/capi/sqlite3;ILjava/lang/String;)V" ); S3JniUnrefLocal(klazz); S3JniIfThrew { @@ -2870,7 +2894,7 @@ S3JniApi(sqlite3_column_int64(),jlong,1column_1int64)( S3JniApi(sqlite3_column_java_object(),jobject,1column_1java_1object)( JniArgsEnvClass, jlong jpStmt, jint ndx ){ - sqlite3_stmt * const stmt = S3JniLongPtr_sqlite3_stmt(jpStmt); + sqlite3_stmt * const stmt = LongPtrGet_sqlite3_stmt(jpStmt); jobject rv = 0; if( stmt ){ sqlite3 * const db = sqlite3_db_handle(stmt); @@ -2942,7 +2966,10 @@ static int s3jni_commit_rollback_hook_impl(int isCommit, S3JniDb * const ps){ ? (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback) : (int)((*env)->CallVoidMethod(env, hook.jObj, hook.midCallback), 0); S3JniIfThrew{ - rc = s3jni_db_exception(ps->pDb, SQLITE_ERROR, "hook callback threw"); + rc = s3jni_db_exception(ps->pDb, SQLITE_ERROR, + isCommit + ? "Commit hook callback threw" + : "Rollback hook callback threw"); } S3JniHook_localundup(hook); } @@ -3045,7 +3072,7 @@ S3JniApi(sqlite3_compileoption_used(),jboolean,1compileoption_1used)( return rc; } -S3JniApi(sqlite3_complete(),int,1complete)( +S3JniApi(sqlite3_complete(),jint,1complete)( JniArgsEnvClass, jbyteArray jSql ){ jbyte * const pBuf = s3jni_jbyteArray_bytes(jSql); @@ -3429,7 +3456,6 @@ S3JniApi( } break; } - case 0: default: rc = SQLITE_MISUSE; } @@ -3492,7 +3518,7 @@ S3JniApi(sqlite3_db_readonly(),jint,1db_1readonly)( return (jint)rc; } -S3JniApi(sqlite3_db_release_memory(),int,1db_1release_1memory)( +S3JniApi(sqlite3_db_release_memory(),jint,1db_1release_1memory)( JniArgsEnvClass, jobject jDb ){ sqlite3 * const pDb = PtrGet_sqlite3(jDb); @@ -3594,19 +3620,21 @@ S3JniApi(sqlite3_normalized_sql(),jstring,1normalized_1sql)( #endif } -S3JniApi(sqlite3_extended_result_codes(),jboolean,1extended_1result_1codes)( +S3JniApi(sqlite3_extended_result_codes(),jint,1extended_1result_1codes)( JniArgsEnvClass, jobject jpDb, jboolean onoff ){ sqlite3 * const pDb = PtrGet_sqlite3(jpDb); - int const rc = pDb ? sqlite3_extended_result_codes(pDb, onoff ? 1 : 0) : 0; - return rc ? JNI_TRUE : JNI_FALSE; + int const rc = pDb + ? sqlite3_extended_result_codes(pDb, onoff ? 1 : 0) + : SQLITE_MISUSE; + return rc; } S3JniApi(sqlite3_finalize(),jint,1finalize)( JniArgsEnvClass, jlong jpStmt ){ return jpStmt - ? sqlite3_finalize(S3JniLongPtr_sqlite3_stmt(jpStmt)) + ? sqlite3_finalize(LongPtrGet_sqlite3_stmt(jpStmt)) : 0; } @@ -3655,6 +3683,11 @@ JniDecl(jboolean,1java_1uncache_1thread)(JniArgsEnvClass){ return rc ? JNI_TRUE : JNI_FALSE; } +JniDecl(jboolean,1jni_1supports_1nio)(JniArgsEnvClass){ + return SJG.g.cByteBuffer ? JNI_TRUE : JNI_FALSE; +} + + S3JniApi(sqlite3_keyword_check(),jboolean,1keyword_1check)( JniArgsEnvClass, jstring jWord ){ @@ -3835,7 +3868,7 @@ jint sqlite3_jni_prepare_v123( int prepVersion, JNIEnv * const env, jclass self, sqlite3_stmt * pStmt = 0; jobject jStmt = 0; const char * zTail = 0; - sqlite3 * const pDb = S3JniLongPtr_sqlite3(jpDb); + sqlite3 * const pDb = LongPtrGet_sqlite3(jpDb); jbyte * const pBuf = pDb ? s3jni_jbyteArray_bytes(baSql) : 0; int rc = SQLITE_ERROR; @@ -3990,11 +4023,11 @@ static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb, #if !defined(SQLITE_ENABLE_PREUPDATE_HOOK) /* We need no-op impls for preupdate_{count,depth,blobwrite}() */ -S3JniApi(sqlite3_preupdate_blobwrite(),int,1preupdate_1blobwrite)( +S3JniApi(sqlite3_preupdate_blobwrite(),jint,1preupdate_1blobwrite)( JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; } -S3JniApi(sqlite3_preupdate_count(),int,1preupdate_1count)( +S3JniApi(sqlite3_preupdate_count(),jint,1preupdate_1count)( JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; } -S3JniApi(sqlite3_preupdate_depth(),int,1preupdate_1depth)( +S3JniApi(sqlite3_preupdate_depth(),jint,1preupdate_1depth)( JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; } #endif /* !SQLITE_ENABLE_PREUPDATE_HOOK */ @@ -4089,7 +4122,7 @@ S3JniApi(sqlite3_preupdate_hook(),jobject,1preupdate_1hook)( static int s3jni_preupdate_newold(JNIEnv * const env, int isNew, jlong jpDb, jint iCol, jobject jOut){ #ifdef SQLITE_ENABLE_PREUPDATE_HOOK - sqlite3 * const pDb = S3JniLongPtr_sqlite3(jpDb); + sqlite3 * const pDb = LongPtrGet_sqlite3(jpDb); int rc = SQLITE_MISUSE; if( pDb ){ sqlite3_value * pOut = 0; @@ -4310,7 +4343,7 @@ S3JniApi(sqlite3_result_double(),void,1result_1double)( } S3JniApi(sqlite3_result_error(),void,1result_1error)( - JniArgsEnvClass, jobject jpCx, jbyteArray baMsg, int eTextRep + JniArgsEnvClass, jobject jpCx, jbyteArray baMsg, jint eTextRep ){ const char * zUnspecified = "Unspecified error."; jsize const baLen = (*env)->GetArrayLength(env, baMsg); @@ -4570,20 +4603,8 @@ S3JniApi(sqlite3_shutdown(),jint,1shutdown)( S3JniEnv_uncache( SJG.envCache.aHead->env ); } } S3JniEnv_mutex_leave; -#if 0 - /* - ** Is automatically closing any still-open dbs a good idea? We will - ** get rid of the perDb list once sqlite3 gets a per-db client - ** state, at which point we won't have a central list of databases - ** to close. - */ - S3JniDb_mutex_enter; - while( SJG.perDb.pHead ){ - s3jni_close_db(env, SJG.perDb.pHead->jDb, 2); - } - S3JniDb_mutex_leave; -#endif - /* Do not clear S3JniGlobal.jvm: it's legal to restart the lib. */ + /* Do not clear S3JniGlobal.jvm or S3JniGlobal.g: it's legal to + ** restart the lib. */ return sqlite3_shutdown(); } @@ -4664,13 +4685,13 @@ S3JniApi(sqlite3_sql(),jstring,1sql)( } S3JniApi(sqlite3_step(),jint,1step)( - JniArgsEnvClass,jobject jStmt + JniArgsEnvClass, jlong jpStmt ){ - sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jStmt); + sqlite3_stmt * const pStmt = LongPtrGet_sqlite3_stmt(jpStmt); return pStmt ? (jint)sqlite3_step(pStmt) : (jint)SQLITE_MISUSE; } -S3JniApi(sqlite3_table_column_metadata(),int,1table_1column_1metadata)( +S3JniApi(sqlite3_table_column_metadata(),jint,1table_1column_1metadata)( JniArgsEnvClass, jobject jDb, jstring jDbName, jstring jTableName, jstring jColumnName, jobject jDataType, jobject jCollSeq, jobject jNotNull, jobject jPrimaryKey, jobject jAutoinc @@ -4846,7 +4867,7 @@ S3JniApi(sqlite3_update_hook(),jobject,1update_1hook)( S3JniApi(sqlite3_value_blob(),jbyteArray,1value_1blob)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); const jbyte * pBytes = sv ? sqlite3_value_blob(sv) : 0; int const nLen = pBytes ? sqlite3_value_bytes(sv) : 0; @@ -4856,17 +4877,17 @@ S3JniApi(sqlite3_value_blob(),jbyteArray,1value_1blob)( : NULL; } -S3JniApi(sqlite3_value_bytes(),int,1value_1bytes)( +S3JniApi(sqlite3_value_bytes(),jint,1value_1bytes)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); return sv ? sqlite3_value_bytes(sv) : 0; } -S3JniApi(sqlite3_value_bytes16(),int,1value_1bytes16)( +S3JniApi(sqlite3_value_bytes16(),jint,1value_1bytes16)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); return sv ? sqlite3_value_bytes16(sv) : 0; } @@ -4874,7 +4895,7 @@ S3JniApi(sqlite3_value_bytes16(),int,1value_1bytes16)( S3JniApi(sqlite3_value_double(),jdouble,1value_1double)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); return (jdouble) (sv ? sqlite3_value_double(sv) : 0.0); } @@ -4882,7 +4903,7 @@ S3JniApi(sqlite3_value_double(),jdouble,1value_1double)( S3JniApi(sqlite3_value_dup(),jobject,1value_1dup)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); sqlite3_value * const sd = sv ? sqlite3_value_dup(sv) : 0; jobject rv = sd ? new_java_sqlite3_value(env, sd) : 0; if( sd && !rv ) { @@ -4895,7 +4916,7 @@ S3JniApi(sqlite3_value_dup(),jobject,1value_1dup)( S3JniApi(sqlite3_value_free(),void,1value_1free)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); if( sv ){ sqlite3_value_free(sv); } @@ -4904,21 +4925,21 @@ S3JniApi(sqlite3_value_free(),void,1value_1free)( S3JniApi(sqlite3_value_int(),jint,1value_1int)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); return (jint) (sv ? sqlite3_value_int(sv) : 0); } S3JniApi(sqlite3_value_int64(),jlong,1value_1int64)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); return (jlong) (sv ? sqlite3_value_int64(sv) : 0LL); } S3JniApi(sqlite3_value_java_object(),jobject,1value_1java_1object)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); return sv ? sqlite3_value_pointer(sv, s3jni__value_jref_key) : 0; @@ -4927,7 +4948,7 @@ S3JniApi(sqlite3_value_java_object(),jobject,1value_1java_1object)( S3JniApi(sqlite3_value_text(),jbyteArray,1value_1text)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); const unsigned char * const p = sv ? sqlite3_value_text(sv) : 0; int const n = p ? sqlite3_value_bytes(sv) : 0; return p ? s3jni_new_jbyteArray(p, n) : 0; @@ -4938,7 +4959,7 @@ S3JniApi(sqlite3_value_text(),jbyteArray,1value_1text)( S3JniApi(sqlite3_value_text(),jstring,1value_1text)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); const unsigned char * const p = sv ? sqlite3_value_text(sv) : 0; int const n = p ? sqlite3_value_bytes(sv) : 0; return p ? s3jni_utf8_to_jstring( (const char *)p, n) : 0; @@ -4948,7 +4969,7 @@ S3JniApi(sqlite3_value_text(),jstring,1value_1text)( S3JniApi(sqlite3_value_text16(),jstring,1value_1text16)( JniArgsEnvClass, jlong jpSVal ){ - sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal); + sqlite3_value * const sv = LongPtrGet_sqlite3_value(jpSVal); const int n = sv ? sqlite3_value_bytes16(sv) : 0; const void * const p = sv ? sqlite3_value_text16(sv) : 0; return p ? s3jni_text16_to_jstring(env, p, n) : 0; @@ -5530,7 +5551,7 @@ JniDeclFtsXA(jlong,xRowid)(JniArgsEnvObj,jobject jCtx){ return (jlong)ext->xRowid(PtrGet_Fts5Context(jCtx)); } -JniDeclFtsXA(int,xSetAuxdata)(JniArgsEnvObj,jobject jCtx, jobject jAux){ +JniDeclFtsXA(jint,xSetAuxdata)(JniArgsEnvObj,jobject jCtx, jobject jAux){ Fts5ExtDecl; int rc; S3JniFts5AuxData * pAux; @@ -5924,6 +5945,19 @@ Java_org_sqlite_jni_capi_CApi_init(JniArgsEnvClass){ s3jni_oom_fatal( SJG.metrics.mutex ); #endif + { + /* Test whether this JVM supports direct memory access via + ByteBuffer. */ + unsigned char buf[16] = {0}; + jobject bb = (*env)->NewDirectByteBuffer(env, buf, 16); + if( bb ){ + SJG.g.cByteBuffer = (*env)->GetObjectClass(env, bb); + S3JniUnrefLocal(bb); + }else{ + SJG.g.cByteBuffer = 0; + } + } + sqlite3_shutdown() /* So that it becomes legal for Java-level code to call ** sqlite3_config(). */; diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index e4393ddd8b..51d49bba3c 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -427,8 +427,6 @@ extern "C" { #define org_sqlite_jni_capi_CApi_SQLITE_OPEN_EXRESCODE 33554432L #undef org_sqlite_jni_capi_CApi_SQLITE_PREPARE_PERSISTENT #define org_sqlite_jni_capi_CApi_SQLITE_PREPARE_PERSISTENT 1L -#undef org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NORMALIZE -#define org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NORMALIZE 2L #undef org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NO_VTAB #define org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NO_VTAB 4L #undef org_sqlite_jni_capi_CApi_SQLITE_OK @@ -707,8 +705,12 @@ extern "C" { #define org_sqlite_jni_capi_CApi_SQLITE_DETERMINISTIC 2048L #undef org_sqlite_jni_capi_CApi_SQLITE_DIRECTONLY #define org_sqlite_jni_capi_CApi_SQLITE_DIRECTONLY 524288L +#undef org_sqlite_jni_capi_CApi_SQLITE_SUBTYPE +#define org_sqlite_jni_capi_CApi_SQLITE_SUBTYPE 1048576L #undef org_sqlite_jni_capi_CApi_SQLITE_INNOCUOUS #define org_sqlite_jni_capi_CApi_SQLITE_INNOCUOUS 2097152L +#undef org_sqlite_jni_capi_CApi_SQLITE_RESULT_SUBTYPE +#define org_sqlite_jni_capi_CApi_SQLITE_RESULT_SUBTYPE 16777216L #undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_SCAN_UNIQUE #define org_sqlite_jni_capi_CApi_SQLITE_INDEX_SCAN_UNIQUE 1L #undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_EQ @@ -775,6 +777,14 @@ JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_init JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1java_1uncache_1thread (JNIEnv *, jclass); +/* + * Class: org_sqlite_jni_capi_CApi + * Method: sqlite3_jni_supports_nio + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1jni_1supports_1nio + (JNIEnv *, jclass); + /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_aggregate_context @@ -1402,9 +1412,9 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1extended_1errcode /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_extended_result_codes - * Signature: (Lorg/sqlite/jni/capi/sqlite3;Z)Z + * Signature: (Lorg/sqlite/jni/capi/sqlite3;Z)I */ -JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1extended_1result_1codes +JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1extended_1result_1codes (JNIEnv *, jclass, jobject, jboolean); /* @@ -1866,10 +1876,10 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1status64 /* * Class: org_sqlite_jni_capi_CApi * Method: sqlite3_step - * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)I + * Signature: (J)I */ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1step - (JNIEnv *, jclass, jobject); + (JNIEnv *, jclass, jlong); /* * Class: org_sqlite_jni_capi_CApi diff --git a/ext/jni/src/org/sqlite/jni/annotation/NotNull.java b/ext/jni/src/org/sqlite/jni/annotation/NotNull.java index 3b4c1c7af1..57639fa0d9 100644 --- a/ext/jni/src/org/sqlite/jni/annotation/NotNull.java +++ b/ext/jni/src/org/sqlite/jni/annotation/NotNull.java @@ -18,12 +18,12 @@ package org.sqlite.jni.annotation; null or point to closed/finalized C-side resources.

In the case of Java types which map directly to C struct types - (e.g. {@link org.sqlite.jni.sqlite3}, {@link - org.sqlite.jni.sqlite3_stmt}, and {@link - org.sqlite.jni.sqlite3_context}), a closed/finalized resource is - also considered to be null for purposes this annotation because the - C-side effect of passing such a handle is the same as if null is - passed.

+ (e.g. {@link org.sqlite.jni.capi.sqlite3}, {@link + org.sqlite.jni.capi.sqlite3_stmt}, and {@link + org.sqlite.jni.capi.sqlite3_context}), a closed/finalized resource + is also considered to be null for purposes this annotation because + the C-side effect of passing such a handle is the same as if null + is passed.

When used in the context of Java interfaces which are called from the C APIs, this annotation communicates that the C API will @@ -31,12 +31,23 @@ package org.sqlite.jni.annotation;

Passing a null, for this annotation's definition of null, for any parameter marked with this annoation specifically invokes - undefined behavior.

+ undefined behavior (see below).

Passing 0 (i.e. C NULL) or a negative value for any long-type parameter marked with this annoation specifically invokes undefined - behavior. Such values are treated as C pointers in the JNI - layer.

+ behavior (see below). Such values are treated as C pointers in the + JNI layer.

+ +

Undefined behaviour: the JNI build uses the {@code + SQLITE_ENABLE_API_ARMOR} build flag, meaning that the C code + invoked with invalid NULL pointers and the like will not invoke + undefined behavior in the conventional C sense, but may, for + example, return result codes which are not documented for the + affected APIs or may otherwise behave unpredictably. In no known + cases will such arguments result in C-level code dereferencing a + NULL pointer or accessing out-of-bounds (or otherwise invalid) + memory. In other words, they may cause unexpected behavior but + should never cause an outright crash or security issue.

Note that the C-style API does not throw any exceptions on its own because it has a no-throw policy in order to retain its C-style @@ -48,7 +59,7 @@ package org.sqlite.jni.annotation; code.

This annotation is solely for the use by the classes in the - org.sqlite package and subpackages, but is made public so that + org.sqlite.jni package and subpackages, but is made public so that javadoc will link to it from the annotated functions. It is not part of the public API and client-level code must not rely on it.

diff --git a/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java b/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java index ce7c6fca6d..298e3a5900 100644 --- a/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java +++ b/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java @@ -20,7 +20,8 @@ import org.sqlite.jni.annotation.*; public interface AuthorizerCallback extends CallbackProxy { /** Must function as described for the C-level - sqlite3_set_authorizer() callback. + sqlite3_set_authorizer() callback. If it throws, the error is + converted to a db-level error and the exception is suppressed. */ int call(int opId, @Nullable String s1, @Nullable String s2, @Nullable String s3, @Nullable String s4); diff --git a/ext/jni/src/org/sqlite/jni/capi/CApi.java b/ext/jni/src/org/sqlite/jni/capi/CApi.java index 6eeeb29a24..07ca851ce5 100644 --- a/ext/jni/src/org/sqlite/jni/capi/CApi.java +++ b/ext/jni/src/org/sqlite/jni/capi/CApi.java @@ -123,6 +123,12 @@ public final class CApi { */ public static native boolean sqlite3_java_uncache_thread(); + /** + Returns true if this JVM has JNI-level support for direct memory + access using java.nio.ByteBuffer, else returns false. + */ + public static native boolean sqlite3_jni_supports_nio(); + ////////////////////////////////////////////////////////////////////// // Maintenance reminder: please keep the sqlite3_.... functions // alphabetized. The SQLITE_... values. on the other hand, are @@ -164,13 +170,13 @@ public final class CApi { */ public static native int sqlite3_auto_extension(@NotNull AutoExtensionCallback callback); - static native int sqlite3_backup_finish(@NotNull long ptrToBackup); + private static native int sqlite3_backup_finish(@NotNull long ptrToBackup); public static int sqlite3_backup_finish(@NotNull sqlite3_backup b){ - return sqlite3_backup_finish(b.clearNativePointer()); + return null==b ? 0 : sqlite3_backup_finish(b.clearNativePointer()); } - static native sqlite3_backup sqlite3_backup_init( + private static native sqlite3_backup sqlite3_backup_init( @NotNull long ptrToDbDest, @NotNull String destTableName, @NotNull long ptrToDbSrc, @NotNull String srcTableName ); @@ -183,25 +189,25 @@ public final class CApi { dbSrc.getNativePointer(), srcTableName ); } - static native int sqlite3_backup_pagecount(@NotNull long ptrToBackup); + private static native int sqlite3_backup_pagecount(@NotNull long ptrToBackup); public static int sqlite3_backup_pagecount(@NotNull sqlite3_backup b){ return sqlite3_backup_pagecount(b.getNativePointer()); } - static native int sqlite3_backup_remaining(@NotNull long ptrToBackup); + private static native int sqlite3_backup_remaining(@NotNull long ptrToBackup); public static int sqlite3_backup_remaining(@NotNull sqlite3_backup b){ return sqlite3_backup_remaining(b.getNativePointer()); } - static native int sqlite3_backup_step(@NotNull long ptrToBackup, int nPage); + private static native int sqlite3_backup_step(@NotNull long ptrToBackup, int nPage); public static int sqlite3_backup_step(@NotNull sqlite3_backup b, int nPage){ return sqlite3_backup_step(b.getNativePointer(), nPage); } - static native int sqlite3_bind_blob( + private static native int sqlite3_bind_blob( @NotNull long ptrToStmt, int ndx, @Nullable byte[] data, int n ); @@ -223,7 +229,7 @@ public final class CApi { : sqlite3_bind_blob(stmt.getNativePointer(), ndx, data, data.length); } - static native int sqlite3_bind_double( + private static native int sqlite3_bind_double( @NotNull long ptrToStmt, int ndx, double v ); @@ -233,7 +239,7 @@ public final class CApi { return sqlite3_bind_double(stmt.getNativePointer(), ndx, v); } - static native int sqlite3_bind_int( + private static native int sqlite3_bind_int( @NotNull long ptrToStmt, int ndx, int v ); @@ -243,7 +249,7 @@ public final class CApi { return sqlite3_bind_int(stmt.getNativePointer(), ndx, v); } - static native int sqlite3_bind_int64( + private static native int sqlite3_bind_int64( @NotNull long ptrToStmt, int ndx, long v ); @@ -251,7 +257,7 @@ public final class CApi { return sqlite3_bind_int64( stmt.getNativePointer(), ndx, v ); } - static native int sqlite3_bind_java_object( + private static native int sqlite3_bind_java_object( @NotNull long ptrToStmt, int ndx, @Nullable Object o ); @@ -267,13 +273,13 @@ public final class CApi { return sqlite3_bind_java_object(stmt.getNativePointer(), ndx, o); } - static native int sqlite3_bind_null(@NotNull long ptrToStmt, int ndx); + private static native int sqlite3_bind_null(@NotNull long ptrToStmt, int ndx); public static int sqlite3_bind_null(@NotNull sqlite3_stmt stmt, int ndx){ return sqlite3_bind_null(stmt.getNativePointer(), ndx); } - static native int sqlite3_bind_parameter_count(@NotNull long ptrToStmt); + private static native int sqlite3_bind_parameter_count(@NotNull long ptrToStmt); public static int sqlite3_bind_parameter_count(@NotNull sqlite3_stmt stmt){ return sqlite3_bind_parameter_count(stmt.getNativePointer()); @@ -300,7 +306,7 @@ public final class CApi { return null==utf8 ? 0 : sqlite3_bind_parameter_index(stmt.getNativePointer(), utf8); } - static native String sqlite3_bind_parameter_name( + private static native String sqlite3_bind_parameter_name( @NotNull long ptrToStmt, int index ); @@ -308,7 +314,7 @@ public final class CApi { return sqlite3_bind_parameter_name(stmt.getNativePointer(), index); } - static native int sqlite3_bind_text( + private static native int sqlite3_bind_text( @NotNull long ptrToStmt, int ndx, @Nullable byte[] utf8, int maxBytes ); @@ -352,7 +358,7 @@ public final class CApi { : sqlite3_bind_text(stmt.getNativePointer(), ndx, utf8, utf8.length); } - static native int sqlite3_bind_text16( + private static native int sqlite3_bind_text16( @NotNull long ptrToStmt, int ndx, @Nullable byte[] data, int maxBytes ); @@ -393,7 +399,7 @@ public final class CApi { : sqlite3_bind_text16(stmt.getNativePointer(), ndx, data, data.length); } - static native int sqlite3_bind_value(@NotNull long ptrToStmt, int ndx, long ptrToValue); + private static native int sqlite3_bind_value(@NotNull long ptrToStmt, int ndx, long ptrToValue); /** Functions like the C-level sqlite3_bind_value(), or @@ -404,13 +410,13 @@ public final class CApi { null==val ? 0L : val.getNativePointer()); } - static native int sqlite3_bind_zeroblob(@NotNull long ptrToStmt, int ndx, int n); + private static native int sqlite3_bind_zeroblob(@NotNull long ptrToStmt, int ndx, int n); public static int sqlite3_bind_zeroblob(@NotNull sqlite3_stmt stmt, int ndx, int n){ return sqlite3_bind_zeroblob(stmt.getNativePointer(), ndx, n); } - static native int sqlite3_bind_zeroblob64( + private static native int sqlite3_bind_zeroblob64( @NotNull long ptrToStmt, int ndx, long n ); @@ -418,19 +424,19 @@ public final class CApi { return sqlite3_bind_zeroblob64(stmt.getNativePointer(), ndx, n); } - static native int sqlite3_blob_bytes(@NotNull long ptrToBlob); + private static native int sqlite3_blob_bytes(@NotNull long ptrToBlob); public static int sqlite3_blob_bytes(@NotNull sqlite3_blob blob){ return sqlite3_blob_bytes(blob.getNativePointer()); } - static native int sqlite3_blob_close(@Nullable long ptrToBlob); + private static native int sqlite3_blob_close(@Nullable long ptrToBlob); public static int sqlite3_blob_close(@Nullable sqlite3_blob blob){ - return sqlite3_blob_close(blob.clearNativePointer()); + return null==blob ? 0 : sqlite3_blob_close(blob.clearNativePointer()); } - static native int sqlite3_blob_open( + private static native int sqlite3_blob_open( @NotNull long ptrToDb, @NotNull String dbName, @NotNull String tableName, @NotNull String columnName, long iRow, int flags, @NotNull OutputPointer.sqlite3_blob out @@ -458,7 +464,7 @@ public final class CApi { return out.take(); }; - static native int sqlite3_blob_read( + private static native int sqlite3_blob_read( @NotNull long ptrToBlob, @NotNull byte[] target, int iOffset ); @@ -468,7 +474,7 @@ public final class CApi { return sqlite3_blob_read(b.getNativePointer(), target, iOffset); } - static native int sqlite3_blob_reopen( + private static native int sqlite3_blob_reopen( @NotNull long ptrToBlob, long newRowId ); @@ -476,7 +482,7 @@ public final class CApi { return sqlite3_blob_reopen(b.getNativePointer(), newRowId); } - static native int sqlite3_blob_write( + private static native int sqlite3_blob_write( @NotNull long ptrToBlob, @NotNull byte[] bytes, int iOffset ); @@ -486,7 +492,7 @@ public final class CApi { return sqlite3_blob_write(b.getNativePointer(), bytes, iOffset); } - static native int sqlite3_busy_handler( + private static native int sqlite3_busy_handler( @NotNull long ptrToDb, @Nullable BusyHandlerCallback handler ); @@ -501,7 +507,7 @@ public final class CApi { return sqlite3_busy_handler(db.getNativePointer(), handler); } - static native int sqlite3_busy_timeout(@NotNull long ptrToDb, int ms); + private static native int sqlite3_busy_timeout(@NotNull long ptrToDb, int ms); public static int sqlite3_busy_timeout(@NotNull sqlite3 db, int ms){ return sqlite3_busy_timeout(db.getNativePointer(), ms); @@ -511,64 +517,59 @@ public final class CApi { @NotNull AutoExtensionCallback ax ); - static native int sqlite3_changes(@NotNull long ptrToDb); + private static native int sqlite3_changes(@NotNull long ptrToDb); public static int sqlite3_changes(@NotNull sqlite3 db){ return sqlite3_changes(db.getNativePointer()); } - static native long sqlite3_changes64(@NotNull long ptrToDb); + private static native long sqlite3_changes64(@NotNull long ptrToDb); public static long sqlite3_changes64(@NotNull sqlite3 db){ return sqlite3_changes64(db.getNativePointer()); } - static native int sqlite3_clear_bindings(@NotNull long ptrToStmt); + private static native int sqlite3_clear_bindings(@NotNull long ptrToStmt); public static int sqlite3_clear_bindings(@NotNull sqlite3_stmt stmt){ return sqlite3_clear_bindings(stmt.getNativePointer()); } - static native int sqlite3_close(@Nullable long ptrToDb); + private static native int sqlite3_close(@Nullable long ptrToDb); public static int sqlite3_close(@Nullable sqlite3 db){ - int rc = 0; - if( null!=db ){ - rc = sqlite3_close(db.getNativePointer()); - if( 0==rc ) db.clearNativePointer(); - } - return rc; + return null==db ? 0 : sqlite3_close(db.clearNativePointer()); } - static native int sqlite3_close_v2(@Nullable long ptrToDb); + private static native int sqlite3_close_v2(@Nullable long ptrToDb); public static int sqlite3_close_v2(@Nullable sqlite3 db){ - return db==null ? 0 : sqlite3_close_v2(db.clearNativePointer()); + return null==db ? 0 : sqlite3_close_v2(db.clearNativePointer()); } public static native byte[] sqlite3_column_blob( @NotNull sqlite3_stmt stmt, int ndx ); - static native int sqlite3_column_bytes(@NotNull long ptrToStmt, int ndx); + private static native int sqlite3_column_bytes(@NotNull long ptrToStmt, int ndx); public static int sqlite3_column_bytes(@NotNull sqlite3_stmt stmt, int ndx){ return sqlite3_column_bytes(stmt.getNativePointer(), ndx); } - static native int sqlite3_column_bytes16(@NotNull long ptrToStmt, int ndx); + private static native int sqlite3_column_bytes16(@NotNull long ptrToStmt, int ndx); public static int sqlite3_column_bytes16(@NotNull sqlite3_stmt stmt, int ndx){ return sqlite3_column_bytes16(stmt.getNativePointer(), ndx); } - static native int sqlite3_column_count(@NotNull long ptrToStmt); + private static native int sqlite3_column_count(@NotNull long ptrToStmt); public static int sqlite3_column_count(@NotNull sqlite3_stmt stmt){ return sqlite3_column_count(stmt.getNativePointer()); } - static native String sqlite3_column_decltype(@NotNull long ptrToStmt, int ndx); + private static native String sqlite3_column_decltype(@NotNull long ptrToStmt, int ndx); public static String sqlite3_column_decltype(@NotNull sqlite3_stmt stmt, int ndx){ return sqlite3_column_decltype(stmt.getNativePointer(), ndx); @@ -586,7 +587,7 @@ public final class CApi { @NotNull sqlite3_stmt stmt, int ndx ); - static native Object sqlite3_column_java_object( + private static native Object sqlite3_column_java_object( @NotNull long ptrToStmt, int ndx ); @@ -616,26 +617,35 @@ public final class CApi { return type.isInstance(o) ? (T)o : null; } - static native String sqlite3_column_name(@NotNull long ptrToStmt, int ndx); + private static native String sqlite3_column_name(@NotNull long ptrToStmt, int ndx); public static String sqlite3_column_name(@NotNull sqlite3_stmt stmt, int ndx){ return sqlite3_column_name(stmt.getNativePointer(), ndx); } - static native String sqlite3_column_database_name(@NotNull long ptrToStmt, int ndx); + private static native String sqlite3_column_database_name(@NotNull long ptrToStmt, int ndx); + /** + Only available if built with SQLITE_ENABLE_COLUMN_METADATA. + */ public static String sqlite3_column_database_name(@NotNull sqlite3_stmt stmt, int ndx){ return sqlite3_column_database_name(stmt.getNativePointer(), ndx); } - static native String sqlite3_column_origin_name(@NotNull long ptrToStmt, int ndx); + private static native String sqlite3_column_origin_name(@NotNull long ptrToStmt, int ndx); + /** + Only available if built with SQLITE_ENABLE_COLUMN_METADATA. + */ public static String sqlite3_column_origin_name(@NotNull sqlite3_stmt stmt, int ndx){ return sqlite3_column_origin_name(stmt.getNativePointer(), ndx); } - static native String sqlite3_column_table_name(@NotNull long ptrToStmt, int ndx); + private static native String sqlite3_column_table_name(@NotNull long ptrToStmt, int ndx); + /** + Only available if built with SQLITE_ENABLE_COLUMN_METADATA. + */ public static String sqlite3_column_table_name(@NotNull sqlite3_stmt stmt, int ndx){ return sqlite3_column_table_name(stmt.getNativePointer(), ndx); } @@ -693,7 +703,7 @@ public final class CApi { // return rv; // } - static native int sqlite3_column_type(@NotNull long ptrToStmt, int ndx); + private static native int sqlite3_column_type(@NotNull long ptrToStmt, int ndx); public static int sqlite3_column_type(@NotNull sqlite3_stmt stmt, int ndx){ return sqlite3_column_type(stmt.getNativePointer(), ndx); @@ -703,7 +713,7 @@ public final class CApi { @NotNull sqlite3_stmt stmt, int ndx ); - static native int sqlite3_collation_needed( + private static native int sqlite3_collation_needed( @NotNull long ptrToDb, @Nullable CollationNeededCallback callback ); @@ -717,7 +727,7 @@ public final class CApi { return sqlite3_collation_needed(db.getNativePointer(), callback); } - static native CommitHookCallback sqlite3_commit_hook( + private static native CommitHookCallback sqlite3_commit_hook( @NotNull long ptrToDb, @Nullable CommitHookCallback hook ); @@ -816,7 +826,7 @@ public final class CApi { int nArg, int eTextRep, @NotNull SQLFunction func ); - static native int sqlite3_data_count(@NotNull long ptrToStmt); + private static native int sqlite3_data_count(@NotNull long ptrToStmt); public static int sqlite3_data_count(@NotNull sqlite3_stmt stmt){ return sqlite3_data_count(stmt.getNativePointer()); @@ -828,7 +838,7 @@ public final class CApi { SQLITE_DBCONFIG_... options which uses this call form.

Unlike the C API, this returns SQLITE_MISUSE if its db argument - are null (as opposed to invoking UB). + is null (as opposed to invoking UB). */ public static native int sqlite3_db_config( @NotNull sqlite3 db, int op, int onOff, @Nullable OutputPointer.Int32 out @@ -851,7 +861,6 @@ public final class CApi { return null==db ? null : sqlite3_db_name(db.getNativePointer(), ndx); } - public static native String sqlite3_db_filename( @NotNull sqlite3 db, @NotNull String dbName ); @@ -871,7 +880,7 @@ public final class CApi { public static native String sqlite3_errmsg(@NotNull sqlite3 db); - static native int sqlite3_error_offset(@NotNull long ptrToDb); + private static native int sqlite3_error_offset(@NotNull long ptrToDb); /** Note that the returned byte offset values assume UTF-8-encoded @@ -885,17 +894,17 @@ public final class CApi { public static native String sqlite3_expanded_sql(@NotNull sqlite3_stmt stmt); - static native int sqlite3_extended_errcode(@NotNull long ptrToDb); + private static native int sqlite3_extended_errcode(@NotNull long ptrToDb); public static int sqlite3_extended_errcode(@NotNull sqlite3 db){ return sqlite3_extended_errcode(db.getNativePointer()); } - public static native boolean sqlite3_extended_result_codes( - @NotNull sqlite3 db, boolean onoff + public static native int sqlite3_extended_result_codes( + @NotNull sqlite3 db, boolean on ); - static native boolean sqlite3_get_autocommit(@NotNull long ptrToDb); + private static native boolean sqlite3_get_autocommit(@NotNull long ptrToDb); public static boolean sqlite3_get_autocommit(@NotNull sqlite3 db){ return sqlite3_get_autocommit(db.getNativePointer()); @@ -905,7 +914,7 @@ public final class CApi { @NotNull sqlite3_context cx, int n ); - static native int sqlite3_finalize(long ptrToStmt); + private static native int sqlite3_finalize(long ptrToStmt); public static int sqlite3_finalize(@NotNull sqlite3_stmt stmt){ return null==stmt ? 0 : sqlite3_finalize(stmt.clearNativePointer()); @@ -1199,26 +1208,26 @@ public final class CApi { */ public static int sqlite3_prepare_multi( @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, - int preFlags, + int prepFlags, @NotNull PrepareMultiCallback p){ final OutputPointer.Int32 oTail = new OutputPointer.Int32(); int pos = 0, n = 1; byte[] sqlChunk = sqlUtf8; int rc = 0; final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); - while(0==rc && pos 0){ + if( pos>0 ){ sqlChunk = Arrays.copyOfRange(sqlChunk, pos, sqlChunk.length); } if( 0==sqlChunk.length ) break; - rc = sqlite3_prepare_v3(db, sqlChunk, preFlags, outStmt, oTail); + rc = sqlite3_prepare_v3(db, sqlChunk, prepFlags, outStmt, oTail); if( 0!=rc ) break; pos = oTail.value; stmt = outStmt.take(); - if( null == stmt ){ - // empty statement was parsed. + if( null==stmt ){ + // empty statement (whitespace/comments) continue; } rc = p.call(stmt); @@ -1277,7 +1286,7 @@ public final class CApi { return sqlite3_prepare_multi(db, sql, 0, p); } - static native int sqlite3_preupdate_blobwrite(@NotNull long ptrToDb); + private static native int sqlite3_preupdate_blobwrite(@NotNull long ptrToDb); /** If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this @@ -1288,7 +1297,7 @@ public final class CApi { return sqlite3_preupdate_blobwrite(db.getNativePointer()); } - static native int sqlite3_preupdate_count(@NotNull long ptrToDb); + private static native int sqlite3_preupdate_count(@NotNull long ptrToDb); /** If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this @@ -1299,7 +1308,7 @@ public final class CApi { return sqlite3_preupdate_count(db.getNativePointer()); } - static native int sqlite3_preupdate_depth(@NotNull long ptrToDb); + private static native int sqlite3_preupdate_depth(@NotNull long ptrToDb); /** If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this @@ -1310,7 +1319,7 @@ public final class CApi { return sqlite3_preupdate_depth(db.getNativePointer()); } - static native PreupdateHookCallback sqlite3_preupdate_hook( + private static native PreupdateHookCallback sqlite3_preupdate_hook( @NotNull long ptrToDb, @Nullable PreupdateHookCallback hook ); @@ -1325,7 +1334,7 @@ public final class CApi { return sqlite3_preupdate_hook(db.getNativePointer(), hook); } - static native int sqlite3_preupdate_new(@NotNull long ptrToDb, int col, + private static native int sqlite3_preupdate_new(@NotNull long ptrToDb, int col, @NotNull OutputPointer.sqlite3_value out); /** @@ -1348,7 +1357,7 @@ public final class CApi { return out.take(); } - static native int sqlite3_preupdate_old(@NotNull long ptrToDb, int col, + private static native int sqlite3_preupdate_old(@NotNull long ptrToDb, int col, @NotNull OutputPointer.sqlite3_value out); /** @@ -1399,7 +1408,7 @@ public final class CApi { results in the C-level sqlite3_result_error() being called with a complaint about the invalid argument. */ - static native void sqlite3_result_error( + private static native void sqlite3_result_error( @NotNull sqlite3_context cx, @NotNull byte[] msg, int eTextRep ); @@ -1471,9 +1480,6 @@ public final class CApi { cross-language semantic mismatch and (B) Java doesn't need that argument for its intended purpose (type safety). -

Note that there is no sqlite3_column_java_object(), as the - C-level API has no sqlite3_column_pointer() to proxy. - @see #sqlite3_value_java_object @see #sqlite3_bind_java_object */ @@ -1676,7 +1682,7 @@ public final class CApi { } } - static native RollbackHookCallback sqlite3_rollback_hook( + private static native RollbackHookCallback sqlite3_rollback_hook( @NotNull long ptrToDb, @Nullable RollbackHookCallback hook ); @@ -1728,20 +1734,24 @@ public final class CApi { @NotNull OutputPointer.Int64 pHighwater, boolean reset ); - public static native int sqlite3_step(@NotNull sqlite3_stmt stmt); + private static native int sqlite3_step(@NotNull long ptrToStmt); + + public static int sqlite3_step(@NotNull sqlite3_stmt stmt){ + return null==stmt ? SQLITE_MISUSE : sqlite3_step(stmt.getNativePointer()); + } public static native boolean sqlite3_stmt_busy(@NotNull sqlite3_stmt stmt); - static native int sqlite3_stmt_explain(@NotNull long ptrToStmt, int op); + private static native int sqlite3_stmt_explain(@NotNull long ptrToStmt, int op); public static int sqlite3_stmt_explain(@NotNull sqlite3_stmt stmt, int op){ - return sqlite3_stmt_explain(stmt.getNativePointer(), op); + return null==stmt ? SQLITE_MISUSE : sqlite3_stmt_explain(stmt.getNativePointer(), op); } - static native int sqlite3_stmt_isexplain(@NotNull long ptrToStmt); + private static native int sqlite3_stmt_isexplain(@NotNull long ptrToStmt); public static int sqlite3_stmt_isexplain(@NotNull sqlite3_stmt stmt){ - return sqlite3_stmt_isexplain(stmt.getNativePointer()); + return null==stmt ? 0 : sqlite3_stmt_isexplain(stmt.getNativePointer()); } public static native boolean sqlite3_stmt_readonly(@NotNull sqlite3_stmt stmt); @@ -1762,7 +1772,7 @@ public final class CApi { signature is the public-facing one. */ private static native int sqlite3_strglob( - @NotNull byte[] glob, @NotNull byte[] nullTerminatedUtf8 + @NotNull byte[] glob, @NotNull byte[] nulTerminatedUtf8 ); public static int sqlite3_strglob( @@ -1776,7 +1786,7 @@ public final class CApi { The LIKE counterpart of the private sqlite3_strglob() method. */ private static native int sqlite3_strlike( - @NotNull byte[] glob, @NotNull byte[] nullTerminatedUtf8, + @NotNull byte[] glob, @NotNull byte[] nulTerminatedUtf8, int escChar ); @@ -1788,7 +1798,7 @@ public final class CApi { (int)escChar); } - static native int sqlite3_system_errno(@NotNull long ptrToDb); + private static native int sqlite3_system_errno(@NotNull long ptrToDb); public static int sqlite3_system_errno(@NotNull sqlite3 db){ return sqlite3_system_errno(db.getNativePointer()); @@ -1834,13 +1844,13 @@ public final class CApi { public static native int sqlite3_threadsafe(); - static native int sqlite3_total_changes(@NotNull long ptrToDb); + private static native int sqlite3_total_changes(@NotNull long ptrToDb); public static int sqlite3_total_changes(@NotNull sqlite3 db){ return sqlite3_total_changes(db.getNativePointer()); } - static native long sqlite3_total_changes64(@NotNull long ptrToDb); + private static native long sqlite3_total_changes64(@NotNull long ptrToDb); public static long sqlite3_total_changes64(@NotNull sqlite3 db){ return sqlite3_total_changes64(db.getNativePointer()); @@ -1863,7 +1873,7 @@ public final class CApi { @NotNull sqlite3 db, @Nullable String zSchema ); - static native UpdateHookCallback sqlite3_update_hook( + private static native UpdateHookCallback sqlite3_update_hook( @NotNull long ptrToDb, @Nullable UpdateHookCallback hook ); @@ -1883,67 +1893,67 @@ public final class CApi { sqlite3_create_function(). */ - static native byte[] sqlite3_value_blob(@NotNull long ptrToValue); + private static native byte[] sqlite3_value_blob(@NotNull long ptrToValue); public static byte[] sqlite3_value_blob(@NotNull sqlite3_value v){ return sqlite3_value_blob(v.getNativePointer()); } - static native int sqlite3_value_bytes(@NotNull long ptrToValue); + private static native int sqlite3_value_bytes(@NotNull long ptrToValue); public static int sqlite3_value_bytes(@NotNull sqlite3_value v){ return sqlite3_value_bytes(v.getNativePointer()); } - static native int sqlite3_value_bytes16(@NotNull long ptrToValue); + private static native int sqlite3_value_bytes16(@NotNull long ptrToValue); public static int sqlite3_value_bytes16(@NotNull sqlite3_value v){ return sqlite3_value_bytes16(v.getNativePointer()); } - static native double sqlite3_value_double(@NotNull long ptrToValue); + private static native double sqlite3_value_double(@NotNull long ptrToValue); public static double sqlite3_value_double(@NotNull sqlite3_value v){ return sqlite3_value_double(v.getNativePointer()); } - static native sqlite3_value sqlite3_value_dup(@NotNull long ptrToValue); + private static native sqlite3_value sqlite3_value_dup(@NotNull long ptrToValue); public static sqlite3_value sqlite3_value_dup(@NotNull sqlite3_value v){ return sqlite3_value_dup(v.getNativePointer()); } - static native int sqlite3_value_encoding(@NotNull long ptrToValue); + private static native int sqlite3_value_encoding(@NotNull long ptrToValue); public static int sqlite3_value_encoding(@NotNull sqlite3_value v){ return sqlite3_value_encoding(v.getNativePointer()); } - static native void sqlite3_value_free(@Nullable long ptrToValue); + private static native void sqlite3_value_free(@Nullable long ptrToValue); public static void sqlite3_value_free(@Nullable sqlite3_value v){ - sqlite3_value_free(v.getNativePointer()); + if( null!=v ) sqlite3_value_free(v.clearNativePointer()); } - static native boolean sqlite3_value_frombind(@NotNull long ptrToValue); + private static native boolean sqlite3_value_frombind(@NotNull long ptrToValue); public static boolean sqlite3_value_frombind(@NotNull sqlite3_value v){ return sqlite3_value_frombind(v.getNativePointer()); } - static native int sqlite3_value_int(@NotNull long ptrToValue); + private static native int sqlite3_value_int(@NotNull long ptrToValue); public static int sqlite3_value_int(@NotNull sqlite3_value v){ return sqlite3_value_int(v.getNativePointer()); } - static native long sqlite3_value_int64(@NotNull long ptrToValue); + private static native long sqlite3_value_int64(@NotNull long ptrToValue); public static long sqlite3_value_int64(@NotNull sqlite3_value v){ return sqlite3_value_int64(v.getNativePointer()); } - static native Object sqlite3_value_java_object(@NotNull long ptrToValue); + private static native Object sqlite3_value_java_object(@NotNull long ptrToValue); /** If the given value was set using {@link @@ -1969,25 +1979,25 @@ public final class CApi { return type.isInstance(o) ? (T)o : null; } - static native int sqlite3_value_nochange(@NotNull long ptrToValue); + private static native int sqlite3_value_nochange(@NotNull long ptrToValue); public static int sqlite3_value_nochange(@NotNull sqlite3_value v){ return sqlite3_value_nochange(v.getNativePointer()); } - static native int sqlite3_value_numeric_type(@NotNull long ptrToValue); + private static native int sqlite3_value_numeric_type(@NotNull long ptrToValue); public static int sqlite3_value_numeric_type(@NotNull sqlite3_value v){ return sqlite3_value_numeric_type(v.getNativePointer()); } - static native int sqlite3_value_subtype(@NotNull long ptrToValue); + private static native int sqlite3_value_subtype(@NotNull long ptrToValue); public static int sqlite3_value_subtype(@NotNull sqlite3_value v){ return sqlite3_value_subtype(v.getNativePointer()); } - static native byte[] sqlite3_value_text(@NotNull long ptrToValue); + private static native byte[] sqlite3_value_text(@NotNull long ptrToValue); /** Functions identially to the C API, and this note is just to @@ -1999,13 +2009,13 @@ public final class CApi { return sqlite3_value_text(v.getNativePointer()); } - static native String sqlite3_value_text16(@NotNull long ptrToValue); + private static native String sqlite3_value_text16(@NotNull long ptrToValue); public static String sqlite3_value_text16(@NotNull sqlite3_value v){ return sqlite3_value_text16(v.getNativePointer()); } - static native int sqlite3_value_type(@NotNull long ptrToValue); + private static native int sqlite3_value_type(@NotNull long ptrToValue); public static int sqlite3_value_type(@NotNull sqlite3_value v){ return sqlite3_value_type(v.getNativePointer()); @@ -2280,7 +2290,6 @@ public final class CApi { // prepare flags public static final int SQLITE_PREPARE_PERSISTENT = 1; - public static final int SQLITE_PREPARE_NORMALIZE = 2; public static final int SQLITE_PREPARE_NO_VTAB = 4; // result codes @@ -2436,9 +2445,11 @@ public final class CApi { public static final int SQLITE_TXN_WRITE = 2; // udf flags - public static final int SQLITE_DETERMINISTIC = 0x000000800; - public static final int SQLITE_DIRECTONLY = 0x000080000; - public static final int SQLITE_INNOCUOUS = 0x000200000; + public static final int SQLITE_DETERMINISTIC = 0x000000800; + public static final int SQLITE_DIRECTONLY = 0x000080000; + public static final int SQLITE_SUBTYPE = 0x000100000; + public static final int SQLITE_INNOCUOUS = 0x000200000; + public static final int SQLITE_RESULT_SUBTYPE = 0x001000000; // virtual tables public static final int SQLITE_INDEX_SCAN_UNIQUE = 1; diff --git a/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java b/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java index fe61fe5065..ffd7fa94ab 100644 --- a/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java +++ b/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java @@ -21,8 +21,9 @@ public interface CollationNeededCallback extends CallbackProxy { Has the same semantics as the C-level sqlite3_create_collation() callback. -

If it throws, the exception message is passed on to the db and - the exception is suppressed. +

Because the C API has no mechanism for reporting errors + from this callbacks, any exceptions thrown by this callback + are suppressed. */ - int call(sqlite3 db, int eTextRep, String collationName); + void call(sqlite3 db, int eTextRep, String collationName); } diff --git a/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java b/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java index 24373bdf2b..e1e55c78d2 100644 --- a/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java +++ b/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java @@ -19,7 +19,8 @@ package org.sqlite.jni.capi; public interface CommitHookCallback extends CallbackProxy { /** Works as documented for the C-level sqlite3_commit_hook() - callback. Must not throw. + callback. If it throws, the exception is translated into + a db-level error. */ int call(); } diff --git a/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java b/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java index 99d3fb0351..38f7c5613e 100644 --- a/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java +++ b/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java @@ -19,7 +19,8 @@ package org.sqlite.jni.capi; public interface PreupdateHookCallback extends CallbackProxy { /** Must function as described for the C-level sqlite3_preupdate_hook() - callback. + callback. If it throws, the exception is translated to a + db-level error and the exception is suppressed. */ void call(sqlite3 db, int op, String dbName, String dbTable, long iKey1, long iKey2 ); diff --git a/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java b/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java index 5ce17e718a..cf9c4b6e7a 100644 --- a/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java +++ b/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java @@ -18,8 +18,9 @@ package org.sqlite.jni.capi; */ public interface RollbackHookCallback extends CallbackProxy { /** - Works as documented for the C-level sqlite3_rollback_hook() - callback. + Must function as documented for the C-level sqlite3_rollback_hook() + callback. If it throws, the exception is translated into + a db-level error. */ void call(); } diff --git a/ext/jni/src/org/sqlite/jni/capi/Tester1.java b/ext/jni/src/org/sqlite/jni/capi/Tester1.java index 4657185658..3ac58c67d3 100644 --- a/ext/jni/src/org/sqlite/jni/capi/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/capi/Tester1.java @@ -46,7 +46,7 @@ public class Tester1 implements Runnable { //! True to shuffle the order of the tests. private static boolean shuffle = false; //! True to dump the list of to-run tests to stdout. - private static boolean listRunTests = false; + private static int listRunTests = 0; //! True to squelch all out() and outln() output. private static boolean quietMode = false; //! Total number of runTests() calls. @@ -327,7 +327,7 @@ public class Tester1 implements Runnable { rc = sqlite3_prepare_v3(db, "INSERT INTO t2(a) VALUES(1),(2),(3)", - SQLITE_PREPARE_NORMALIZE, outStmt); + 0, outStmt); affirm(0 == rc); stmt = outStmt.get(); affirm(0 != stmt.getNativePointer()); @@ -382,6 +382,15 @@ public class Tester1 implements Runnable { stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); affirm( sqlite3_stmt_readonly(stmt) ); affirm( !sqlite3_stmt_busy(stmt) ); + if( sqlite3_compileoption_used("ENABLE_COLUMN_METADATA") ){ + /* Unlike in native C code, JNI won't trigger an + UnsatisfiedLinkError until these are called (on Linux, at + least). */ + affirm("t".equals(sqlite3_column_table_name(stmt,0))); + affirm("main".equals(sqlite3_column_database_name(stmt,0))); + affirm("a".equals(sqlite3_column_origin_name(stmt,0))); + } + int total2 = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ affirm( sqlite3_stmt_busy(stmt) ); @@ -593,9 +602,9 @@ public class Tester1 implements Runnable { }; final CollationNeededCallback collLoader = new CollationNeededCallback(){ @Override - public int call(sqlite3 dbArg, int eTextRep, String collationName){ + public void call(sqlite3 dbArg, int eTextRep, String collationName){ affirm(dbArg == db/* as opposed to a temporary object*/); - return sqlite3_create_collation(dbArg, "reversi", eTextRep, myCollation); + sqlite3_create_collation(dbArg, "reversi", eTextRep, myCollation); } }; int rc = sqlite3_collation_needed(db, collLoader); @@ -1031,48 +1040,48 @@ public class Tester1 implements Runnable { @SingleThreadOnly /* because threads inherently break this test */ private static void testBusy(){ final String dbName = "_busy-handler.db"; - final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3(); - final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); - - int rc = sqlite3_open(dbName, outDb); - ++metrics.dbOpen; - affirm( 0 == rc ); - final sqlite3 db1 = outDb.get(); - execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)"); - rc = sqlite3_open(dbName, outDb); - ++metrics.dbOpen; - affirm( 0 == rc ); - affirm( outDb.get() != db1 ); - final sqlite3 db2 = outDb.get(); - - affirm( "main".equals( sqlite3_db_name(db1, 0) ) ); - rc = sqlite3_db_config(db1, SQLITE_DBCONFIG_MAINDBNAME, "foo"); - affirm( sqlite3_db_filename(db1, "foo").endsWith(dbName) ); - affirm( "foo".equals( sqlite3_db_name(db1, 0) ) ); - - final ValueHolder xBusyCalled = new ValueHolder<>(0); - BusyHandlerCallback handler = new BusyHandlerCallback(){ - @Override public int call(int n){ - //outln("busy handler #"+n); - return n > 2 ? 0 : ++xBusyCalled.value; - } - }; - rc = sqlite3_busy_handler(db2, handler); - affirm(0 == rc); - - // Force a locked condition... - execSql(db1, "BEGIN EXCLUSIVE"); - rc = sqlite3_prepare_v2(db2, "SELECT * from t", outStmt); - affirm( SQLITE_BUSY == rc); - affirm( null == outStmt.get() ); - affirm( 3 == xBusyCalled.value ); - sqlite3_close_v2(db1); - sqlite3_close_v2(db2); try{ - final java.io.File f = new java.io.File(dbName); - f.delete(); - }catch(Exception e){ - /* ignore */ + final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3(); + final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); + + int rc = sqlite3_open(dbName, outDb); + ++metrics.dbOpen; + affirm( 0 == rc ); + final sqlite3 db1 = outDb.get(); + execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)"); + rc = sqlite3_open(dbName, outDb); + ++metrics.dbOpen; + affirm( 0 == rc ); + affirm( outDb.get() != db1 ); + final sqlite3 db2 = outDb.get(); + + affirm( "main".equals( sqlite3_db_name(db1, 0) ) ); + rc = sqlite3_db_config(db1, SQLITE_DBCONFIG_MAINDBNAME, "foo"); + affirm( sqlite3_db_filename(db1, "foo").endsWith(dbName) ); + affirm( "foo".equals( sqlite3_db_name(db1, 0) ) ); + affirm( SQLITE_MISUSE == sqlite3_db_config(db1, 0, 0, null) ); + + final ValueHolder xBusyCalled = new ValueHolder<>(0); + BusyHandlerCallback handler = new BusyHandlerCallback(){ + @Override public int call(int n){ + //outln("busy handler #"+n); + return n > 2 ? 0 : ++xBusyCalled.value; + } + }; + rc = sqlite3_busy_handler(db2, handler); + affirm(0 == rc); + + // Force a locked condition... + execSql(db1, "BEGIN EXCLUSIVE"); + rc = sqlite3_prepare_v2(db2, "SELECT * from t", outStmt); + affirm( SQLITE_BUSY == rc); + affirm( null == outStmt.get() ); + affirm( 3 == xBusyCalled.value ); + sqlite3_close_v2(db1); + sqlite3_close_v2(db2); + }finally{ + try{(new java.io.File(dbName)).delete();} + catch(Exception e){/* ignore */} } } @@ -1096,6 +1105,7 @@ public class Tester1 implements Runnable { private void testCommitHook(){ final sqlite3 db = createNewDb(); + sqlite3_extended_result_codes(db, true); final ValueHolder counter = new ValueHolder<>(0); final ValueHolder hookResult = new ValueHolder<>(0); final CommitHookCallback theHook = new CommitHookCallback(){ @@ -1138,7 +1148,7 @@ public class Tester1 implements Runnable { affirm( 5 == counter.value ); hookResult.value = SQLITE_ERROR; int rc = execSql(db, false, "BEGIN; update t set a='j' where a='i'; COMMIT;"); - affirm( SQLITE_CONSTRAINT == rc ); + affirm( SQLITE_CONSTRAINT_COMMITHOOK == rc ); affirm( 6 == counter.value ); sqlite3_close_v2(db); } @@ -1357,6 +1367,9 @@ public class Tester1 implements Runnable { authRc.value = SQLITE_DENY; int rc = execSql(db, false, "UPDATE t SET a=2"); affirm( SQLITE_AUTH==rc ); + sqlite3_set_authorizer(db, null); + rc = execSql(db, false, "UPDATE t SET a=2"); + affirm( 0==rc ); // TODO: expand these tests considerably sqlite3_close(db); } @@ -1418,7 +1431,7 @@ public class Tester1 implements Runnable { val.value = 0; final AutoExtensionCallback ax2 = new AutoExtensionCallback(){ - @Override public synchronized int call(sqlite3 db){ + @Override public int call(sqlite3 db){ ++val.value; return 0; } @@ -1629,7 +1642,7 @@ public class Tester1 implements Runnable { sqlite3_finalize(stmt); b = sqlite3_blob_open(db, "main", "t", "a", - sqlite3_last_insert_rowid(db), 1); + sqlite3_last_insert_rowid(db), 0); affirm( null!=b ); rc = sqlite3_blob_reopen(b, 2); affirm( 0==rc ); @@ -1701,7 +1714,7 @@ public class Tester1 implements Runnable { mlist = new ArrayList<>( testMethods.subList(0, testMethods.size()) ); java.util.Collections.shuffle(mlist); } - if( listRunTests ){ + if( (!fromThread && listRunTests>0) || listRunTests>1 ){ synchronized(this.getClass()){ if( !fromThread ){ out("Initial test"," list: "); @@ -1763,8 +1776,11 @@ public class Tester1 implements Runnable { -naps: sleep small random intervals between tests in order to add some chaos for cross-thread contention. + -list-tests: outputs the list of tests being run, minus some - which are hard-coded. This is noisy in multi-threaded mode. + which are hard-coded. In multi-threaded mode, use this twice to + to emit the list run by each thread (which may differ from the initial + list, in particular if -shuffle is used). -fail: forces an exception to be thrown during the test run. Use with -shuffle to make its appearance unpredictable. @@ -1793,7 +1809,7 @@ public class Tester1 implements Runnable { }else if(arg.equals("shuffle")){ shuffle = true; }else if(arg.equals("list-tests")){ - listRunTests = true; + ++listRunTests; }else if(arg.equals("fail")){ forceFail = true; }else if(arg.equals("sqllog")){ @@ -1904,6 +1920,7 @@ public class Tester1 implements Runnable { sqlite3_libversion_number(),"\n", sqlite3_libversion(),"\n",SQLITE_SOURCE_ID,"\n", "SQLITE_THREADSAFE=",sqlite3_threadsafe()); + outln("JVM NIO support? ",sqlite3_jni_supports_nio() ? "YES" : "NO"); final boolean showLoopCount = (nRepeat>1 && nThread>1); if( showLoopCount ){ outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each."); diff --git a/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java b/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java index 33d72a5dd2..e3d491f67e 100644 --- a/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java +++ b/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java @@ -19,7 +19,8 @@ package org.sqlite.jni.capi; public interface UpdateHookCallback extends CallbackProxy { /** Must function as described for the C-level sqlite3_update_hook() - callback. + callback. If it throws, the exception is translated into + a db-level error. */ void call(int opId, String dbName, String tableName, long rowId); } diff --git a/ext/jni/src/org/sqlite/jni/capi/sqlite3.java b/ext/jni/src/org/sqlite/jni/capi/sqlite3.java index 901317f0ef..cc6f2e6e8d 100644 --- a/ext/jni/src/org/sqlite/jni/capi/sqlite3.java +++ b/ext/jni/src/org/sqlite/jni/capi/sqlite3.java @@ -38,6 +38,6 @@ public final class sqlite3 extends NativePointerHolder } @Override public void close(){ - CApi.sqlite3_close_v2(this.clearNativePointer()); + CApi.sqlite3_close_v2(this); } } diff --git a/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java b/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java index 1b96c18b06..bdc0200af4 100644 --- a/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java +++ b/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java @@ -25,7 +25,6 @@ public final class sqlite3_blob extends NativePointerHolder private sqlite3_blob(){} @Override public void close(){ - CApi.sqlite3_blob_close(this.clearNativePointer()); + CApi.sqlite3_blob_close(this); } - } diff --git a/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java b/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java index 3b8b71f8a5..564891c727 100644 --- a/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java +++ b/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java @@ -25,6 +25,6 @@ public final class sqlite3_stmt extends NativePointerHolder private sqlite3_stmt(){} @Override public void close(){ - CApi.sqlite3_finalize(this.clearNativePointer()); + CApi.sqlite3_finalize(this); } } diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java b/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java index 6a38d4b530..fc63b53542 100644 --- a/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java +++ b/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java @@ -12,10 +12,6 @@ ** This file is part of the wrapper1 interface for sqlite3. */ package org.sqlite.jni.wrapper1; -import org.sqlite.jni.capi.CApi; -import org.sqlite.jni.annotation.*; -import org.sqlite.jni.capi.sqlite3_context; -import org.sqlite.jni.capi.sqlite3_value; /** EXPERIMENTAL/INCOMPLETE/UNTESTED diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java b/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java index 5bcb3bd5fa..dcfc2ebebd 100644 --- a/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java +++ b/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java @@ -25,13 +25,10 @@ public interface SqlFunction { public static final int DETERMINISTIC = CApi.SQLITE_DETERMINISTIC; public static final int INNOCUOUS = CApi.SQLITE_INNOCUOUS; public static final int DIRECTONLY = CApi.SQLITE_DIRECTONLY; + public static final int SUBTYPE = CApi.SQLITE_SUBTYPE; + public static final int RESULT_SUBTYPE = CApi.SQLITE_RESULT_SUBTYPE; public static final int UTF8 = CApi.SQLITE_UTF8; public static final int UTF16 = CApi.SQLITE_UTF16; - // /** - // For Window functions only and is not currently bound because - // doing so may require exposing sqlite3_value for effective use. - // */ - // public static final int SUBTYPE = CApi.SQLITE_SUBTYPE; /** The Arguments type is an abstraction on top of the lower-level @@ -56,7 +53,7 @@ public interface SqlFunction { */ Arguments(sqlite3_context cx, sqlite3_value args[]){ this.cx = cx; - this.args = args==null ? new sqlite3_value[0] : args;; + this.args = args==null ? new sqlite3_value[0] : args; this.length = this.args.length; } @@ -76,6 +73,16 @@ public interface SqlFunction { //! Returns the underlying sqlite3_context for these arguments. sqlite3_context getContext(){return cx;} + /** + Returns the Sqlite (db) object associated with this UDF call, + or null if the UDF is somehow called without such an object or + the db has been closed in an untimely manner (e.g. closed by a + UDF call). + */ + public Sqlite getDb(){ + return Sqlite.fromNative( CApi.sqlite3_context_db_handle(cx) ); + } + public int getArgCount(){ return args.length; } public int getInt(int argNdx){return CApi.sqlite3_value_int(valueAt(argNdx));} @@ -107,6 +114,10 @@ public interface SqlFunction { public void resultErrorCode(int rc){CApi.sqlite3_result_error_code(cx, rc);} public void resultObject(Object o){CApi.sqlite3_result_java_object(cx, o);} public void resultNull(){CApi.sqlite3_result_null(cx);} + /** + Analog to sqlite3_result_value(), using the Value object at the + given argument index. + */ public void resultArg(int argNdx){CApi.sqlite3_result_value(cx, valueAt(argNdx));} public void resultSubtype(int subtype){CApi.sqlite3_result_subtype(cx, subtype);} public void resultZeroBlob(long n){ @@ -153,7 +164,7 @@ public interface SqlFunction { } /** - Wrapper for a single SqlFunction argument. Primarily intended + Represents a single SqlFunction argument. Primarily intended for use with the Arguments class's Iterable interface. */ public final static class Arg { diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java b/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java index 907755d99a..e61b7e59dd 100644 --- a/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java +++ b/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java @@ -13,10 +13,11 @@ */ package org.sqlite.jni.wrapper1; import java.nio.charset.StandardCharsets; -import static org.sqlite.jni.capi.CApi.*; import org.sqlite.jni.capi.CApi; import org.sqlite.jni.capi.sqlite3; import org.sqlite.jni.capi.sqlite3_stmt; +import org.sqlite.jni.capi.sqlite3_backup; +import org.sqlite.jni.capi.sqlite3_blob; import org.sqlite.jni.capi.OutputPointer; /** @@ -29,9 +30,115 @@ import org.sqlite.jni.capi.OutputPointer; public final class Sqlite implements AutoCloseable { private sqlite3 db; + public static final int OK = CApi.SQLITE_OK; + public static final int ERROR = CApi.SQLITE_ERROR; + public static final int INTERNAL = CApi.SQLITE_INTERNAL; + public static final int PERM = CApi.SQLITE_PERM; + public static final int ABORT = CApi.SQLITE_ABORT; + public static final int BUSY = CApi.SQLITE_BUSY; + public static final int LOCKED = CApi.SQLITE_LOCKED; + public static final int NOMEM = CApi.SQLITE_NOMEM; + public static final int READONLY = CApi.SQLITE_READONLY; + public static final int INTERRUPT = CApi.SQLITE_INTERRUPT; + public static final int IOERR = CApi.SQLITE_IOERR; + public static final int CORRUPT = CApi.SQLITE_CORRUPT; + public static final int NOTFOUND = CApi.SQLITE_NOTFOUND; + public static final int FULL = CApi.SQLITE_FULL; + public static final int CANTOPEN = CApi.SQLITE_CANTOPEN; + public static final int PROTOCOL = CApi.SQLITE_PROTOCOL; + public static final int EMPTY = CApi.SQLITE_EMPTY; + public static final int SCHEMA = CApi.SQLITE_SCHEMA; + public static final int TOOBIG = CApi.SQLITE_TOOBIG; + public static final int CONSTRAINT = CApi. SQLITE_CONSTRAINT; + public static final int MISMATCH = CApi.SQLITE_MISMATCH; + public static final int MISUSE = CApi.SQLITE_MISUSE; + public static final int NOLFS = CApi.SQLITE_NOLFS; + public static final int AUTH = CApi.SQLITE_AUTH; + public static final int FORMAT = CApi.SQLITE_FORMAT; + public static final int RANGE = CApi.SQLITE_RANGE; + public static final int NOTADB = CApi.SQLITE_NOTADB; + public static final int NOTICE = CApi.SQLITE_NOTICE; + public static final int WARNING = CApi.SQLITE_WARNING; + public static final int ROW = CApi.SQLITE_ROW; + public static final int DONE = CApi.SQLITE_DONE; + public static final int ERROR_MISSING_COLLSEQ = CApi.SQLITE_ERROR_MISSING_COLLSEQ; + public static final int ERROR_RETRY = CApi.SQLITE_ERROR_RETRY; + public static final int ERROR_SNAPSHOT = CApi.SQLITE_ERROR_SNAPSHOT; + public static final int IOERR_READ = CApi.SQLITE_IOERR_READ; + public static final int IOERR_SHORT_READ = CApi.SQLITE_IOERR_SHORT_READ; + public static final int IOERR_WRITE = CApi.SQLITE_IOERR_WRITE; + public static final int IOERR_FSYNC = CApi.SQLITE_IOERR_FSYNC; + public static final int IOERR_DIR_FSYNC = CApi.SQLITE_IOERR_DIR_FSYNC; + public static final int IOERR_TRUNCATE = CApi.SQLITE_IOERR_TRUNCATE; + public static final int IOERR_FSTAT = CApi.SQLITE_IOERR_FSTAT; + public static final int IOERR_UNLOCK = CApi.SQLITE_IOERR_UNLOCK; + public static final int IOERR_RDLOCK = CApi.SQLITE_IOERR_RDLOCK; + public static final int IOERR_DELETE = CApi.SQLITE_IOERR_DELETE; + public static final int IOERR_BLOCKED = CApi.SQLITE_IOERR_BLOCKED; + public static final int IOERR_NOMEM = CApi.SQLITE_IOERR_NOMEM; + public static final int IOERR_ACCESS = CApi.SQLITE_IOERR_ACCESS; + public static final int IOERR_CHECKRESERVEDLOCK = CApi.SQLITE_IOERR_CHECKRESERVEDLOCK; + public static final int IOERR_LOCK = CApi.SQLITE_IOERR_LOCK; + public static final int IOERR_CLOSE = CApi.SQLITE_IOERR_CLOSE; + public static final int IOERR_DIR_CLOSE = CApi.SQLITE_IOERR_DIR_CLOSE; + public static final int IOERR_SHMOPEN = CApi.SQLITE_IOERR_SHMOPEN; + public static final int IOERR_SHMSIZE = CApi.SQLITE_IOERR_SHMSIZE; + public static final int IOERR_SHMLOCK = CApi.SQLITE_IOERR_SHMLOCK; + public static final int IOERR_SHMMAP = CApi.SQLITE_IOERR_SHMMAP; + public static final int IOERR_SEEK = CApi.SQLITE_IOERR_SEEK; + public static final int IOERR_DELETE_NOENT = CApi.SQLITE_IOERR_DELETE_NOENT; + public static final int IOERR_MMAP = CApi.SQLITE_IOERR_MMAP; + public static final int IOERR_GETTEMPPATH = CApi.SQLITE_IOERR_GETTEMPPATH; + public static final int IOERR_CONVPATH = CApi.SQLITE_IOERR_CONVPATH; + public static final int IOERR_VNODE = CApi.SQLITE_IOERR_VNODE; + public static final int IOERR_AUTH = CApi.SQLITE_IOERR_AUTH; + public static final int IOERR_BEGIN_ATOMIC = CApi.SQLITE_IOERR_BEGIN_ATOMIC; + public static final int IOERR_COMMIT_ATOMIC = CApi.SQLITE_IOERR_COMMIT_ATOMIC; + public static final int IOERR_ROLLBACK_ATOMIC = CApi.SQLITE_IOERR_ROLLBACK_ATOMIC; + public static final int IOERR_DATA = CApi.SQLITE_IOERR_DATA; + public static final int IOERR_CORRUPTFS = CApi.SQLITE_IOERR_CORRUPTFS; + public static final int LOCKED_SHAREDCACHE = CApi.SQLITE_LOCKED_SHAREDCACHE; + public static final int LOCKED_VTAB = CApi.SQLITE_LOCKED_VTAB; + public static final int BUSY_RECOVERY = CApi.SQLITE_BUSY_RECOVERY; + public static final int BUSY_SNAPSHOT = CApi.SQLITE_BUSY_SNAPSHOT; + public static final int BUSY_TIMEOUT = CApi.SQLITE_BUSY_TIMEOUT; + public static final int CANTOPEN_NOTEMPDIR = CApi.SQLITE_CANTOPEN_NOTEMPDIR; + public static final int CANTOPEN_ISDIR = CApi.SQLITE_CANTOPEN_ISDIR; + public static final int CANTOPEN_FULLPATH = CApi.SQLITE_CANTOPEN_FULLPATH; + public static final int CANTOPEN_CONVPATH = CApi.SQLITE_CANTOPEN_CONVPATH; + public static final int CANTOPEN_SYMLINK = CApi.SQLITE_CANTOPEN_SYMLINK; + public static final int CORRUPT_VTAB = CApi.SQLITE_CORRUPT_VTAB; + public static final int CORRUPT_SEQUENCE = CApi.SQLITE_CORRUPT_SEQUENCE; + public static final int CORRUPT_INDEX = CApi.SQLITE_CORRUPT_INDEX; + public static final int READONLY_RECOVERY = CApi.SQLITE_READONLY_RECOVERY; + public static final int READONLY_CANTLOCK = CApi.SQLITE_READONLY_CANTLOCK; + public static final int READONLY_ROLLBACK = CApi.SQLITE_READONLY_ROLLBACK; + public static final int READONLY_DBMOVED = CApi.SQLITE_READONLY_DBMOVED; + public static final int READONLY_CANTINIT = CApi.SQLITE_READONLY_CANTINIT; + public static final int READONLY_DIRECTORY = CApi.SQLITE_READONLY_DIRECTORY; + public static final int ABORT_ROLLBACK = CApi.SQLITE_ABORT_ROLLBACK; + public static final int CONSTRAINT_CHECK = CApi.SQLITE_CONSTRAINT_CHECK; + public static final int CONSTRAINT_COMMITHOOK = CApi.SQLITE_CONSTRAINT_COMMITHOOK; + public static final int CONSTRAINT_FOREIGNKEY = CApi.SQLITE_CONSTRAINT_FOREIGNKEY; + public static final int CONSTRAINT_FUNCTION = CApi.SQLITE_CONSTRAINT_FUNCTION; + public static final int CONSTRAINT_NOTNULL = CApi.SQLITE_CONSTRAINT_NOTNULL; + public static final int CONSTRAINT_PRIMARYKEY = CApi.SQLITE_CONSTRAINT_PRIMARYKEY; + public static final int CONSTRAINT_TRIGGER = CApi.SQLITE_CONSTRAINT_TRIGGER; + public static final int CONSTRAINT_UNIQUE = CApi.SQLITE_CONSTRAINT_UNIQUE; + public static final int CONSTRAINT_VTAB = CApi.SQLITE_CONSTRAINT_VTAB; + public static final int CONSTRAINT_ROWID = CApi.SQLITE_CONSTRAINT_ROWID; + public static final int CONSTRAINT_PINNED = CApi.SQLITE_CONSTRAINT_PINNED; + public static final int CONSTRAINT_DATATYPE = CApi.SQLITE_CONSTRAINT_DATATYPE; + public static final int NOTICE_RECOVER_WAL = CApi.SQLITE_NOTICE_RECOVER_WAL; + public static final int NOTICE_RECOVER_ROLLBACK = CApi.SQLITE_NOTICE_RECOVER_ROLLBACK; + public static final int WARNING_AUTOINDEX = CApi.SQLITE_WARNING_AUTOINDEX; + public static final int AUTH_USER = CApi.SQLITE_AUTH_USER; + public static final int OK_LOAD_PERMANENTLY = CApi.SQLITE_OK_LOAD_PERMANENTLY; + public static final int OPEN_READWRITE = CApi.SQLITE_OPEN_READWRITE; public static final int OPEN_CREATE = CApi.SQLITE_OPEN_CREATE; public static final int OPEN_EXRESCODE = CApi.SQLITE_OPEN_EXRESCODE; + public static final int TXN_NONE = CApi.SQLITE_TXN_NONE; public static final int TXN_READ = CApi.SQLITE_TXN_READ; public static final int TXN_WRITE = CApi.SQLITE_TXN_WRITE; @@ -44,6 +151,20 @@ public final class Sqlite implements AutoCloseable { public static final int STATUS_PAGECACHE_SIZE = CApi.SQLITE_STATUS_PAGECACHE_SIZE; public static final int STATUS_MALLOC_COUNT = CApi.SQLITE_STATUS_MALLOC_COUNT; + public static final int DBSTATUS_LOOKASIDE_USED = CApi.SQLITE_DBSTATUS_LOOKASIDE_USED; + public static final int DBSTATUS_CACHE_USED = CApi.SQLITE_DBSTATUS_CACHE_USED; + public static final int DBSTATUS_SCHEMA_USED = CApi.SQLITE_DBSTATUS_SCHEMA_USED; + public static final int DBSTATUS_STMT_USED = CApi.SQLITE_DBSTATUS_STMT_USED; + public static final int DBSTATUS_LOOKASIDE_HIT = CApi.SQLITE_DBSTATUS_LOOKASIDE_HIT; + public static final int DBSTATUS_LOOKASIDE_MISS_SIZE = CApi.SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE; + public static final int DBSTATUS_LOOKASIDE_MISS_FULL = CApi.SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL; + public static final int DBSTATUS_CACHE_HIT = CApi.SQLITE_DBSTATUS_CACHE_HIT; + public static final int DBSTATUS_CACHE_MISS = CApi.SQLITE_DBSTATUS_CACHE_MISS; + public static final int DBSTATUS_CACHE_WRITE = CApi.SQLITE_DBSTATUS_CACHE_WRITE; + public static final int DBSTATUS_DEFERRED_FKS = CApi.SQLITE_DBSTATUS_DEFERRED_FKS; + public static final int DBSTATUS_CACHE_USED_SHARED = CApi.SQLITE_DBSTATUS_CACHE_USED_SHARED; + public static final int DBSTATUS_CACHE_SPILL = CApi.SQLITE_DBSTATUS_CACHE_SPILL; + public static final int LIMIT_LENGTH = CApi.SQLITE_LIMIT_LENGTH; public static final int LIMIT_SQL_LENGTH = CApi.SQLITE_LIMIT_SQL_LENGTH; public static final int LIMIT_COLUMN = CApi.SQLITE_LIMIT_COLUMN; @@ -58,14 +179,95 @@ public final class Sqlite implements AutoCloseable { public static final int LIMIT_WORKER_THREADS = CApi.SQLITE_LIMIT_WORKER_THREADS; public static final int PREPARE_PERSISTENT = CApi.SQLITE_PREPARE_PERSISTENT; - public static final int PREPARE_NORMALIZE = CApi.SQLITE_PREPARE_NORMALIZE; public static final int PREPARE_NO_VTAB = CApi.SQLITE_PREPARE_NO_VTAB; + public static final int TRACE_STMT = CApi.SQLITE_TRACE_STMT; + public static final int TRACE_PROFILE = CApi.SQLITE_TRACE_PROFILE; + public static final int TRACE_ROW = CApi.SQLITE_TRACE_ROW; + public static final int TRACE_CLOSE = CApi.SQLITE_TRACE_CLOSE; + public static final int TRACE_ALL = TRACE_STMT | TRACE_PROFILE | TRACE_ROW | TRACE_CLOSE; + + public static final int DBCONFIG_ENABLE_FKEY = CApi.SQLITE_DBCONFIG_ENABLE_FKEY; + public static final int DBCONFIG_ENABLE_TRIGGER = CApi.SQLITE_DBCONFIG_ENABLE_TRIGGER; + public static final int DBCONFIG_ENABLE_FTS3_TOKENIZER = CApi.SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER; + public static final int DBCONFIG_ENABLE_LOAD_EXTENSION = CApi.SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION; + public static final int DBCONFIG_NO_CKPT_ON_CLOSE = CApi.SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE; + public static final int DBCONFIG_ENABLE_QPSG = CApi.SQLITE_DBCONFIG_ENABLE_QPSG; + public static final int DBCONFIG_TRIGGER_EQP = CApi.SQLITE_DBCONFIG_TRIGGER_EQP; + public static final int DBCONFIG_RESET_DATABASE = CApi.SQLITE_DBCONFIG_RESET_DATABASE; + public static final int DBCONFIG_DEFENSIVE = CApi.SQLITE_DBCONFIG_DEFENSIVE; + public static final int DBCONFIG_WRITABLE_SCHEMA = CApi.SQLITE_DBCONFIG_WRITABLE_SCHEMA; + public static final int DBCONFIG_LEGACY_ALTER_TABLE = CApi.SQLITE_DBCONFIG_LEGACY_ALTER_TABLE; + public static final int DBCONFIG_DQS_DML = CApi.SQLITE_DBCONFIG_DQS_DML; + public static final int DBCONFIG_DQS_DDL = CApi.SQLITE_DBCONFIG_DQS_DDL; + public static final int DBCONFIG_ENABLE_VIEW = CApi.SQLITE_DBCONFIG_ENABLE_VIEW; + public static final int DBCONFIG_LEGACY_FILE_FORMAT = CApi.SQLITE_DBCONFIG_LEGACY_FILE_FORMAT; + public static final int DBCONFIG_TRUSTED_SCHEMA = CApi.SQLITE_DBCONFIG_TRUSTED_SCHEMA; + public static final int DBCONFIG_STMT_SCANSTATUS = CApi.SQLITE_DBCONFIG_STMT_SCANSTATUS; + public static final int DBCONFIG_REVERSE_SCANORDER = CApi.SQLITE_DBCONFIG_REVERSE_SCANORDER; + + public static final int UTF8 = CApi.SQLITE_UTF8; + public static final int UTF16 = CApi.SQLITE_UTF16; + public static final int UTF16LE = CApi.SQLITE_UTF16LE; + public static final int UTF16BE = CApi.SQLITE_UTF16BE; + /* We elide the UTF16_ALIGNED from this interface because it + is irrelevant for the Java interface. */ + + public static final int DENY = CApi.SQLITE_DENY; + public static final int IGNORE = CApi.SQLITE_IGNORE; + public static final int CREATE_INDEX = CApi.SQLITE_CREATE_INDEX; + public static final int CREATE_TABLE = CApi.SQLITE_CREATE_TABLE; + public static final int CREATE_TEMP_INDEX = CApi.SQLITE_CREATE_TEMP_INDEX; + public static final int CREATE_TEMP_TABLE = CApi.SQLITE_CREATE_TEMP_TABLE; + public static final int CREATE_TEMP_TRIGGER = CApi.SQLITE_CREATE_TEMP_TRIGGER; + public static final int CREATE_TEMP_VIEW = CApi.SQLITE_CREATE_TEMP_VIEW; + public static final int CREATE_TRIGGER = CApi.SQLITE_CREATE_TRIGGER; + public static final int CREATE_VIEW = CApi.SQLITE_CREATE_VIEW; + public static final int DELETE = CApi.SQLITE_DELETE; + public static final int DROP_INDEX = CApi.SQLITE_DROP_INDEX; + public static final int DROP_TABLE = CApi.SQLITE_DROP_TABLE; + public static final int DROP_TEMP_INDEX = CApi.SQLITE_DROP_TEMP_INDEX; + public static final int DROP_TEMP_TABLE = CApi.SQLITE_DROP_TEMP_TABLE; + public static final int DROP_TEMP_TRIGGER = CApi.SQLITE_DROP_TEMP_TRIGGER; + public static final int DROP_TEMP_VIEW = CApi.SQLITE_DROP_TEMP_VIEW; + public static final int DROP_TRIGGER = CApi.SQLITE_DROP_TRIGGER; + public static final int DROP_VIEW = CApi.SQLITE_DROP_VIEW; + public static final int INSERT = CApi.SQLITE_INSERT; + public static final int PRAGMA = CApi.SQLITE_PRAGMA; + public static final int READ = CApi.SQLITE_READ; + public static final int SELECT = CApi.SQLITE_SELECT; + public static final int TRANSACTION = CApi.SQLITE_TRANSACTION; + public static final int UPDATE = CApi.SQLITE_UPDATE; + public static final int ATTACH = CApi.SQLITE_ATTACH; + public static final int DETACH = CApi.SQLITE_DETACH; + public static final int ALTER_TABLE = CApi.SQLITE_ALTER_TABLE; + public static final int REINDEX = CApi.SQLITE_REINDEX; + public static final int ANALYZE = CApi.SQLITE_ANALYZE; + public static final int CREATE_VTABLE = CApi.SQLITE_CREATE_VTABLE; + public static final int DROP_VTABLE = CApi.SQLITE_DROP_VTABLE; + public static final int FUNCTION = CApi.SQLITE_FUNCTION; + public static final int SAVEPOINT = CApi.SQLITE_SAVEPOINT; + public static final int RECURSIVE = CApi.SQLITE_RECURSIVE; + //! Used only by the open() factory functions. private Sqlite(sqlite3 db){ this.db = db; } + /** Maps org.sqlite.jni.capi.sqlite3 to Sqlite instances. */ + private static final java.util.Map nativeToWrapper + = new java.util.HashMap<>(); + + /** + Returns the Sqlite object associated with the given sqlite3 + object, or null if there is no such mapping. + */ + static Sqlite fromNative(sqlite3 low){ + synchronized(nativeToWrapper){ + return nativeToWrapper.get(low); + } + } + /** Returns a newly-opened db connection or throws SqliteException if opening fails. All arguments are as documented for @@ -76,7 +278,7 @@ public final class Sqlite implements AutoCloseable { */ public static Sqlite open(String filename, int flags, String vfsName){ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); - final int rc = sqlite3_open_v2(filename, out, flags, vfsName); + final int rc = CApi.sqlite3_open_v2(filename, out, flags, vfsName); final sqlite3 n = out.take(); if( 0!=rc ){ if( null==n ) throw new SqliteException(rc); @@ -84,7 +286,12 @@ public final class Sqlite implements AutoCloseable { n.close(); throw ex; } - return new Sqlite(n); + final Sqlite rv = new Sqlite(n); + synchronized(nativeToWrapper){ + nativeToWrapper.put(n, rv); + } + runAutoExtensions(rv); + return rv; } public static Sqlite open(String filename, int flags){ @@ -92,7 +299,7 @@ public final class Sqlite implements AutoCloseable { } public static Sqlite open(String filename){ - return open(filename, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, null); + return open(filename, OPEN_READWRITE|OPEN_CREATE, null); } public static String libVersion(){ @@ -107,28 +314,6 @@ public final class Sqlite implements AutoCloseable { return CApi.sqlite3_sourceid(); } - /** - As per sqlite3_status64(), but returns its current and high-water - results as a two-element array. Throws if the first argument is - not one of the STATUS_... constants. - */ - public long[] libStatus(int op, boolean resetStats){ - org.sqlite.jni.capi.OutputPointer.Int64 pCurrent = - new org.sqlite.jni.capi.OutputPointer.Int64(); - org.sqlite.jni.capi.OutputPointer.Int64 pHighwater = - new org.sqlite.jni.capi.OutputPointer.Int64(); - final int rc = CApi.sqlite3_status64(op, pCurrent, pHighwater, resetStats); - checkRc(rc); - return new long[] {pCurrent.value, pHighwater.value}; - } - - @Override public void close(){ - if(null!=this.db){ - this.db.close(); - this.db = null; - } - } - /** Returns the value of the native library's build-time value of the SQLITE_THREADSAFE build option. @@ -151,6 +336,22 @@ public final class Sqlite implements AutoCloseable { return CApi.sqlite3_compileoption_used(optName); } + private static boolean hasNormalizeSql = + compileOptionUsed("ENABLE_NORMALIZE"); + + /** + Throws UnsupportedOperationException if check is false. + flag is expected to be the name of an SQLITE_ENABLE_... + build flag. + */ + private static void checkSupported(boolean check, String flag){ + if( !check ){ + throw new UnsupportedOperationException( + "Library was built without "+flag + ); + } + } + /** Analog to sqlite3_complete(). */ @@ -186,6 +387,61 @@ public final class Sqlite implements AutoCloseable { return 0==CApi.sqlite3_strlike(glob, txt, escChar); } + /** + Output object for use with status() and libStatus(). + */ + public static final class Status { + /** The current value for the requested status() or libStatus() metric. */ + long current; + /** The peak value for the requested status() or libStatus() metric. */ + long peak; + }; + + /** + As per sqlite3_status64(), but returns its current and high-water + results as a Status object. Throws if the first argument is + not one of the STATUS_... constants. + */ + public static Status libStatus(int op, boolean resetStats){ + org.sqlite.jni.capi.OutputPointer.Int64 pCurrent = + new org.sqlite.jni.capi.OutputPointer.Int64(); + org.sqlite.jni.capi.OutputPointer.Int64 pHighwater = + new org.sqlite.jni.capi.OutputPointer.Int64(); + checkRc2( CApi.sqlite3_status64(op, pCurrent, pHighwater, resetStats) ); + final Status s = new Status(); + s.current = pCurrent.value; + s.peak = pHighwater.value; + return s; + } + + /** + As per sqlite3_db_status(), but returns its current and + high-water results as a Status object. Throws if the first + argument is not one of the DBSTATUS_... constants or on any other + misuse. + */ + public Status status(int op, boolean resetStats){ + org.sqlite.jni.capi.OutputPointer.Int32 pCurrent = + new org.sqlite.jni.capi.OutputPointer.Int32(); + org.sqlite.jni.capi.OutputPointer.Int32 pHighwater = + new org.sqlite.jni.capi.OutputPointer.Int32(); + checkRc( CApi.sqlite3_db_status(thisDb(), op, pCurrent, pHighwater, resetStats) ); + final Status s = new Status(); + s.current = pCurrent.value; + s.peak = pHighwater.value; + return s; + } + + @Override public void close(){ + if(null!=this.db){ + synchronized(nativeToWrapper){ + nativeToWrapper.remove(this.db); + } + this.db.close(); + this.db = null; + } + } + /** Returns this object's underlying native db handle, or null if this instance has been closed. This is very specifically not @@ -218,7 +474,7 @@ public final class Sqlite implements AutoCloseable { if( 0!=rc ){ if( CApi.SQLITE_NOMEM==rc ){ throw new OutOfMemoryError(); - }else if( null==db || 0==sqlite3_errcode(db)){ + }else if( null==db || 0==CApi.sqlite3_errcode(db) ){ throw new SqliteException(rc); }else{ throw new SqliteException(db); @@ -227,19 +483,54 @@ public final class Sqlite implements AutoCloseable { } /** + Like checkRc() but behaves as if that function were + called with a null db object. + */ + private static void checkRc2(int rc){ + if( 0!=rc ){ + if( CApi.SQLITE_NOMEM==rc ){ + throw new OutOfMemoryError(); + }else{ + throw new SqliteException(rc); + } + } + } + + /** + Toggles the use of extended result codes on or off. By default + they are turned off, but they can be enabled by default by + including the OPEN_EXRESCODE flag when opening a database. + + Because this API reports db-side errors using exceptions, + enabling this may change the values returned by + SqliteException.errcode(). + */ + public void useExtendedResultCodes(boolean on){ + checkRc( CApi.sqlite3_extended_result_codes(thisDb(), on) ); + } + + /** + Analog to sqlite3_prepare_v3(), this prepares the first SQL + statement from the given input string and returns it as a + Stmt. It throws an SqliteException if preparation fails or an + IllegalArgumentException if the input is empty (e.g. contains + only comments or whitespace). + + The first argument must be SQL input in UTF-8 encoding. + prepFlags must be 0 or a bitmask of the PREPARE_... constants. - prepare() TODOs include: + For processing multiple statements from a single input, use + prepareMulti(). - - overloads taking byte[] and ByteBuffer. - - - multi-statement processing, like CApi.sqlite3_prepare_multi() - but using a callback specific to the higher-level Stmt class - rather than the sqlite3_stmt class. + Design note: though the C-level API succeeds with a null + statement object for empty inputs, that approach is cumbersome to + use in higher-level APIs because every prepared statement has to + be checked for null before using it. */ - public Stmt prepare(String sql, int prepFlags){ + public Stmt prepare(byte utf8Sql[], int prepFlags){ final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt(); - final int rc = sqlite3_prepare_v3(thisDb(), sql, prepFlags, out); + final int rc = CApi.sqlite3_prepare_v3(thisDb(), utf8Sql, prepFlags, out); checkRc(rc); final sqlite3_stmt q = out.take(); if( null==q ){ @@ -256,10 +547,113 @@ public final class Sqlite implements AutoCloseable { return new Stmt(this, q); } + /** + Equivalent to prepare(X, prepFlags), where X is + sql.getBytes(StandardCharsets.UTF_8). + */ + public Stmt prepare(String sql, int prepFlags){ + return prepare( sql.getBytes(StandardCharsets.UTF_8), prepFlags ); + } + + /** + Equivalent to prepare(sql, 0). + */ public Stmt prepare(String sql){ return prepare(sql, 0); } + + /** + Callback type for use with prepareMulti(). + */ + public interface PrepareMulti { + /** + Gets passed a Stmt which it may handle in arbitrary ways. + Ownership of st is passed to this function. It must throw on + error. + */ + void call(Sqlite.Stmt st); + } + + /** + A PrepareMulti implementation which calls another PrepareMulti + object and then finalizes its statement. + */ + public static class PrepareMultiFinalize implements PrepareMulti { + private final PrepareMulti pm; + /** + Proxies the given PrepareMulti via this object's call() method. + */ + public PrepareMultiFinalize(PrepareMulti proxy){ + this.pm = proxy; + } + /** + Passes st to the call() method of the object this one proxies, + then finalizes st, propagating any exceptions from call() after + finalizing st. + */ + @Override public void call(Stmt st){ + try{ pm.call(st); } + finally{ st.finalizeStmt(); } + } + } + + /** + Equivalent to prepareMulti(sql,0,visitor). + */ + public void prepareMulti(String sql, PrepareMulti visitor){ + prepareMulti( sql, 0, visitor ); + } + + /** + A variant of prepare() which can handle multiple SQL statements + in a single input string. For each statement in the given string, + the statement is passed to visitor.call() a single time, passing + ownership of the statement to that function. This function does + not step() or close() statements - those operations are left to + caller or the visitor function. + + Unlike prepare(), this function does not fail if the input + contains only whitespace or SQL comments. In that case it is up + to the caller to arrange for that to be an error (if desired). + + PrepareMultiFinalize offers a proxy which finalizes each + statement after it is passed to another client-defined visitor. + */ + public void prepareMulti(byte sqlUtf8[], int prepFlags, PrepareMulti visitor){ + int pos = 0, n = 1; + byte[] sqlChunk = sqlUtf8; + final org.sqlite.jni.capi.OutputPointer.sqlite3_stmt outStmt = + new org.sqlite.jni.capi.OutputPointer.sqlite3_stmt(); + final org.sqlite.jni.capi.OutputPointer.Int32 oTail = + new org.sqlite.jni.capi.OutputPointer.Int32(); + while( pos < sqlChunk.length ){ + sqlite3_stmt stmt = null; + if( pos>0 ){ + sqlChunk = java.util.Arrays.copyOfRange(sqlChunk, pos, sqlChunk.length); + } + if( 0==sqlChunk.length ) break; + checkRc( + CApi.sqlite3_prepare_v3(db, sqlChunk, prepFlags, outStmt, oTail) + ); + pos = oTail.value; + stmt = outStmt.take(); + if( null==stmt ){ + /* empty statement, e.g. only comments or whitespace, was parsed. */ + continue; + } + visitor.call(new Stmt(this, stmt)); + } + } + + /** + Equivallent to prepareMulti(X,prepFlags,visitor), where X is + sql.getBytes(StandardCharsets.UTF_8). + */ + public void prepareMulti(String sql, int prepFlags, PrepareMulti visitor){ + prepareMulti(sql.getBytes(StandardCharsets.UTF_8), prepFlags, visitor); + } + public void createFunction(String name, int nArg, int eTextRep, ScalarFunction f){ int rc = CApi.sqlite3_create_function(thisDb(), name, nArg, eTextRep, new SqlFunction.ScalarAdapter(f)); @@ -318,10 +712,6 @@ public final class Sqlite implements AutoCloseable { return CApi.sqlite3_get_autocommit(thisDb()); } - public void setBusyTimeout(int ms){ - checkRc(CApi.sqlite3_busy_timeout(thisDb(), ms)); - } - /** Analog to sqlite3_txn_state(). Returns one of TXN_NONE, TXN_READ, or TXN_WRITE to denote this database's current transaction state @@ -348,6 +738,19 @@ public final class Sqlite implements AutoCloseable { return CApi.sqlite3_db_filename(thisDb(), dbName); } + /** + Analog to sqlite3_db_config() for the call forms which take one + of the boolean-type db configuration flags (namely the + DBCONFIG_... constants defined in this class). On success it + returns the result of that underlying call. Throws on error. + */ + public boolean dbConfig(int op, boolean on){ + org.sqlite.jni.capi.OutputPointer.Int32 pOut = + new org.sqlite.jni.capi.OutputPointer.Int32(); + checkRc( CApi.sqlite3_db_config(thisDb(), op, on ? 1 : 0, pOut) ); + return pOut.get()!=0; + } + /** Analog to the variant of sqlite3_db_config() for configuring the SQLITE_DBCONFIG_MAINDBNAME option. Throws on error. @@ -381,7 +784,7 @@ public final class Sqlite implements AutoCloseable { /** Analog to sqlite3_release_memory(). */ - public static int releaseMemory(int n){ + public static int libReleaseMemory(int n){ return CApi.sqlite3_release_memory(n); } @@ -467,11 +870,71 @@ public final class Sqlite implements AutoCloseable { return rv; } + public interface TraceCallback { + /** + Called by sqlite3 for various tracing operations, as per + sqlite3_trace_v2(). Note that this interface elides the 2nd + argument to the native trace callback, as that role is better + filled by instance-local state. + +

These callbacks may throw, in which case their exceptions are + converted to C-level error information. + +

The 2nd argument to this function, if non-null, will be a an + Sqlite or Sqlite.Stmt object, depending on the first argument + (see below). + +

The final argument to this function is the "X" argument + documented for sqlite3_trace() and sqlite3_trace_v2(). Its type + depends on value of the first argument: + +

- SQLITE_TRACE_STMT: pNative is a Sqlite.Stmt. pX is a String + containing the prepared SQL. + +

- SQLITE_TRACE_PROFILE: pNative is a sqlite3_stmt. pX is a Long + holding an approximate number of nanoseconds the statement took + to run. + +

- SQLITE_TRACE_ROW: pNative is a sqlite3_stmt. pX is null. + +

- SQLITE_TRACE_CLOSE: pNative is a sqlite3. pX is null. + */ + void call(int traceFlag, Object pNative, Object pX); + } + + /** + Analog to sqlite3_trace_v2(). traceMask must be a mask of the + TRACE_... constants. Pass a null callback to remove tracing. + + Throws on error. + */ + public void trace(int traceMask, TraceCallback callback){ + final Sqlite self = this; + final org.sqlite.jni.capi.TraceV2Callback tc = + (null==callback) ? null : new org.sqlite.jni.capi.TraceV2Callback(){ + @SuppressWarnings("unchecked") + @Override public int call(int flag, Object pNative, Object pX){ + switch(flag){ + case TRACE_ROW: + case TRACE_PROFILE: + case TRACE_STMT: + callback.call(flag, Sqlite.Stmt.fromNative((sqlite3_stmt)pNative), pX); + break; + case TRACE_CLOSE: + callback.call(flag, self, pX); + break; + } + return 0; + } + }; + checkRc( CApi.sqlite3_trace_v2(thisDb(), traceMask, tc) ); + }; + /** Corresponds to the sqlite3_stmt class. Use Sqlite.prepare() to create new instances. */ - public final class Stmt implements AutoCloseable { + public static final class Stmt implements AutoCloseable { private Sqlite _db = null; private sqlite3_stmt stmt = null; /** @@ -489,12 +952,29 @@ public final class Sqlite implements AutoCloseable { this._db = db; this.stmt = stmt; this.resultColCount = CApi.sqlite3_column_count(stmt); + synchronized(nativeToWrapper){ + nativeToWrapper.put(this.stmt, this); + } } sqlite3_stmt nativeHandle(){ return stmt; } + /** Maps org.sqlite.jni.capi.sqlite3_stmt to Stmt instances. */ + private static final java.util.Map nativeToWrapper + = new java.util.HashMap<>(); + + /** + Returns the Stmt object associated with the given sqlite3_stmt + object, or null if there is no such mapping. + */ + static Stmt fromNative(sqlite3_stmt low){ + synchronized(nativeToWrapper){ + return nativeToWrapper.get(low); + } + } + /** If this statement is still opened, its low-level handle is returned, eelse an IllegalArgumentException is thrown. @@ -527,7 +1007,10 @@ public final class Sqlite implements AutoCloseable { public int finalizeStmt(){ int rc = 0; if( null!=stmt ){ - sqlite3_finalize(stmt); + synchronized(nativeToWrapper){ + nativeToWrapper.remove(this.stmt); + } + CApi.sqlite3_finalize(stmt); stmt = null; _db = null; resultColCount = 0; @@ -548,8 +1031,8 @@ public final class Sqlite implements AutoCloseable { private int checkRc(int rc){ switch(rc){ case 0: - case SQLITE_ROW: - case SQLITE_DONE: return rc; + case CApi.SQLITE_ROW: + case CApi.SQLITE_DONE: return rc; default: if( null==stmt ) throw new SqliteException(rc); else throw new SqliteException(this); @@ -562,7 +1045,7 @@ public final class Sqlite implements AutoCloseable { result. */ public boolean step(){ - switch(checkRc(sqlite3_step(thisStmt()))){ + switch(checkRc(CApi.sqlite3_step(thisStmt()))){ case CApi.SQLITE_ROW: return true; case CApi.SQLITE_DONE: return false; default: @@ -570,21 +1053,13 @@ public final class Sqlite implements AutoCloseable { "This \"cannot happen\": all possible result codes were checked already." ); } - /* - Potential signature change TODO: - - boolean step() - - Returning true for SQLITE_ROW and false for anything else. - Those semantics have proven useful in the WASM/JS bindings. - */ } /** Returns the Sqlite which prepared this statement, or null if this statement has been finalized. */ - public Sqlite db(){ return this._db; } + public Sqlite getDb(){ return this._db; } /** Works like sqlite3_reset() but throws on error. @@ -624,10 +1099,12 @@ public final class Sqlite implements AutoCloseable { } /** - Analog to sqlite3_normalized_sql(), returning null if the - library is built without the SQLITE_ENABLE_NORMALIZE flag. + Analog to sqlite3_normalized_sql(), but throws + UnsupportedOperationException if the library was built without + the SQLITE_ENABLE_NORMALIZE flag. */ public String normalizedSql(){ + Sqlite.checkSupported(hasNormalizeSql, "SQLITE_ENABLE_NORMALIZE"); return CApi.sqlite3_normalized_sql(thisStmt()); } @@ -733,4 +1210,608 @@ public final class Sqlite implements AutoCloseable { } } /* Stmt class */ + /** + Interface for auto-extensions, as per the + sqlite3_auto_extension() API. + + Design note: the chicken/egg timing of auto-extension execution + requires that this feature be entirely re-implemented in Java + because the C-level API has no access to the Sqlite type so + cannot pass on an object of that type while the database is being + opened. One side effect of this reimplementation is that this + class's list of auto-extensions is 100% independent of the + C-level list so, e.g., clearAutoExtensions() will have no effect + on auto-extensions added via the C-level API and databases opened + from that level of API will not be passed to this level's + AutoExtension instances. + */ + public interface AutoExtension { + public void call(Sqlite db); + } + + private static final java.util.Set autoExtensions = + new java.util.LinkedHashSet<>(); + + /** + Passes db to all auto-extensions. If any one of them throws, + db.close() is called before the exception is propagated. + */ + private static void runAutoExtensions(Sqlite db){ + AutoExtension list[]; + synchronized(autoExtensions){ + /* Avoid that modifications to the AutoExtension list from within + auto-extensions affect this execution of this list. */ + list = autoExtensions.toArray(new AutoExtension[0]); + } + try { + for( AutoExtension ax : list ) ax.call(db); + }catch(Exception e){ + db.close(); + throw e; + } + } + + /** + Analog to sqlite3_auto_extension(), adds the given object to the + list of auto-extensions if it is not already in that list. The + given object will be run as part of Sqlite.open(), and passed the + being-opened database. If the extension throws then open() will + fail. + + This API does not guaranty whether or not manipulations made to + the auto-extension list from within auto-extension callbacks will + affect the current traversal of the auto-extension list. Whether + or not they do is unspecified and subject to change between + versions. e.g. if an AutoExtension calls addAutoExtension(), + whether or not the new extension will be run on the being-opened + database is undefined. + + Note that calling Sqlite.open() from an auto-extension will + necessarily result in recursion loop and (eventually) a stack + overflow. + */ + public static void addAutoExtension( AutoExtension e ){ + if( null==e ){ + throw new IllegalArgumentException("AutoExtension may not be null."); + } + synchronized(autoExtensions){ + autoExtensions.add(e); + } + } + + /** + Removes the given object from the auto-extension list if it is in + that list, otherwise this has no side-effects beyond briefly + locking that list. + */ + public static void removeAutoExtension( AutoExtension e ){ + synchronized(autoExtensions){ + autoExtensions.remove(e); + } + } + + /** + Removes all auto-extensions which were added via addAutoExtension(). + */ + public static void clearAutoExtensions(){ + synchronized(autoExtensions){ + autoExtensions.clear(); + } + } + + /** + Encapsulates state related to the sqlite3 backup API. Use + Sqlite.initBackup() to create new instances. + */ + public static final class Backup implements AutoCloseable { + private sqlite3_backup b = null; + private Sqlite dbTo = null; + private Sqlite dbFrom = null; + + Backup(Sqlite dbDest, String schemaDest,Sqlite dbSrc, String schemaSrc){ + this.dbTo = dbDest; + this.dbFrom = dbSrc; + b = CApi.sqlite3_backup_init(dbDest.nativeHandle(), schemaDest, + dbSrc.nativeHandle(), schemaSrc); + if(null==b) toss(); + } + + private void toss(){ + int rc = CApi.sqlite3_errcode(dbTo.nativeHandle()); + if(0!=rc) throw new SqliteException(dbTo); + rc = CApi.sqlite3_errcode(dbFrom.nativeHandle()); + if(0!=rc) throw new SqliteException(dbFrom); + throw new SqliteException(CApi.SQLITE_ERROR); + } + + private sqlite3_backup getNative(){ + if( null==b ) throw new IllegalStateException("This Backup is already closed."); + return b; + } + /** + If this backup is still active, this completes the backup and + frees its native resources, otherwise it this is a no-op. + */ + public void finish(){ + if( null!=b ){ + CApi.sqlite3_backup_finish(b); + b = null; + dbTo = null; + dbFrom = null; + } + } + + /** Equivalent to finish(). */ + @Override public void close(){ + this.finish(); + } + + /** + Analog to sqlite3_backup_step(). Returns 0 if stepping succeeds + or, Sqlite.DONE if the end is reached, Sqlite.BUSY if one of + the databases is busy, Sqlite.LOCKED if one of the databases is + locked, and throws for any other result code or if this object + has been closed. Note that BUSY and LOCKED are not necessarily + permanent errors, so do not trigger an exception. + */ + public int step(int pageCount){ + final int rc = CApi.sqlite3_backup_step(getNative(), pageCount); + switch(rc){ + case 0: + case Sqlite.DONE: + case Sqlite.BUSY: + case Sqlite.LOCKED: + return rc; + default: + toss(); + return CApi.SQLITE_ERROR/*not reached*/; + } + } + + /** + Analog to sqlite3_backup_pagecount(). + */ + public int pageCount(){ + return CApi.sqlite3_backup_pagecount(getNative()); + } + + /** + Analog to sqlite3_backup_remaining(). + */ + public int remaining(){ + return CApi.sqlite3_backup_remaining(getNative()); + } + } + + /** + Analog to sqlite3_backup_init(). If schemaSrc is null, "main" is + assumed. Throws if either this db or dbSrc (the source db) are + not opened, if either of schemaDest or schemaSrc are null, or if + the underlying call to sqlite3_backup_init() fails. + + The returned object must eventually be cleaned up by either + arranging for it to be auto-closed (e.g. using + try-with-resources) or by calling its finish() method. + */ + public Backup initBackup(String schemaDest, Sqlite dbSrc, String schemaSrc){ + thisDb(); + dbSrc.thisDb(); + if( null==schemaSrc || null==schemaDest ){ + throw new IllegalArgumentException( + "Neither the source nor destination schema name may be null." + ); + } + return new Backup(this, schemaDest, dbSrc, schemaSrc); + } + + + /** + Callback type for use with createCollation(). + */ + public interface Collation { + /** + Called by the SQLite core to compare inputs. Implementations + must compare its two arguments using memcmp(3) semantics. + + Warning: the SQLite core has no mechanism for reporting errors + from custom collations and its workflow does not accommodate + propagation of exceptions from callbacks. Any exceptions thrown + from collations will be silently supressed and sorting results + will be unpredictable. + */ + int call(byte[] lhs, byte[] rhs); + } + + /** + Analog to sqlite3_create_collation(). + + Throws if name is null or empty, c is null, or the encoding flag + is invalid. The encoding must be one of the UTF8, UTF16, UTF16LE, + or UTF16BE constants. + */ + public void createCollation(String name, int encoding, Collation c){ + thisDb(); + if( null==name || 0==name.length()){ + throw new IllegalArgumentException("Collation name may not be null or empty."); + } + if( null==c ){ + throw new IllegalArgumentException("Collation may not be null."); + } + switch(encoding){ + case UTF8: + case UTF16: + case UTF16LE: + case UTF16BE: + break; + default: + throw new IllegalArgumentException("Invalid Collation encoding."); + } + checkRc( + CApi.sqlite3_create_collation( + thisDb(), name, encoding, new org.sqlite.jni.capi.CollationCallback(){ + @Override public int call(byte[] lhs, byte[] rhs){ + try{return c.call(lhs, rhs);} + catch(Exception e){return 0;} + } + @Override public void xDestroy(){} + } + ) + ); + } + + /** + Callback for use with onCollationNeeded(). + */ + public interface CollationNeeded { + /** + Must behave as documented for the callback for + sqlite3_collation_needed(). + + Warning: the C API has no mechanism for reporting or + propagating errors from this callback, so any exceptions it + throws are suppressed. + */ + void call(Sqlite db, int encoding, String collationName); + } + + /** + Sets up the given object to be called by the SQLite core when it + encounters a collation name which it does not know. Pass a null + object to disconnect the object from the core. This replaces any + existing collation-needed loader, or is a no-op if the given + object is already registered. Throws if registering the loader + fails. + */ + public void onCollationNeeded( CollationNeeded cn ){ + org.sqlite.jni.capi.CollationNeededCallback cnc = null; + if( null!=cn ){ + cnc = new org.sqlite.jni.capi.CollationNeededCallback(){ + @Override public void call(sqlite3 db, int encoding, String collationName){ + final Sqlite xdb = Sqlite.fromNative(db); + if(null!=xdb) cn.call(xdb, encoding, collationName); + } + }; + } + checkRc( CApi.sqlite3_collation_needed(thisDb(), cnc) ); + } + + /** + Callback for use with busyHandler(). + */ + public interface BusyHandler { + /** + Must function as documented for the C-level + sqlite3_busy_handler() callback argument, minus the (void*) + argument the C-level function requires. + + If this function throws, it is translated to a database-level + error. + */ + int call(int n); + } + + /** + Analog to sqlite3_busy_timeout(). + */ + public void setBusyTimeout(int ms){ + checkRc(CApi.sqlite3_busy_timeout(thisDb(), ms)); + } + + /** + Analog to sqlite3_busy_handler(). If b is null then any + current handler is cleared. + */ + public void setBusyHandler( BusyHandler b ){ + org.sqlite.jni.capi.BusyHandlerCallback bhc = null; + if( null!=b ){ + bhc = new org.sqlite.jni.capi.BusyHandlerCallback(){ + @Override public int call(int n){ + return b.call(n); + } + }; + } + checkRc( CApi.sqlite3_busy_handler(thisDb(), bhc) ); + } + + public interface CommitHook { + /** + Must behave as documented for the C-level sqlite3_commit_hook() + callback. If it throws, the exception is translated into + a db-level error. + */ + int call(); + } + + /** + A level of indirection to permit setCommitHook() to have similar + semantics as the C API, returning the previous hook. The caveat + is that if the low-level API is used to install a hook, it will + have a different hook type than Sqlite.CommitHook so + setCommitHook() will return null instead of that object. + */ + private static class CommitHookProxy + implements org.sqlite.jni.capi.CommitHookCallback { + final CommitHook commitHook; + CommitHookProxy(CommitHook ch){ + this.commitHook = ch; + } + @Override public int call(){ + return commitHook.call(); + } + } + + /** + Analog to sqlite3_commit_hook(). Returns the previous hook, if + any (else null). Throws if this db is closed. + + Minor caveat: if a commit hook is set on this object's underlying + db handle using the lower-level SQLite API, this function may + return null when replacing it, despite there being a hook, + because it will have a different callback type. So long as the + handle is only manipulated via the high-level API, this caveat + does not apply. + */ + public CommitHook setCommitHook( CommitHook c ){ + CommitHookProxy chp = null; + if( null!=c ){ + chp = new CommitHookProxy(c); + } + final org.sqlite.jni.capi.CommitHookCallback rv = + CApi.sqlite3_commit_hook(thisDb(), chp); + return (rv instanceof CommitHookProxy) + ? ((CommitHookProxy)rv).commitHook + : null; + } + + + public interface RollbackHook { + /** + Must behave as documented for the C-level sqlite3_rollback_hook() + callback. If it throws, the exception is translated into + a db-level error. + */ + void call(); + } + + /** + A level of indirection to permit setRollbackHook() to have similar + semantics as the C API, returning the previous hook. The caveat + is that if the low-level API is used to install a hook, it will + have a different hook type than Sqlite.RollbackHook so + setRollbackHook() will return null instead of that object. + */ + private static class RollbackHookProxy + implements org.sqlite.jni.capi.RollbackHookCallback { + final RollbackHook rollbackHook; + RollbackHookProxy(RollbackHook ch){ + this.rollbackHook = ch; + } + @Override public void call(){rollbackHook.call();} + } + + /** + Analog to sqlite3_rollback_hook(). Returns the previous hook, if + any (else null). Throws if this db is closed. + + Minor caveat: if a rollback hook is set on this object's underlying + db handle using the lower-level SQLite API, this function may + return null when replacing it, despite there being a hook, + because it will have a different callback type. So long as the + handle is only manipulated via the high-level API, this caveat + does not apply. + */ + public RollbackHook setRollbackHook( RollbackHook c ){ + RollbackHookProxy chp = null; + if( null!=c ){ + chp = new RollbackHookProxy(c); + } + final org.sqlite.jni.capi.RollbackHookCallback rv = + CApi.sqlite3_rollback_hook(thisDb(), chp); + return (rv instanceof RollbackHookProxy) + ? ((RollbackHookProxy)rv).rollbackHook + : null; + } + + public interface UpdateHook { + /** + Must function as described for the C-level sqlite3_update_hook() + callback. + */ + void call(int opId, String dbName, String tableName, long rowId); + } + + /** + A level of indirection to permit setUpdateHook() to have similar + semantics as the C API, returning the previous hook. The caveat + is that if the low-level API is used to install a hook, it will + have a different hook type than Sqlite.UpdateHook so + setUpdateHook() will return null instead of that object. + */ + private static class UpdateHookProxy + implements org.sqlite.jni.capi.UpdateHookCallback { + final UpdateHook updateHook; + UpdateHookProxy(UpdateHook ch){ + this.updateHook = ch; + } + @Override public void call(int opId, String dbName, String tableName, long rowId){ + updateHook.call(opId, dbName, tableName, rowId); + } + } + + /** + Analog to sqlite3_update_hook(). Returns the previous hook, if + any (else null). Throws if this db is closed. + + Minor caveat: if a update hook is set on this object's underlying + db handle using the lower-level SQLite API, this function may + return null when replacing it, despite there being a hook, + because it will have a different callback type. So long as the + handle is only manipulated via the high-level API, this caveat + does not apply. + */ + public UpdateHook setUpdateHook( UpdateHook c ){ + UpdateHookProxy chp = null; + if( null!=c ){ + chp = new UpdateHookProxy(c); + } + final org.sqlite.jni.capi.UpdateHookCallback rv = + CApi.sqlite3_update_hook(thisDb(), chp); + return (rv instanceof UpdateHookProxy) + ? ((UpdateHookProxy)rv).updateHook + : null; + } + + + /** + Callback interface for use with setProgressHandler(). + */ + public interface ProgressHandler { + /** + Must behave as documented for the C-level sqlite3_progress_handler() + callback. If it throws, the exception is translated into + a db-level error. + */ + int call(); + } + + /** + Analog to sqlite3_progress_handler(), sets the current progress + handler or clears it if p is null. + + Note that this API, in contrast to setUpdateHook(), + setRollbackHook(), and setCommitHook(), cannot return the + previous handler. That inconsistency is part of the lower-level C + API. + */ + public void setProgressHandler( int n, ProgressHandler p ){ + org.sqlite.jni.capi.ProgressHandlerCallback phc = null; + if( null!=p ){ + phc = new org.sqlite.jni.capi.ProgressHandlerCallback(){ + @Override public int call(){ return p.call(); } + }; + } + CApi.sqlite3_progress_handler( thisDb(), n, phc ); + } + + + /** + Callback for use with setAuthorizer(). + */ + public interface Authorizer { + /** + Must function as described for the C-level + sqlite3_set_authorizer() callback. If it throws, the error is + converted to a db-level error and the exception is suppressed. + */ + int call(int opId, String s1, String s2, String s3, String s4); + } + + /** + Analog to sqlite3_set_authorizer(), this sets the current + authorizer callback, or clears if it passed null. + */ + public void setAuthorizer( Authorizer a ) { + org.sqlite.jni.capi.AuthorizerCallback ac = null; + if( null!=a ){ + ac = new org.sqlite.jni.capi.AuthorizerCallback(){ + @Override public int call(int opId, String s1, String s2, String s3, String s4){ + return a.call(opId, s1, s2, s3, s4); + } + }; + } + checkRc( CApi.sqlite3_set_authorizer( thisDb(), ac ) ); + } + + /** + Object type for use with blobOpen() + */ + public final class Blob implements AutoCloseable { + private Sqlite db; + private sqlite3_blob b; + Blob(Sqlite db, sqlite3_blob b){ + this.db = db; + this.b = b; + } + + /** + Analog to sqlite3_blob_close(). + */ + @Override public void close(){ + if( null!=b ){ + CApi.sqlite3_blob_close(b); + b = null; + db = null; + } + } + + /** + Analog to sqlite3_blob_reopen() but throws on error. + */ + public void reopen(long newRowId){ + db.checkRc( CApi.sqlite3_blob_reopen(b, newRowId) ); + } + + /** + Analog to sqlite3_blob_write() but throws on error. + */ + public void write( byte[] bytes, int atOffset ){ + db.checkRc( CApi.sqlite3_blob_write(b, bytes, atOffset) ); + } + + /** + Analog to sqlite3_blob_read() but throws on error. + */ + public void read( byte[] dest, int atOffset ){ + db.checkRc( CApi.sqlite3_blob_read(b, dest, atOffset) ); + } + + /** + Analog to sqlite3_blob_bytes(). + */ + public int bytes(){ + return CApi.sqlite3_blob_bytes(b); + } + } + + /** + Analog to sqlite3_blob_open(). Returns a Blob object for the + given database, table, column, and rowid. The blob is opened for + read-write mode if writeable is true, else it is read-only. + + The returned object must eventually be freed, before this + database is closed, by either arranging for it to be auto-closed + or calling its close() method. + + Throws on error. + */ + public Blob blobOpen(String dbName, String tableName, String columnName, + long iRow, boolean writeable){ + final OutputPointer.sqlite3_blob out = new OutputPointer.sqlite3_blob(); + checkRc( + CApi.sqlite3_blob_open(thisDb(), dbName, tableName, columnName, + iRow, writeable ? 1 : 0, out) + ); + return new Blob(this, out.take()); + } + } diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java b/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java index 27cfc0e6bb..9b4440f190 100644 --- a/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java +++ b/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java @@ -50,7 +50,7 @@ public final class SqliteException extends java.lang.RuntimeException { /** Records the current error state of db (which must not be null and - must refer to an opened db object). Note that this does NOT close + must refer to an opened db object). Note that this does not close the db. Design note: closing the db on error is really only useful during @@ -74,7 +74,7 @@ public final class SqliteException extends java.lang.RuntimeException { } public SqliteException(Sqlite.Stmt stmt){ - this(stmt.db()); + this(stmt.getDb()); } public int errcode(){ return errCode; } diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java b/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java index d24551ebf1..c276e383be 100644 --- a/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java +++ b/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java @@ -46,7 +46,7 @@ public class Tester2 implements Runnable { //! True to shuffle the order of the tests. private static boolean shuffle = false; //! True to dump the list of to-run tests to stdout. - private static boolean listRunTests = false; + private static int listRunTests = 0; //! True to squelch all out() and outln() output. private static boolean quietMode = false; //! Total number of runTests() calls. @@ -125,7 +125,7 @@ public class Tester2 implements Runnable { } - public static void execSql(Sqlite db, String[] sql){ + public static void execSql(Sqlite db, String sql[]){ execSql(db, String.join("", sql)); } @@ -133,6 +133,9 @@ public class Tester2 implements Runnable { Executes all SQL statements in the given string. If throwOnError is true then it will throw for any prepare/step errors, else it will return the corresponding non-0 result code. + + TODO: reimplement this in the high-level API once it has the + multi-prepare capability. */ public static int execSql(Sqlite dbw, boolean throwOnError, String sql){ final sqlite3 db = dbw.nativeHandle(); @@ -163,7 +166,7 @@ public class Tester2 implements Runnable { } CApi.sqlite3_finalize(stmt); affirm(0 == stmt.getNativePointer()); - if(CApi.SQLITE_DONE!=rc){ + if(Sqlite.DONE!=rc){ break; } } @@ -181,7 +184,7 @@ public class Tester2 implements Runnable { @SingleThreadOnly /* because it's thread-agnostic */ private void test1(){ - affirm(CApi.sqlite3_libversion_number() == CApi.SQLITE_VERSION_NUMBER); + affirm(Sqlite.libVersionNumber() == CApi.SQLITE_VERSION_NUMBER); } /* Copy/paste/rename this to add new tests. */ @@ -211,15 +214,24 @@ public class Tester2 implements Runnable { void testOpenDb1(){ Sqlite db = openDb(); affirm( 0!=db.nativeHandle().getNativePointer() ); + affirm( "main".equals( db.dbName(0) ) ); + db.setMainDbName("foo"); + affirm( "foo".equals( db.dbName(0) ) ); + affirm( db.dbConfig(Sqlite.DBCONFIG_DEFENSIVE, true) + /* The underlying function has different mangled names in jdk8 + vs jdk19, and this call is here to ensure that the build + fails if it cannot find both names. */ ); + affirm( !db.dbConfig(Sqlite.DBCONFIG_DEFENSIVE, false) ); + SqliteException ex = null; + try{ db.dbConfig(0, false); } + catch(SqliteException e){ ex = e; } + affirm( null!=ex ); + ex = null; db.close(); affirm( null==db.nativeHandle() ); - SqliteException ex = null; - try { - db = openDb("/no/such/dir/.../probably"); - }catch(SqliteException e){ - ex = e; - } + try{ db = openDb("/no/such/dir/.../probably"); } + catch(SqliteException e){ ex = e; } affirm( ex!=null ); affirm( ex.errcode() != 0 ); affirm( ex.extendedErrcode() != 0 ); @@ -232,6 +244,7 @@ public class Tester2 implements Runnable { Sqlite.Stmt stmt = db.prepare("SELECT ?1"); Exception e = null; affirm( null!=stmt.nativeHandle() ); + affirm( db == stmt.getDb() ); affirm( 1==stmt.bindParameterCount() ); affirm( "?1".equals(stmt.bindParameterName(1)) ); affirm( null==stmt.bindParameterName(2) ); @@ -285,21 +298,30 @@ public class Tester2 implements Runnable { final ValueHolder vh = new ValueHolder<>(0); final ScalarFunction f = new ScalarFunction(){ public void xFunc(SqlFunction.Arguments args){ + affirm( db == args.getDb() ); for( SqlFunction.Arguments.Arg arg : args ){ vh.value += arg.getInt(); } + args.resultInt(vh.value); } public void xDestroy(){ ++xDestroyCalled.value; } }; db.createFunction("myfunc", -1, f); - execSql(db, "select myfunc(1,2,3)"); + Sqlite.Stmt q = db.prepare("select myfunc(1,2,3)"); + affirm( q.step() ); affirm( 6 == vh.value ); - vh.value = 0; - execSql(db, "select myfunc(-1,-2,-3)"); - affirm( -6 == vh.value ); + affirm( 6 == q.columnInt(0) ); + q.finalizeStmt(); affirm( 0 == xDestroyCalled.value ); + vh.value = 0; + q = db.prepare("select myfunc(-1,-2,-3)"); + affirm( q.step() ); + affirm( -6 == vh.value ); + affirm( -6 == q.columnInt(0) ); + affirm( 0 == xDestroyCalled.value ); + q.finalizeStmt(); } affirm( 1 == xDestroyCalled.value ); } @@ -444,6 +466,508 @@ public class Tester2 implements Runnable { db.close(); } + + private void testTrace(){ + final Sqlite db = openDb(); + final ValueHolder counter = new ValueHolder<>(0); + /* Ensure that characters outside of the UTF BMP survive the trip + from Java to sqlite3 and back to Java. (At no small efficiency + penalty.) */ + final String nonBmpChar = "😃"; + db.trace( + Sqlite.TRACE_ALL, + new Sqlite.TraceCallback(){ + @Override public void call(int traceFlag, Object pNative, Object x){ + ++counter.value; + //outln("TRACE "+traceFlag+" pNative = "+pNative.getClass().getName()); + switch(traceFlag){ + case Sqlite.TRACE_STMT: + affirm(pNative instanceof Sqlite.Stmt); + //outln("TRACE_STMT sql = "+x); + affirm(x instanceof String); + affirm( ((String)x).indexOf(nonBmpChar) > 0 ); + break; + case Sqlite.TRACE_PROFILE: + affirm(pNative instanceof Sqlite.Stmt); + affirm(x instanceof Long); + //outln("TRACE_PROFILE time = "+x); + break; + case Sqlite.TRACE_ROW: + affirm(pNative instanceof Sqlite.Stmt); + affirm(null == x); + //outln("TRACE_ROW = "+sqlite3_column_text16((sqlite3_stmt)pNative, 0)); + break; + case Sqlite.TRACE_CLOSE: + affirm(pNative instanceof Sqlite); + affirm(null == x); + break; + default: + affirm(false /*cannot happen*/); + break; + } + } + }); + execSql(db, "SELECT coalesce(null,null,'"+nonBmpChar+"'); "+ + "SELECT 'w"+nonBmpChar+"orld'"); + affirm( 6 == counter.value ); + db.close(); + affirm( 7 == counter.value ); + } + + private void testStatus(){ + final Sqlite db = openDb(); + execSql(db, "create table t(a); insert into t values(1),(2),(3)"); + + Sqlite.Status s = Sqlite.libStatus(Sqlite.STATUS_MEMORY_USED, false); + affirm( s.current > 0 ); + affirm( s.peak >= s.current ); + + s = db.status(Sqlite.DBSTATUS_SCHEMA_USED, false); + affirm( s.current > 0 ); + affirm( s.peak == 0 /* always 0 for SCHEMA_USED */ ); + + db.close(); + } + + @SingleThreadOnly /* because multiple threads legitimately make these + results unpredictable */ + private synchronized void testAutoExtension(){ + final ValueHolder val = new ValueHolder<>(0); + final ValueHolder toss = new ValueHolder<>(null); + final Sqlite.AutoExtension ax = new Sqlite.AutoExtension(){ + @Override public void call(Sqlite db){ + ++val.value; + if( null!=toss.value ){ + throw new RuntimeException(toss.value); + } + } + }; + Sqlite.addAutoExtension(ax); + openDb().close(); + affirm( 1==val.value ); + openDb().close(); + affirm( 2==val.value ); + Sqlite.clearAutoExtensions(); + openDb().close(); + affirm( 2==val.value ); + + Sqlite.addAutoExtension( ax ); + Sqlite.addAutoExtension( ax ); // Must not add a second entry + Sqlite.addAutoExtension( ax ); // or a third one + openDb().close(); + affirm( 3==val.value ); + + Sqlite db = openDb(); + affirm( 4==val.value ); + execSql(db, "ATTACH ':memory:' as foo"); + affirm( 4==val.value, "ATTACH uses the same connection, not sub-connections." ); + db.close(); + db = null; + + Sqlite.removeAutoExtension(ax); + openDb().close(); + affirm( 4==val.value ); + Sqlite.addAutoExtension(ax); + Exception err = null; + toss.value = "Throwing from auto_extension."; + try{ + openDb(); + }catch(Exception e){ + err = e; + } + affirm( err!=null ); + affirm( err.getMessage().indexOf(toss.value)>=0 ); + toss.value = null; + + val.value = 0; + final Sqlite.AutoExtension ax2 = new Sqlite.AutoExtension(){ + @Override public void call(Sqlite db){ + ++val.value; + } + }; + Sqlite.addAutoExtension(ax2); + openDb().close(); + affirm( 2 == val.value ); + Sqlite.removeAutoExtension(ax); + openDb().close(); + affirm( 3 == val.value ); + Sqlite.addAutoExtension(ax); + openDb().close(); + affirm( 5 == val.value ); + Sqlite.removeAutoExtension(ax2); + openDb().close(); + affirm( 6 == val.value ); + Sqlite.addAutoExtension(ax2); + openDb().close(); + affirm( 8 == val.value ); + + Sqlite.clearAutoExtensions(); + openDb().close(); + affirm( 8 == val.value ); + } + + private void testBackup(){ + final Sqlite dbDest = openDb(); + + try (Sqlite dbSrc = openDb()) { + execSql(dbSrc, new String[]{ + "pragma page_size=512; VACUUM;", + "create table t(a);", + "insert into t(a) values(1),(2),(3);" + }); + Exception e = null; + try { + dbSrc.initBackup("main",dbSrc,"main"); + }catch(Exception x){ + e = x; + } + affirm( e instanceof SqliteException ); + e = null; + try (Sqlite.Backup b = dbDest.initBackup("main",dbSrc,"main")) { + affirm( null!=b ); + int rc; + while( Sqlite.DONE!=(rc = b.step(1)) ){ + affirm( 0==rc ); + } + affirm( b.pageCount() > 0 ); + b.finish(); + } + } + + try (Sqlite.Stmt q = dbDest.prepare("SELECT sum(a) from t")) { + q.step(); + affirm( q.columnInt(0) == 6 ); + } + dbDest.close(); + } + + private void testCollation(){ + final Sqlite db = openDb(); + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + final Sqlite.Collation myCollation = new Sqlite.Collation() { + private String myState = + "this is local state. There is much like it, but this is mine."; + @Override + // Reverse-sorts its inputs... + public int call(byte[] lhs, byte[] rhs){ + int len = lhs.length > rhs.length ? rhs.length : lhs.length; + int c = 0, i = 0; + for(i = 0; i < len; ++i){ + c = lhs[i] - rhs[i]; + if(0 != c) break; + } + if(0==c){ + if(i < lhs.length) c = 1; + else if(i < rhs.length) c = -1; + } + return -c; + } + }; + final Sqlite.CollationNeeded collLoader = new Sqlite.CollationNeeded(){ + @Override + public void call(Sqlite dbArg, int eTextRep, String collationName){ + affirm(dbArg == db); + db.createCollation("reversi", eTextRep, myCollation); + } + }; + db.onCollationNeeded(collLoader); + Sqlite.Stmt stmt = db.prepare("SELECT a FROM t ORDER BY a COLLATE reversi"); + int counter = 0; + while( stmt.step() ){ + final String val = stmt.columnText16(0); + ++counter; + switch(counter){ + case 1: affirm("c".equals(val)); break; + case 2: affirm("b".equals(val)); break; + case 3: affirm("a".equals(val)); break; + } + } + affirm(3 == counter); + stmt.finalizeStmt(); + stmt = db.prepare("SELECT a FROM t ORDER BY a"); + counter = 0; + while( stmt.step() ){ + final String val = stmt.columnText16(0); + ++counter; + //outln("Non-REVERSI'd row#"+counter+": "+val); + switch(counter){ + case 3: affirm("c".equals(val)); break; + case 2: affirm("b".equals(val)); break; + case 1: affirm("a".equals(val)); break; + } + } + affirm(3 == counter); + stmt.finalizeStmt(); + db.onCollationNeeded(null); + db.close(); + } + + @SingleThreadOnly /* because threads inherently break this test */ + private void testBusy(){ + final String dbName = "_busy-handler.db"; + try{ + Sqlite db1 = openDb(dbName); + ++metrics.dbOpen; + execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)"); + Sqlite db2 = openDb(dbName); + ++metrics.dbOpen; + + final ValueHolder xBusyCalled = new ValueHolder<>(0); + Sqlite.BusyHandler handler = new Sqlite.BusyHandler(){ + @Override public int call(int n){ + return n > 2 ? 0 : ++xBusyCalled.value; + } + }; + db2.setBusyHandler(handler); + + // Force a locked condition... + execSql(db1, "BEGIN EXCLUSIVE"); + int rc = 0; + SqliteException ex = null; + try{ + db2.prepare("SELECT * from t"); + }catch(SqliteException x){ + ex = x; + } + affirm( null!=ex ); + affirm( Sqlite.BUSY == ex.errcode() ); + affirm( 3 == xBusyCalled.value ); + db1.close(); + db2.close(); + }finally{ + try{(new java.io.File(dbName)).delete();} + catch(Exception e){/* ignore */} + } + } + + private void testCommitHook(){ + final Sqlite db = openDb(); + final ValueHolder counter = new ValueHolder<>(0); + final ValueHolder hookResult = new ValueHolder<>(0); + final Sqlite.CommitHook theHook = new Sqlite.CommitHook(){ + @Override public int call(){ + ++counter.value; + return hookResult.value; + } + }; + Sqlite.CommitHook oldHook = db.setCommitHook(theHook); + affirm( null == oldHook ); + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + affirm( 2 == counter.value ); + execSql(db, "BEGIN; SELECT 1; SELECT 2; COMMIT;"); + affirm( 2 == counter.value /* NOT invoked if no changes are made */ ); + execSql(db, "BEGIN; update t set a='d' where a='c'; COMMIT;"); + affirm( 3 == counter.value ); + oldHook = db.setCommitHook(theHook); + affirm( theHook == oldHook ); + execSql(db, "BEGIN; update t set a='e' where a='d'; COMMIT;"); + affirm( 4 == counter.value ); + oldHook = db.setCommitHook(null); + affirm( theHook == oldHook ); + execSql(db, "BEGIN; update t set a='f' where a='e'; COMMIT;"); + affirm( 4 == counter.value ); + oldHook = db.setCommitHook(null); + affirm( null == oldHook ); + execSql(db, "BEGIN; update t set a='g' where a='f'; COMMIT;"); + affirm( 4 == counter.value ); + + final Sqlite.CommitHook newHook = new Sqlite.CommitHook(){ + @Override public int call(){return 0;} + }; + oldHook = db.setCommitHook(newHook); + affirm( null == oldHook ); + execSql(db, "BEGIN; update t set a='h' where a='g'; COMMIT;"); + affirm( 4 == counter.value ); + oldHook = db.setCommitHook(theHook); + affirm( newHook == oldHook ); + execSql(db, "BEGIN; update t set a='i' where a='h'; COMMIT;"); + affirm( 5 == counter.value ); + hookResult.value = CApi.SQLITE_ERROR; + int rc = execSql(db, false, "BEGIN; update t set a='j' where a='i'; COMMIT;"); + affirm( CApi.SQLITE_CONSTRAINT_COMMITHOOK == rc ); + affirm( 6 == counter.value ); + db.close(); + } + + private void testRollbackHook(){ + final Sqlite db = openDb(); + final ValueHolder counter = new ValueHolder<>(0); + final Sqlite.RollbackHook theHook = new Sqlite.RollbackHook(){ + @Override public void call(){ + ++counter.value; + } + }; + Sqlite.RollbackHook oldHook = db.setRollbackHook(theHook); + affirm( null == oldHook ); + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + affirm( 0 == counter.value ); + execSql(db, false, "BEGIN; SELECT 1; SELECT 2; ROLLBACK;"); + affirm( 1 == counter.value /* contra to commit hook, is invoked if no changes are made */ ); + + final Sqlite.RollbackHook newHook = new Sqlite.RollbackHook(){ + @Override public void call(){} + }; + oldHook = db.setRollbackHook(newHook); + affirm( theHook == oldHook ); + execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;"); + affirm( 1 == counter.value ); + oldHook = db.setRollbackHook(theHook); + affirm( newHook == oldHook ); + execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;"); + affirm( 2 == counter.value ); + int rc = execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;"); + affirm( 0 == rc ); + affirm( 3 == counter.value ); + db.close(); + } + + private void testUpdateHook(){ + final Sqlite db = openDb(); + final ValueHolder counter = new ValueHolder<>(0); + final ValueHolder expectedOp = new ValueHolder<>(0); + final Sqlite.UpdateHook theHook = new Sqlite.UpdateHook(){ + @Override + public void call(int opId, String dbName, String tableName, long rowId){ + ++counter.value; + if( 0!=expectedOp.value ){ + affirm( expectedOp.value == opId ); + } + } + }; + Sqlite.UpdateHook oldHook = db.setUpdateHook(theHook); + affirm( null == oldHook ); + expectedOp.value = Sqlite.INSERT; + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + affirm( 3 == counter.value ); + expectedOp.value = Sqlite.UPDATE; + execSql(db, "update t set a='d' where a='c';"); + affirm( 4 == counter.value ); + oldHook = db.setUpdateHook(theHook); + affirm( theHook == oldHook ); + expectedOp.value = Sqlite.DELETE; + execSql(db, "DELETE FROM t where a='d'"); + affirm( 5 == counter.value ); + oldHook = db.setUpdateHook(null); + affirm( theHook == oldHook ); + execSql(db, "update t set a='e' where a='b';"); + affirm( 5 == counter.value ); + oldHook = db.setUpdateHook(null); + affirm( null == oldHook ); + + final Sqlite.UpdateHook newHook = new Sqlite.UpdateHook(){ + @Override public void call(int opId, String dbName, String tableName, long rowId){ + } + }; + oldHook = db.setUpdateHook(newHook); + affirm( null == oldHook ); + execSql(db, "update t set a='h' where a='a'"); + affirm( 5 == counter.value ); + oldHook = db.setUpdateHook(theHook); + affirm( newHook == oldHook ); + expectedOp.value = Sqlite.UPDATE; + execSql(db, "update t set a='i' where a='h'"); + affirm( 6 == counter.value ); + db.close(); + } + + private void testProgress(){ + final Sqlite db = openDb(); + final ValueHolder counter = new ValueHolder<>(0); + db.setProgressHandler(1, new Sqlite.ProgressHandler(){ + @Override public int call(){ + ++counter.value; + return 0; + } + }); + execSql(db, "SELECT 1; SELECT 2;"); + affirm( counter.value > 0 ); + int nOld = counter.value; + db.setProgressHandler(0, null); + execSql(db, "SELECT 1; SELECT 2;"); + affirm( nOld == counter.value ); + db.close(); + } + + private void testAuthorizer(){ + final Sqlite db = openDb(); + final ValueHolder counter = new ValueHolder<>(0); + final ValueHolder authRc = new ValueHolder<>(0); + final Sqlite.Authorizer auth = new Sqlite.Authorizer(){ + public int call(int op, String s0, String s1, String s2, String s3){ + ++counter.value; + //outln("xAuth(): "+s0+" "+s1+" "+s2+" "+s3); + return authRc.value; + } + }; + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + db.setAuthorizer(auth); + execSql(db, "UPDATE t SET a=1"); + affirm( 1 == counter.value ); + authRc.value = Sqlite.DENY; + int rc = execSql(db, false, "UPDATE t SET a=2"); + affirm( Sqlite.AUTH==rc ); + db.setAuthorizer(null); + rc = execSql(db, false, "UPDATE t SET a=2"); + affirm( 0==rc ); + db.close(); + } + + private void testBlobOpen(){ + final Sqlite db = openDb(); + + execSql(db, "CREATE TABLE T(a BLOB);" + +"INSERT INTO t(rowid,a) VALUES(1, 'def'),(2, 'XYZ');" + ); + Sqlite.Blob b = db.blobOpen("main", "t", "a", + db.lastInsertRowId(), true); + affirm( 3==b.bytes() ); + b.write(new byte[] {100, 101, 102 /*"DEF"*/}, 0); + b.close(); + Sqlite.Stmt stmt = db.prepare("SELECT length(a), a FROM t ORDER BY a"); + affirm( stmt.step() ); + affirm( 3 == stmt.columnInt(0) ); + affirm( "def".equals(stmt.columnText16(1)) ); + stmt.finalizeStmt(); + + b = db.blobOpen("main", "t", "a", db.lastInsertRowId(), false); + b.reopen(2); + final byte[] tgt = new byte[3]; + b.read( tgt, 0 ); + affirm( 100==tgt[0] && 101==tgt[1] && 102==tgt[2], "DEF" ); + b.close(); + db.close(); + } + + void testPrepareMulti(){ + final ValueHolder fCount = new ValueHolder<>(0); + final ValueHolder mCount = new ValueHolder<>(0); + try (Sqlite db = openDb()) { + execSql(db, "create table t(a); insert into t(a) values(1),(2),(3)"); + db.createFunction("counter", -1, new ScalarFunction(){ + @Override public void xFunc(SqlFunction.Arguments args){ + ++fCount.value; + args.resultNull(); + } + } + ); + final Sqlite.PrepareMulti pm = new Sqlite.PrepareMultiFinalize( + new Sqlite.PrepareMulti() { + @Override public void call(Sqlite.Stmt q){ + ++mCount.value; + while(q.step()){} + } + } + ); + final String sql = "select counter(*) from t;"+ + "select counter(*) from t; /* comment */"+ + "select counter(*) from t; -- comment\n" + ; + db.prepareMulti(sql, pm); + } + affirm( 3 == mCount.value ); + affirm( 9 == fCount.value ); + } + private void runTests(boolean fromThread) throws Exception { List mlist = testMethods; affirm( null!=mlist ); @@ -451,7 +975,7 @@ public class Tester2 implements Runnable { mlist = new ArrayList<>( testMethods.subList(0, testMethods.size()) ); java.util.Collections.shuffle(mlist); } - if( listRunTests ){ + if( (!fromThread && listRunTests>0) || listRunTests>1 ){ synchronized(this.getClass()){ if( !fromThread ){ out("Initial test"," list: "); @@ -514,7 +1038,9 @@ public class Tester2 implements Runnable { some chaos for cross-thread contention. -list-tests: outputs the list of tests being run, minus some - which are hard-coded. This is noisy in multi-threaded mode. + which are hard-coded. In multi-threaded mode, use this twice to + to emit the list run by each thread (which may differ from the initial + list, in particular if -shuffle is used). -fail: forces an exception to be thrown during the test run. Use with -shuffle to make its appearance unpredictable. @@ -543,7 +1069,7 @@ public class Tester2 implements Runnable { }else if(arg.equals("shuffle")){ shuffle = true; }else if(arg.equals("list-tests")){ - listRunTests = true; + ++listRunTests; }else if(arg.equals("fail")){ forceFail = true; }else if(arg.equals("sqllog")){ diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java b/ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java index 479fc74d7f..a3905567d4 100644 --- a/ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java +++ b/ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java @@ -12,10 +12,6 @@ ** This file is part of the wrapper1 interface for sqlite3. */ package org.sqlite.jni.wrapper1; -import org.sqlite.jni.capi.CApi; -import org.sqlite.jni.annotation.*; -import org.sqlite.jni.capi.sqlite3_context; -import org.sqlite.jni.capi.sqlite3_value; /** A SqlFunction implementation for window functions. The T type diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c index 18d27bdf0c..300307e5ea 100644 --- a/ext/wasm/api/sqlite3-wasm.c +++ b/ext/wasm/api/sqlite3-wasm.c @@ -877,7 +877,7 @@ const char * sqlite3_wasm_enum_json(void){ DefInt(SQLITE_STMTSTATUS_FILTER_HIT); DefInt(SQLITE_STMTSTATUS_MEMUSED); } _DefGroup; - + DefGroup(syncFlags) { DefInt(SQLITE_SYNC_NORMAL); DefInt(SQLITE_SYNC_FULL); @@ -901,6 +901,8 @@ const char * sqlite3_wasm_enum_json(void){ DefInt(SQLITE_DETERMINISTIC); DefInt(SQLITE_DIRECTONLY); DefInt(SQLITE_INNOCUOUS); + DefInt(SQLITE_SUBTYPE); + DefInt(SQLITE_RESULT_SUBTYPE); } _DefGroup; DefGroup(version) { diff --git a/ext/wasm/tester1.c-pp.js b/ext/wasm/tester1.c-pp.js index c26bce25be..36ca4c976f 100644 --- a/ext/wasm/tester1.c-pp.js +++ b/ext/wasm/tester1.c-pp.js @@ -1688,7 +1688,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; wasm.sqlite3_wasm_vfs_unlink(0, filename); } } - }/*sqlite3_js_vfs_create_file()*/) + }/*sqlite3_js_posix_create_file()*/) //////////////////////////////////////////////////////////////////// .t({ @@ -2605,28 +2605,6 @@ globalThis.sqlite3InitModule = sqlite3InitModule; } } }/*kvvfs sanity checks*/) - .t({ - name: 'kvvfs sqlite3_js_vfs_create_file()', - predicate: ()=>"kvvfs does not currently support this", - test: function(sqlite3){ - let db; - try { - db = new this.JDb(this.kvvfsDbFile); - const exp = capi.sqlite3_js_db_export(db); - db.close(); - this.kvvfsUnlink(); - capi.sqlite3_js_vfs_create_file("kvvfs", this.kvvfsDbFile, exp); - db = new this.JDb(filename); - T.assert(6 === db.selectValue('select count(*) from kvvfs')); - }finally{ - db.close(); - this.kvvfsUnlink(); - } - delete this.kvvfsDbFile; - delete this.kvvfsUnlink; - delete this.JDb; - } - }/*kvvfs sqlite3_js_vfs_create_file()*/) ;/* end kvvfs tests */ //////////////////////////////////////////////////////////////////////// diff --git a/manifest b/manifest index 98578565a7..c56d84fa85 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Refactor\sMBCS/UTF-8\stranslation\sto\savoid\sextra\sallocations,\ssupporting\snon-formatted\s(faster)\soutput.\sSome\scode\scleanup.\sWrap\s.system/.shell\scommand\sexection\swith\srestoration\sof\sstartup\sconsole\smode\sand\srenewing\smode\ssetup.\sChanges\sto\smake\slegacy\sMBCS\sbuild\swork\sbetter\s(than\slegacy\sdid,\seven\swith\s--no-utf8.) -D 2023-11-07T19:30:14.998 +C Pervasive\schanges\sto\sconsole_io.{c,h}\sin\ssupport\sof\ssimplifying\subiquitous\semit\sops\sin\sshell,\sand\sto\sget\sbetter\scontrol\sof\sconsole\sstreams\sthat\smight\sbe\sopened\sonly\svia\s.read\sor\s.output\scommands.\sChanges\sto\sshell\sto\suse\s{s,o,e}put{f,z}(...)\scalls\sfor\sinitial\stesting,\sbut\sthis\scheck-in\shas\sfew\ssuch\sconversions\sso\sthat\smost\swill\sbe\sin\sa\sseparate\scheck-in.\sMany\srenames\sto\sbetter\sfollow\srecent\scoding\sconvention.\sThis\scode\sseems\sto\sbe\sworking,\sbut\shas\snot\sbeen\stested\son\sdifficult\splatforms\sor\swith\smultiple\sconsole\shosts\syet.\sSo\sit\sis\sa\sWIP. +D 2023-11-11T06:20:38.614 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -50,8 +50,8 @@ F ext/README.md fd5f78013b0a2bc6f0067afb19e6ad040e89a10179b4f6f03eee58fac5f169bd F ext/async/README.txt e12275968f6fde133a80e04387d0e839b0c51f91 F ext/async/sqlite3async.c 6f247666b495c477628dd19364d279c78ea48cd90c72d9f9b98ad1aff3294f94 F ext/async/sqlite3async.h 46b47c79357b97ad85d20d2795942c0020dc20c532114a49808287f04aa5309a -F ext/consio/console_io.c adb7da4947a5dc661f0106a7a6962c7528653bf95709bcddae22f3422cde25f7 x -F ext/consio/console_io.h e6055b6a13a2a9f237e1672f9ef861126a37a61db0e6218a137832557f10ea25 +F ext/consio/console_io.c 19344e149e5c939c3c421823033abd4179d276ec1dae5f99e37d5bcd68ddbd59 x +F ext/consio/console_io.h c64d51d9f4e387679027d3b0977893390652f40c2e50a3c797506a9abb4856dc F ext/expert/README.md b321c2762bb93c18ea102d5a5f7753a4b8bac646cb392b3b437f633caf2020c3 F ext/expert/expert.c d548d603a4cc9e61f446cc179c120c6713511c413f82a4a32b1e1e69d3f086a4 F ext/expert/expert1.test 0dd5cb096d66bed593e33053a3b364f6ef52ed72064bf5cf298364636dbf3cd6 @@ -95,8 +95,8 @@ F ext/fts5/fts5_aux.c ee770eec0af8646db9e18fc01a0dad7345b5f5e8cbba236704cfae2d77 F ext/fts5/fts5_buffer.c 3001fbabb585d6de52947b44b455235072b741038391f830d6b729225eeaf6a5 F ext/fts5/fts5_config.c 054359543566cbff1ba65a188330660a5457299513ac71c53b3a07d934c7b081 F ext/fts5/fts5_expr.c bd3b81ce669c4104e34ffe66570af1999a317b142c15fccb112de9fb0caa57a6 -F ext/fts5/fts5_hash.c 65e7707bc8774706574346d18c20218facf87de3599b995963c3e6d6809f203d -F ext/fts5/fts5_index.c 730c9c32ada18ce1eb7ff847b36507f4b005d88d47af7b47db521e695a8ea4c7 +F ext/fts5/fts5_hash.c 076058f93327051952a752dc765df1acfe783eb11b419b30652aa1fc1f987902 +F ext/fts5/fts5_index.c 01b671fedd2189f6969385d96facc4c06d9c441f0f91d584386a62b724282f9f F ext/fts5/fts5_main.c a07ed863b8bd9e6fefb62db2fd40a3518eb30a5f7dcfda5be915dd2db45efa2f F ext/fts5/fts5_storage.c 5d10b9bdcce5b90656cad13c7d12ad4148677d4b9e3fca0481fca56d6601426d F ext/fts5/fts5_tcl.c b1445cbe69908c411df8084a10b2485500ac70a9c747cdc8cda175a3da59d8ae @@ -195,7 +195,7 @@ F ext/fts5/test/fts5plan.test b65cfcca9ddd6fdaa118c61e17aeec8e8433bc5b6bb307abd1 F ext/fts5/test/fts5porter.test 8d08010c28527db66bc3feebd2b8767504aaeb9b101a986342fa7833d49d0d15 F ext/fts5/test/fts5porter2.test 0d251a673f02fa13ca7f011654873b3add20745f7402f108600a23e52d8c7457 F ext/fts5/test/fts5prefix.test a0fa67b06650f2deaa7bf27745899d94e0fb547ad9ecbd08bfad98c04912c056 -F ext/fts5/test/fts5prefix2.test 3847ce46f70b82d61c6095103a9d7c53f2952c40a4704157bc079c04d9c8b18b +F ext/fts5/test/fts5prefix2.test ad751d4a5b029726ee908a7664e27d27bde7584218b8d7944c2a323afd381432 F ext/fts5/test/fts5query.test ac363b17a442620bb0780e93c24f16a5f963dfe2f23dc85647b869efcfada728 F ext/fts5/test/fts5rank.test 30f29e278cd7fb8831ba4f082feb74d8eb90c463bf07113ae200afc2b467ef32 F ext/fts5/test/fts5rebuild.test 55d6f17715cddbf825680dd6551efbc72ed916d8cf1cde40a46fc5d785b451e7 @@ -240,49 +240,49 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 -F ext/jni/GNUmakefile 36919b7c4fb8447da4330df9996c7b064b766957f8b7be214a30eab55a8b8072 +F ext/jni/GNUmakefile f2f3a31923293659b95225e932a286af1f2287d75bf88ad6c0fd1b9d9cd020d4 F ext/jni/README.md ef9ac115e97704ea995d743b4a8334e23c659e5534c3b64065a5405256d5f2f4 F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa -F ext/jni/src/c/sqlite3-jni.c afe9c25b82279a28fe2c81f869070fa0d434b0a8ccd7f8aca0e8173db410d14a -F ext/jni/src/c/sqlite3-jni.h 1c45fd4689cec42f3d84d2fee41bb494016a12fcb5fd80291095590666a14015 -F ext/jni/src/org/sqlite/jni/annotation/NotNull.java a99341e88154e70447596b1af6a27c586317df41a7e0f246fd41370cd7b723b2 +F ext/jni/src/c/sqlite3-jni.c 3774703e5865e7ff776b762de5386af8aa703e569bbb3a85c423c3f8473a3c26 +F ext/jni/src/c/sqlite3-jni.h 891444578550a7aa69fe5e0dedb3e6dedad752501ba99801f17797be51796934 +F ext/jni/src/org/sqlite/jni/annotation/NotNull.java 02091a8112e33389f1c160f506cd413168c8dfacbeda608a4946c6e3557b7d5a F ext/jni/src/org/sqlite/jni/annotation/Nullable.java 0b1879852707f752512d4db9d7edd0d8db2f0c2612316ce1c832715e012ff6ba F ext/jni/src/org/sqlite/jni/annotation/package-info.java 977b374aed9d5853cbf3438ba3b0940abfa2ea4574f702a2448ee143b98ac3ca F ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java 1afa90d3f236f79cc7fcd2497e111992644f7596fbc8e8bcf7f1908ae00acd6c F ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java 0b72cdff61533b564d65b63418129656daa9a9f30e7e7be982bd5ab394b1dbd0 -F ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java 7ed409d5449684616cc924534e22ff6b07d361f12ad904b69ecb10e0568a8013 +F ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java c045a5b47e02bb5f1af91973814a905f12048c428a3504fbc5266d1c1be3de5a F ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java 74cc4998a73d6563542ecb90804a3c4f4e828cb4bd69e61226d1a51f4646e759 F ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java 7b8e19810c42b0ad21a04b5d8c804b32ee5905d137148703f16a75b612c380ca -F ext/jni/src/org/sqlite/jni/capi/CApi.java 24aba7b14b11da52cd47083608d37c186122c2e2072e75b2ff923d1f15bfb9e5 +F ext/jni/src/org/sqlite/jni/capi/CApi.java 92d443b08175c798e132a312f71b1a42140c60d473d35c149e3d95a45b6550f3 F ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java 57e2d275dcebe690b1fc1f3d34eb96879b2d7039bce30b563aee547bf45d8a8b F ext/jni/src/org/sqlite/jni/capi/CollationCallback.java e29bcfc540fdd343e2f5cca4d27235113f2886acb13380686756d5cabdfd065a -F ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java f81cf10b79c52f9b2e9247d523d29ae48863935f60420eae35f257c38c80ce95 -F ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java 29c002f3c638cc80f7db1594564a262d1beb32637824c3dca2d60a224d1f71d7 +F ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java 5bfa226a8e7a92e804fd52d6e42b4c7b875fa7a94f8e2c330af8cc244a8920ab +F ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java 482f53dfec9e3ac2a9070d3fceebd56250932aaaf7c4f5bc8de29fc011416e0c F ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java b995ca412f59b631803b93aa5b3684fce62e335d1e123207084c054abfd488d4 F ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java 701f2e4d8bdeb27cfbeeb56315d15b13d8752b0fdbca705f31bd4366c58d8a33 F ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java b7036dcb1ef1b39f1f36ac605dde0ff1a24a9a01ade6aa1a605039443e089a61 F ext/jni/src/org/sqlite/jni/capi/OutputPointer.java 68f60aec7aeb5cd4e5fb83449037f668c63cb99f682ee1036cc226d0cbd909b9 F ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java aca8f9fa72e3b6602bc9a7dd3ae9f5b2808103fbbee9b2749dc96c19cdc261a1 -F ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java 819d938e26208adde17ca4b7ddde1d8cd6915b6ab7b708249a9787beca6bd6b6 +F ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java efcf57545c5e282d1dd332fa63329b3b218d98f356ef107a9dbe3979be82213a F ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java 01bc0c238eed2d5f93c73522cb7849a445cc9098c2ed1e78248fa20ed1cfde5b F ext/jni/src/org/sqlite/jni/capi/ResultCode.java 8141171f1bcf9f46eef303b9d3c5dc2537a25ad1628f3638398d8a60cacefa7f -F ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java 105e324d09c207100485e7667ad172e64322c62426bb49b547e9b0dc9c33f5f0 +F ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java e172210a2080e851ebb694c70e9f0bf89284237795e38710a7f5f1b61e3f6787 F ext/jni/src/org/sqlite/jni/capi/SQLFunction.java 0d1e9afc9ff8a2adb94a155b72385155fa3b8011a5cca0bb3c28468c7131c1a5 F ext/jni/src/org/sqlite/jni/capi/SQLTester.java 09bee15aa0eedac68d767ae21d9a6a62a31ade59182a3ccbf036d6463d9e30b1 F ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java 93b9700fca4c68075ccab12fe0fbbc76c91cafc9f368e835b9bd7cd7732c8615 F ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java addf120e0e76e5be1ff2260daa7ce305ff9b5fafd64153a7a28e9d8f000a815f -F ext/jni/src/org/sqlite/jni/capi/Tester1.java b6b2f3354ba68956a6bcd1c586b8eb25a0bd66eed2b58b340405e1129da15de9 +F ext/jni/src/org/sqlite/jni/capi/Tester1.java b1a0c015d92a8d0c07a8f6751e9b057557cec9d803e002d48ee5f3b9963abd55 F ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java 0a25e117a0daae3394a77f24713e36d7b44c67d6e6d30e9e1d56a63442eef723 -F ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java 2766b8526bbffc4f1045f70e79f1bc1b1efe1c3e95ca06cdb8a7391032dda3b4 +F ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java c8bdf7848e6599115d601bcc9427ff902cb33129b9be32870ac6808e04b6ae56 F ext/jni/src/org/sqlite/jni/capi/ValueHolder.java 22d365746a78c5cd7ae10c39444eb7bbf1a819aad4bb7eb77b1edc47773a3950 F ext/jni/src/org/sqlite/jni/capi/WindowFunction.java caf4396f91b2567904cf94bc538a069fd62260d975bd037d15a02a890ed1ef9e F ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java f3abb8dd7381f53ebba909437090caf68200f06717b8a7d6aa96fa3e8133117d F ext/jni/src/org/sqlite/jni/capi/package-info.java 08ff986a65d2be9162442c82d28a65ce431d826f188520717c2ecb1484d0a50e -F ext/jni/src/org/sqlite/jni/capi/sqlite3.java 4010bbebc5bf44e2044e610786088cdee7dc155da2b333c0551492ff1cedf33b +F ext/jni/src/org/sqlite/jni/capi/sqlite3.java c6a5c555d163d76663534f2b2cce7cab15325b9852d0f58c6688a85e73ae52f0 F ext/jni/src/org/sqlite/jni/capi/sqlite3_backup.java 6742b431cd4d77e8000c1f92ec66265a58414c86bf3b0b5fbcb1164e08477227 -F ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java f204ab6ab1263e119fe43730141a00662d80972129a5351dfb11aae5d282df36 +F ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java 59e26ca5254cd4771f467237bcfe2d8deed30a77152fabcd4574fd406c301d63 F ext/jni/src/org/sqlite/jni/capi/sqlite3_context.java f0ef982009c335c4393ffcb68051809ca1711e4f47bcb8d1d46952f22c01bc22 -F ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java ff579621e9bd5ffbc6b2ef9f996c12db4df6e0c8cc5697c91273e5fca279fcf8 +F ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java 293b5fa7d5b5724c87de544654aca1103d76f3092bc2c8f4360102a65ba25dff F ext/jni/src/org/sqlite/jni/capi/sqlite3_value.java e1d62a257c13504b46d39d5c21c49cf157ad73fda00cc5f34c931aa008c37049 F ext/jni/src/org/sqlite/jni/fts5/Fts5.java e94681023785f1eff5399f0ddc82f46b035977d350f14838db659236ebdf6b41 F ext/jni/src/org/sqlite/jni/fts5/Fts5Context.java 338637e6e5a2cc385d962b220f3c1f475cc371d12ae43d18ef27327b6e6225f7 @@ -295,14 +295,14 @@ F ext/jni/src/org/sqlite/jni/fts5/fts5_api.java a8e88c3783d21cec51b0748568a96653 F ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java 9e2b954d210d572552b28aca523b272fae14bd41e318921b22f65b728d5bf978 F ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java 92bdaa3893bd684533004d64ade23d329843f809cd0d0f4f1a2856da6e6b4d90 F ext/jni/src/org/sqlite/jni/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e -F ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java bbe60ac7fd8718edb215a23dc901771bcedb1df3b46d9cf6caff6f419828587f +F ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java d5c108b02afd3c63c9e5e53f71f85273c1bfdc461ae526e0a0bb2b25e4df6483 F ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java 43c43adfb7866098aadaaca1620028a6ec82d5193149970019b1cce9eb59fb03 -F ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java 0b01b9058ef6737c85b505c6aa2490fb1dc1d974fb39d88a93269fed09553f9f -F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java 1c95f5e0f872aeb9cdd174cbb2e254d158df1f8b2fee9f0e6ec82c348602a7bd -F ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java aa85b4b05fae240b14f3d332f9524a2f80c619fb03856be72b4adda866b63b72 -F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java 9ab7e38e6741842f8e3b74cd3ecb4953e2f1957f5229bd32663df7331245ce95 +F ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java 27b141f5914c7cb0e40e90a301d5e05b77f3bd42236834a68031b7086381fafd +F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java 0ef62b43b1d6a9f044e106b56c9ea42bc7150b82ebeb79cff58f5be08cb9a435 +F ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java 982538ddb4c0719ef87dfa664cd137b09890b546029a7477810bd64d4c47ee35 +F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java 40806dbbf8e120f115e33255d1813db13b40f0a598869e299a947a580429939b F ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java 7b89a7391f771692c5b83b0a5b86266abe8d59f1c77d7a0eccc9b79f259d79af -F ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java 1a1afbafbd7406ff67e7d6405541c6347517c731de535a97d7a3df1d4db835b4 +F ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java c7d1452f9ff26175b3c19bbf273116cc2846610af68e01756d755f037fe7319f F ext/jni/src/tests/000-000-sanity.test c3427a0e0ac84d7cbe4c95fdc1cd4b61f9ddcf43443408f3000139478c4dc745 F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 F ext/jni/src/tests/900-001-fts.test bf0ce17a8d082773450e91f2388f5bbb2dfa316d0b676c313c637a91198090f0 @@ -593,7 +593,7 @@ F ext/wasm/api/sqlite3-opfs-async-proxy.js 8cf8a897726f14071fae6be6648125162b256 F ext/wasm/api/sqlite3-v-helper.js 7daa0eab0a513a25b05e9abae7b5beaaa39209b3ed12f86aeae9ef8d2719ed25 F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js 595953994aa3ae2287c889c4da39ab3d6f17b6461ecf4bec334b7a3faafddb02 F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 46c4afa6c50d7369252c104f274ad977a97e91ccfafc38b400fe36e90bdda88e -F ext/wasm/api/sqlite3-wasm.c 038de1b6d40b2cc0f41a143a0451db60b2a6f1b5bc06de67da255c54ea1661b7 +F ext/wasm/api/sqlite3-wasm.c d0e09eb5ed3743c00294e30019e591c3aa150572ae7ffe8a8994568a7377589f F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js bc06df0d599e625bde6a10a394e326dc68da9ff07fa5404354580f81566e591f F ext/wasm/api/sqlite3-worker1.c-pp.js a541112aa51e16705f13a99bb943c64efe178aa28c86704a955f8fd9afe4ba37 F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8 @@ -638,7 +638,7 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555 F ext/wasm/test-opfs-vfs.js f09266873e1a34d9bdb6d3981ec8c9e382f31f215c9fd2f9016d2394b8ae9b7b F ext/wasm/tester1-worker.html ebc4b820a128963afce328ecf63ab200bd923309eb939f4110510ab449e9814c F ext/wasm/tester1.c-pp.html 1c1bc78b858af2019e663b1a31e76657b73dc24bede28ca92fbe917c3a972af2 -F ext/wasm/tester1.c-pp.js d628826e936bd143d64e0fa3089752abeeeea38a34a7e2b18d364f090d4e99c6 +F ext/wasm/tester1.c-pp.js a92dc256738dbd1b50f142d1fd0c835294ba09b7bb6526650360e942f88cb63f F ext/wasm/tests/opfs/concurrency/index.html 0802373d57034d51835ff6041cda438c7a982deea6079efd98098d3e42fbcbc1 F ext/wasm/tests/opfs/concurrency/test.js a98016113eaf71e81ddbf71655aa29b0fed9a8b79a3cdd3620d1658eb1cc9a5d F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2 @@ -672,7 +672,7 @@ F src/build.c 189e4517d67f09f0a3e0d8e1faa6e2ef0c2e95f6ac82e33c912cb7efa2a359cc F src/callback.c db3a45e376deff6a16c0058163fe0ae2b73a2945f3f408ca32cf74960b28d490 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e F src/ctime.c 23331529e654be40ca97d171cbbffe9b3d4c71cc53b78fe5501230675952da8b -F src/date.c eebc54a00e888d3c56147779e9f361b77d62fd69ff2008c5373946aa1ba1d574 +F src/date.c 3b8d02977d160e128469de38493b4085f7c5cf4073193459909a6af3cf6d7c91 F src/dbpage.c 80e46e1df623ec40486da7a5086cb723b0275a6e2a7b01d9f9b5da0f04ba2782 F src/dbstat.c 3b677254d512fcafd4d0b341bf267b38b235ccfddbef24f9154e19360fa22e43 F src/delete.c cb766727c78e715f9fb7ec8a7d03658ed2a3016343ca687acfcec9083cdca500 @@ -686,10 +686,10 @@ F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 F src/insert.c 3f0a94082d978bbdd33c38fefea15346c6c6bffb70bc645a71dc0f1f87dd3276 -F src/json.c d69c6e28ff7b602877bda68cd20583b8487c059759aa4d154dd21b3fd99c6238 +F src/json.c f93bf3df3651b1e01e2b57f7dc56f727e7b0e212d934eacf21c6fc8b31bf685e F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa F src/loadext.c 7432c944ff197046d67a1207790a1b13eec4548c85a9457eb0896bb3641dfb36 -F src/main.c e1bc8864834697503d370d94613be945d05ca1c5ebdda43e7d5c8ee8c48d433c +F src/main.c 1b89f3de98d1b59fec5bac1d66d6ece21f703821b8eaa0d53d9604c35309f6f9 F src/malloc.c f016922435dc7d1f1f5083a03338a3e91f8c67ce2c5bdcfa4cdef62e612f5fcc F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c 3bb59158c38e05f6270e761a9f435bf19827a264c13d1631c58b84bdc96d73b2 @@ -710,10 +710,10 @@ F src/os.h 1ff5ae51d339d0e30d8a9d814f4b8f8e448169304d83a7ed9db66a65732f3e63 F src/os_common.h 6c0eb8dd40ef3e12fe585a13e709710267a258e2c8dd1c40b1948a1d14582e06 F src/os_kv.c 4d39e1f1c180b11162c6dc4aa8ad34053873a639bac6baae23272fc03349986a F src/os_setup.h 6011ad7af5db4e05155f385eb3a9b4470688de6f65d6166b8956e58a3d872107 -F src/os_unix.c cb116fde9e3ca3c1bbfdf89d6928f776a2a34da168e2667426523a4db353b271 +F src/os_unix.c 0a33005e6426702c7e76f3d451f296c088693a95b2be28ba9ef59c8d8529ce6b F src/os_win.c 4a50a154aeebc66a1f8fb79c1ff6dd5fe3d005556533361e0d460d41cb6a45a8 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a -F src/pager.c 699aab8dfc88056d796b03b40c0ab979040d58dfc3ae9db207f1be91e4880bbf +F src/pager.c 987ab3a2cd9065d62e9955474470ff733445e2357432a67e3d0f5a8f9313e334 F src/pager.h f4d33fec8052603758792045493423b8871a996da2d0973927b7d36cd6070473 F src/parse.y 020d80386eb216ec9520549106353c517d2bbc89be28752ffdca649a9eaf56ec F src/pcache.c 040b165f30622a21b7a9a77c6f2e4877a32fb7f22d4c7f0d2a6fa6833a156a75 @@ -726,12 +726,12 @@ F src/printf.c 9da63b9ae1c14789bcae12840f5d800fd9302500cd2d62733fac77f0041b4750 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c d017bad7ba8e778617701a0e986fdeb393d67d6afa84fb28ef4e8b8ad2acf916 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c a19daa26e95f7245106a31f288b2f50c72d1f2cc156703f04c8c91450e111515 -F src/shell.c.in 5afd6ba7c0144e2a55df1c24732d88e4ae860459970b25ee2b4a3812af53c358 -F src/sqlite.h.in ef0e41e83ad1ac0dcc9ec9939bf541a44b1c5de821bee2d6c61754c3252f3276 +F src/select.c 503331aca8785254a7bf3d74ab338a99118fa297e1184a4dde33b3cdf7a9d341 +F src/shell.c.in 2bbff1e18baafccf312c622ce966792f6a70ec77b642d92b0c0df4885df76722 +F src/sqlite.h.in 4f841d3d117b830ee5ee45e8d89ceff1195f3ebb72d041ace8d116ba4c103b35 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 3f046c04ea3595d6bfda99b781926b17e672fd6d27da2ba6d8d8fc39981dcb54 -F src/sqliteInt.h 707095a0591e02f4866ed9798cbdd97a8ae8cf4d98f061808944c2cd1c95d1a9 +F src/sqliteInt.h cd171cba32c7a553e7623fbd82b68b36a1b6c81079ab963260777ea9b3abe4d9 F src/sqliteLimit.h 33b1c9baba578d34efe7dfdb43193b366111cdf41476b1e82699e14c11ee1fb6 F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 @@ -789,33 +789,33 @@ F src/test_window.c cdae419fdcea5bad6dcd9368c685abdad6deb59e9fc8b84b153de513d394 F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c F src/tokenize.c 23d9f4539880b40226254ad9072f4ecf12eb1902e62aea47aac29928afafcfd5 -F src/treeview.c 62fafcd31eea60b718f8daf448116b7b19f90134ebc6c20777ddbb07f56a3d28 +F src/treeview.c c6fc972683fd00f975d8b32a81c1f25d2fb7d4035366bf45c9f5622d3ccd70ee F src/trigger.c 0905b96b04bb6658509f711a8207287f1315cdbc3df1a1b13ba6483c8e341c81 F src/update.c 6904814dd62a7a93bbb86d9f1419c7f134a9119582645854ab02b36b676d9f92 F src/upsert.c fa125a8d3410ce9a97b02cb50f7ae68a2476c405c76aa692d3acf6b8586e9242 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 F src/util.c b22cc9f203a8c0b9ee5338a67f8860347d14845864c10248bebe84518a781677 F src/vacuum.c 604fcdaebe76f3497c855afcbf91b8fa5046b32de3045bab89cc008d68e40104 -F src/vdbe.c 14479441337135eed8e290fb1d4abb7db657d93217a3b1ea8a2f031d3895536a +F src/vdbe.c 7034cf3eec0c905df753368efbcdd96377fca0245584e66766ec47a29fe468c8 F src/vdbe.h 41485521f68e9437fdb7ec4a90f9d86ab294e9bb8281e33b235915e29122cfc0 F src/vdbeInt.h 949669dfd8a41550d27dcb905b494f2ccde9a2e6c1b0b04daa1227e2e74c2b2c -F src/vdbeapi.c db190d007bdf5b9165edeb12369f4c59a459f88fd652c1671c1238862e662cc3 -F src/vdbeaux.c dffcf79e7e415fcd6e4c8ac1ec7124cae5257018443adf09551c807655b04993 +F src/vdbeapi.c b07df805110dc6e81f2a3f9cd4e83f56ea523277a59bcec489a12b740c1079e7 +F src/vdbeaux.c f3997b5956c8d97bd2fc3392db42caecddfa6549e9df82e0a7e5804653ca475a F src/vdbeblob.c 13f9287b55b6356b4b1845410382d6bede203ceb29ef69388a4a3d007ffacbe5 -F src/vdbemem.c c936e9002af4993b84c4eb7133d6b1190efe46d391cc86117ecd67ba17b1a04b +F src/vdbemem.c 0012d5f01cc866833847c2f3ae4c318ac53a1cb3d28acad9c35e688039464cf0 F src/vdbesort.c 237840ca1947511fa59bd4e18b9eeae93f2af2468c34d2427b059f896230a547 F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf823 F src/vdbevtab.c 2143db7db0ceed69b21422581f434baffc507a08d831565193a7a02882a1b6a7 F src/vtab.c 154725ebecd3bc02f7fbd7ad3974334f73fff76e02a964e828e48a7c5fb7efff F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 -F src/wal.c 01e051a1e713d9eabdb25df38602837cec8f4c2cae448ce2cf6accc87af903e9 +F src/wal.c bba7db5dae3ffe2c6b9c173fc10be4b570b125e985cb5b95a6c22716213adde4 F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452 F src/walker.c 7c7ea0115345851c3da4e04e2e239a29983b61fb5b038b94eede6aba462640e2 -F src/where.c 313ce81270d2a414672370e1ee74e65949ad620519193d4cac2986d073cbc8a0 +F src/where.c 45b2239e127beaaae2367e503ca4c82868d8fef707c7f7f4a6c0528e7d5f65ff F src/whereInt.h 4b38c5889514e3aead3f27d0ee9a26e47c3f150efc59e2a8b4e3bc8835e4d7a1 F src/wherecode.c 5d77db30a2a3dd532492ae882de114edba2fae672622056b1c7fd61f5917a8f1 F src/whereexpr.c dc5096eca5ed503999be3bdee8a90c51361289a678d396a220912e9cb73b3c00 -F src/window.c ad21e2b73ec75acc79dde2576c573f54a338b0c49e9de847ce984f9b9595b5e2 +F src/window.c 5b1387d59df30d481ed14cceef5f4d1dab1f8752aa106ba72c8b62777bd139d2 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test ce1aafc86e110685b324e9a763eab4f2a73f737842ec3b687bd965867de90627 F test/affinity3.test f094773025eddf31135c7ad4cde722b7696f8eb07b97511f98585addf2a510a9 @@ -1018,7 +1018,7 @@ F test/ctime.test 340f362f41f92972bbd71f44e10569a5cc694062b692231bd08aa6fe6c1c47 F test/cursorhint.test 05cf0febe5c5f8a31f199401fd1c9322249e753950d55f26f9d5aca61408a270 F test/cursorhint2.test 6f3aa9cb19e7418967a10ec6905209bcbb5968054da855fc36c8beee9ae9c42f F test/dataversion1.test 6e5e86ac681f0782e766ebcb56c019ae001522d114e0e111e5ebf68ccf2a7bb8 -F test/date.test c0d17cdfd89395bc78087b131e3538d96f864b5029c335318011accc7c0d0934 +F test/date.test ff2341a1ef71b9a27979494d299222f9a293aa22cb9ff6e9c38d88a895317ebf F test/date2.test 7e12ec14aaf4d5e6294b4ba140445b0eca06ea50062a9c3a69c4ee13d0b6f8b1 F test/date3.test a1b77abf05c6772fe5ca2337cac1398892f2a41e62bce7e6be0f4a08a0e64ae5 F test/date4.test 8aeb3de5b5e9fda968baa9357e4c0fae573724b7904943410195a19e96e31b6a @@ -1271,7 +1271,7 @@ F test/index8.test caa097735c91dbc23d8a402f5e63a2a03c83840ba3928733ed7f9a03f8a91 F test/index9.test 2ac891806a4136ef3e91280477e23114e67575207dc331e6797fa0ed9379f997 F test/indexA.test 11d84f6995e6e5b9d8315953fb1b6d29772ee7c7803ee9112715e7e4dd3e4974 F test/indexedby.test f21eca4f7a6ffe14c8500a7ad6cd53166666c99e5ccd311842a28bc94a195fe0 -F test/indexexpr1.test 62558b1cfd7ccbe7bc015849cc6d1a13ef124e80cbd5b3a98dc66c3c9cce0cf4 +F test/indexexpr1.test 833f511213a5e26549186813f0566bd72f978177a7e6e98a2d2dd695de3c670d F test/indexexpr2.test 1c382e81ef996d8ae8b834a74f2a9013dddf59214c32201d7c8a656d739f999a F test/indexfault.test 98d78a8ff1f5335628b62f886a1cb7c7dac1ef6d48fa39c51ec871c87dce9811 F test/init.test 15c823093fdabbf7b531fe22cf037134d09587a7 @@ -1311,7 +1311,7 @@ F test/joinC.test 1f1a602c2127f55f136e2cbd3bf2d26546614bf8cffe5902ec1ac9c07f87f2 F test/joinD.test 2ce62e7353a0702ca5e70008faf319c1d4686aa19fba34275c6d1da0e960be28 F test/joinE.test d5d182f3812771e2c0d97c9dcf5dbe4c41c8e21c82560e59358731c4a3981d6b F test/joinF.test 53dd66158806823ea680dd7543b5406af151b5aafa5cd06a7f3231cd94938127 -F test/joinH.test 84198ea42bf78b79fe399c0567218cd6df36c50c6dd27d9c4aab221acaad929e +F test/joinH.test f69e5b53b7d887914e854b6a131efbed4ea9f5ca52bdab81788bfc3e79299f43 F test/journal1.test c7b768041b7f494471531e17abc2f4f5ebf9e5096984f43ed17c4eb80ba34497 F test/journal2.test 9dac6b4ba0ca79c3b21446bbae993a462c2397c4 F test/journal3.test 7c3cf23ffc77db06601c1fcfc9743de8441cb77db9d1aa931863d94f5ffa140e @@ -1327,7 +1327,7 @@ F test/json102.test 4c69694773a470f1fda34e5f4ba24920b35184fb66050b450fc2ef9ab5ad F test/json103.test 53df87f83a4e5fa0c0a56eb29ff6c94055c6eb919f33316d62161a8880112dbe F test/json104.test 1b844a70cddcfa2e4cd81a5db0657b2e61e7f00868310f24f56a9ba0114348c1 F test/json105.test 11670a4387f4308ae0318cadcbd6a918ea7edcd19fbafde020720a073952675d -F test/json501.test f71710f60fa45b19dc336fbaac9e8362f70f80cf81badefdb845ed3f7c7c2ccc +F test/json501.test c419deb835b70c1a2c8532936927bcc1146730328edd2052276715bfd209724d F test/json502.test 98c38e3c4573841028a1381dfb81d4c3f9b105d39668167da10d055e503f6d0b F test/keyword1.test 37ef6bba5d2ed5b07ecdd6810571de2956599dff F test/kvtest.c 6e0228409ea7ca0497dad503fbd109badb5e59545d131014b6aaac68b56f484a @@ -1489,7 +1489,6 @@ F test/recover.test fd5199f928757cb308661b5fdca1abc19398a798ff7f24b57c3071e9f8e0 F test/regexp1.test 8f2a8bc1569666e29a4cee6c1a666cd224eb6d50e2470d1dc1df995170f3e0f1 F test/regexp2.test 55ed41da802b0e284ac7e2fe944be3948f93ff25abbca0361a609acfed1368b5 F test/reindex.test cd9d6021729910ece82267b4f5e1b5ac2911a7566c43b43c176a6a4732e2118d -F test/releasetest_data.tcl 80ef3941bf7ad136f4dc3d8960786f10a4f488797238171bdd757cc6eb4c3efa F test/resetdb.test 54c06f18bc832ac6d6319e5ab23d5c8dd49fdbeec7c696d791682a8006bd5fc3 F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb F test/returning1.test db532cde29d6aebbc48c6ddc3149b30476f8e69ca7a2c4b53986c7635e6fd8ec @@ -1589,7 +1588,7 @@ F test/snapshot2.test 8d6ff5dd9cc503f6e12d408a30409c3f9c653507b24408d9cd7195931c F test/snapshot3.test 8744313270c55f6e18574283553d3c5c5fe4c5970585663613a0e75c151e599b F test/snapshot4.test d4e9347ef2fcabc491fc893506c7bbaf334da3be111d6eb4f3a97cc623b78322 F test/snapshot_fault.test 129234ceb9b26a0e1000e8563a16e790f5c1412354e70749cbd78c3d5d07d60a -F test/snapshot_up.test a0a29c4cf33475fcef07c3f8e64af795e24ab91b4cc68295863402a393cdd41c +F test/snapshot_up.test 77dc7853bfb2b4fa249f76e1714cfa1e596826165d9ef22c06ac3a0b7b778d9a F test/soak.test 18944cf21b94a7fe0df02016a6ee1e9632bc4e8d095a0cb49d95e15d5cca2d5c F test/softheap1.test 843cd84db9891b2d01b9ab64cef3e9020f98d087 F test/sort.test f86751134159abb5e5fd4381a0d7038c91013638cd1e3fa1d7850901f6df6196 @@ -1653,7 +1652,7 @@ F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637 F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc F test/tester.tcl 68454ef88508c196d19e8694daa27bff7107a91857799eaa12f417188ae53ede F test/testrunner.tcl 8a6721213bce1cfd3b33e1588cc6431143d96b98819206bf91f5a205fbb150d4 -F test/testrunner_data.tcl 7f73f93634d32dafc857ed491b840f371113d09fde6a8bfb9e47b938d47b8c85 +F test/testrunner_data.tcl e4d5017290a6d5c11785e36cc94c67d8bb950c8cdc2dbe4c1db2a3a583812560 F test/thread001.test a0985c117eab62c0c65526e9fa5d1360dd1cac5b03bde223902763274ce21899 F test/thread002.test c24c83408e35ba5a952a3638b7ac03ccdf1ce4409289c54a050ac4c5f1de7502 F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7 @@ -1954,8 +1953,6 @@ F test/walshared.test 42e3808582504878af237ea02c42ca793e8a0efaa19df7df26ac573370 F test/walslow.test c05c68d4dc2700a982f89133ce103a1a84cc285f F test/walthread.test 14b20fcfa6ae152f5d8e12f5dc8a8a724b7ef189f5d8ef1e2ceab79f2af51747 F test/walvfs.test e1a6ad0f3c78e98b55c3d5f0889cf366cc0d0a1cb2bccb44ac9ec67384adc4a1 -F test/wapp.tcl b440cd8cf57953d3a49e7ee81e6a18f18efdaf113b69f7d8482b0710a64566ec -F test/wapptest.tcl 1bea58a6a8e68a73f542ee4fca28b771b84ed803bd0c9e385087070b3d747b3c x F test/where.test 59abb854eee24f166b5f7ba9d17eb250abc59ce0a66c48912ffb10763648196d F test/where2.test 03c21a11e7b90e2845fc3c8b4002fc44cc2797fa74c86ee47d70bd7ea4f29ed6 F test/where3.test 5b4ffc0ac2ea0fe92f02b1244b7531522fe4d7bccf6fa8741d54e82c10e67753 @@ -2144,8 +2141,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 1721dc6a434361c4e2b87c6e677b6dc223432b3cdd5b9eecabaa258889fb2d2a -R c19fe6cbae4ae273f253c34b1a4e1156 +P d5e88fcde53ca7ba05bb164943a9f57bd92080bb7e5eebbbed64b9886ac97338 3978c084a509c3c739fbe87e20feec9ddf1325e35170329987af197ca9fd731a f1eae192315335d7e385b0a801a17700a9718d245bda6628518c5df9a1e9d3d6 +R 13fdfe04f23cf8d5bcfae91b277b730e U larrybr -Z 24f475e52cde2b6dcf8641f1d924498c +Z 7767a4710aecdf1826b87f96aec24468 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 4d85fa6de7..340d950f96 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -d5e88fcde53ca7ba05bb164943a9f57bd92080bb7e5eebbbed64b9886ac97338 \ No newline at end of file +14762a004cdf37d1e12f26aadff8ed3824893278f22ff141de86dd44d9b250f3 \ No newline at end of file diff --git a/src/date.c b/src/date.c index f163085445..e493542db9 100644 --- a/src/date.c +++ b/src/date.c @@ -1043,6 +1043,12 @@ static int isDate( } computeJD(p); if( p->isError || !validJulianDay(p->iJD) ) return 1; + if( argc==1 && p->validYMD && p->D>28 ){ + /* Make sure a YYYY-MM-DD is normalized. + ** Example: 2023-02-31 -> 2023-03-03 */ + assert( p->validJD ); + p->validYMD = 0; + } return 0; } diff --git a/src/json.c b/src/json.c index c2129a026e..0f11ace02b 100644 --- a/src/json.c +++ b/src/json.c @@ -432,13 +432,19 @@ static void jsonAppendNormalizedString(JsonString *p, const char *zIn, u32 N){ zIn++; N -= 2; while( N>0 ){ - for(i=0; i0 ){ jsonAppendRawNZ(p, zIn, i); zIn += i; N -= i; if( N==0 ) break; } + if( zIn[0]=='"' ){ + jsonAppendRawNZ(p, "\\\"", 2); + zIn++; + N--; + continue; + } assert( zIn[0]=='\\' ); switch( (u8)zIn[1] ){ case '\'': @@ -833,7 +839,8 @@ static void jsonReturnJson( JsonParse *pParse, /* The complete JSON */ JsonNode *pNode, /* Node to return */ sqlite3_context *pCtx, /* Return value for this function */ - int bGenerateAlt /* Also store the rendered text in zAlt */ + int bGenerateAlt, /* Also store the rendered text in zAlt */ + int omitSubtype /* Do not call sqlite3_result_subtype() */ ){ JsonString s; if( pParse->oom ){ @@ -848,7 +855,7 @@ static void jsonReturnJson( pParse->nAlt = s.nUsed; } jsonResult(&s); - sqlite3_result_subtype(pCtx, JSON_SUBTYPE); + if( !omitSubtype ) sqlite3_result_subtype(pCtx, JSON_SUBTYPE); } } @@ -889,7 +896,8 @@ static u32 jsonHexToInt4(const char *z){ static void jsonReturn( JsonParse *pParse, /* Complete JSON parse tree */ JsonNode *pNode, /* Node to return */ - sqlite3_context *pCtx /* Return value for this function */ + sqlite3_context *pCtx, /* Return value for this function */ + int omitSubtype /* Do not call sqlite3_result_subtype() */ ){ switch( pNode->eType ){ default: { @@ -1035,7 +1043,7 @@ static void jsonReturn( } case JSON_ARRAY: case JSON_OBJECT: { - jsonReturnJson(pParse, pNode, pCtx, 0); + jsonReturnJson(pParse, pNode, pCtx, 0, omitSubtype); break; } } @@ -2387,7 +2395,7 @@ static void jsonParseFunc( printf("iSubst = %u\n", p->iSubst); printf("iHold = %u\n", p->iHold); jsonDebugPrintNodeEntries(p->aNode, p->nNode); - jsonReturnJson(p, p->aNode, ctx, 1); + jsonReturnJson(p, p->aNode, ctx, 1, 0); } /* @@ -2573,15 +2581,14 @@ static void jsonExtractFunc( } if( pNode ){ if( flags & JSON_JSON ){ - jsonReturnJson(p, pNode, ctx, 0); + jsonReturnJson(p, pNode, ctx, 0, 0); }else{ - jsonReturn(p, pNode, ctx); - sqlite3_result_subtype(ctx, 0); + jsonReturn(p, pNode, ctx, 1); } } }else{ pNode = jsonLookup(p, zPath, 0, ctx); - if( p->nErr==0 && pNode ) jsonReturn(p, pNode, ctx); + if( p->nErr==0 && pNode ) jsonReturn(p, pNode, ctx, 0); } }else{ /* Two or more PATH arguments results in a JSON array with each @@ -2707,7 +2714,7 @@ static void jsonPatchFunc( if( pResult && pX->oom==0 ){ jsonDebugPrintParse(pX); jsonDebugPrintNode(pResult); - jsonReturnJson(pX, pResult, ctx, 0); + jsonReturnJson(pX, pResult, ctx, 0, 0); }else{ sqlite3_result_error_nomem(ctx); } @@ -2786,7 +2793,7 @@ static void jsonRemoveFunc( } } if( (pParse->aNode[0].jnFlags & JNODE_REMOVE)==0 ){ - jsonReturnJson(pParse, pParse->aNode, ctx, 1); + jsonReturnJson(pParse, pParse->aNode, ctx, 1, 0); } remove_done: jsonDebugPrintParse(p); @@ -2915,7 +2922,7 @@ static void jsonReplaceFunc( jsonReplaceNode(ctx, pParse, (u32)(pNode - pParse->aNode), argv[i+1]); } } - jsonReturnJson(pParse, pParse->aNode, ctx, 1); + jsonReturnJson(pParse, pParse->aNode, ctx, 1, 0); replace_err: jsonDebugPrintParse(pParse); jsonParseFree(pParse); @@ -2969,7 +2976,7 @@ static void jsonSetFunc( } } jsonDebugPrintParse(pParse); - jsonReturnJson(pParse, pParse->aNode, ctx, 1); + jsonReturnJson(pParse, pParse->aNode, ctx, 1, 0); jsonSetDone: jsonParseFree(pParse); } @@ -3484,7 +3491,7 @@ static int jsonEachColumn( case JEACH_KEY: { if( p->i==0 ) break; if( p->eType==JSON_OBJECT ){ - jsonReturn(&p->sParse, pThis, ctx); + jsonReturn(&p->sParse, pThis, ctx, 0); }else if( p->eType==JSON_ARRAY ){ u32 iKey; if( p->bRecursive ){ @@ -3500,7 +3507,7 @@ static int jsonEachColumn( } case JEACH_VALUE: { if( pThis->jnFlags & JNODE_LABEL ) pThis++; - jsonReturn(&p->sParse, pThis, ctx); + jsonReturn(&p->sParse, pThis, ctx, 0); break; } case JEACH_TYPE: { @@ -3511,7 +3518,7 @@ static int jsonEachColumn( case JEACH_ATOM: { if( pThis->jnFlags & JNODE_LABEL ) pThis++; if( pThis->eType>=JSON_ARRAY ) break; - jsonReturn(&p->sParse, pThis, ctx); + jsonReturn(&p->sParse, pThis, ctx, 0); break; } case JEACH_ID: { @@ -3804,34 +3811,43 @@ static sqlite3_module jsonTreeModule = { void sqlite3RegisterJsonFunctions(void){ #ifndef SQLITE_OMIT_JSON static FuncDef aJsonFunc[] = { - JFUNCTION(json, 1, 0, jsonRemoveFunc), - JFUNCTION(json_array, -1, 0, jsonArrayFunc), - JFUNCTION(json_array_length, 1, 0, jsonArrayLengthFunc), - JFUNCTION(json_array_length, 2, 0, jsonArrayLengthFunc), - JFUNCTION(json_error_position,1, 0, jsonErrorFunc), - JFUNCTION(json_extract, -1, 0, jsonExtractFunc), - JFUNCTION(->, 2, JSON_JSON, jsonExtractFunc), - JFUNCTION(->>, 2, JSON_SQL, jsonExtractFunc), - JFUNCTION(json_insert, -1, 0, jsonSetFunc), - JFUNCTION(json_object, -1, 0, jsonObjectFunc), - JFUNCTION(json_patch, 2, 0, jsonPatchFunc), - JFUNCTION(json_quote, 1, 0, jsonQuoteFunc), - JFUNCTION(json_remove, -1, 0, jsonRemoveFunc), - JFUNCTION(json_replace, -1, 0, jsonReplaceFunc), - JFUNCTION(json_set, -1, JSON_ISSET, jsonSetFunc), - JFUNCTION(json_type, 1, 0, jsonTypeFunc), - JFUNCTION(json_type, 2, 0, jsonTypeFunc), - JFUNCTION(json_valid, 1, 0, jsonValidFunc), + /* calls sqlite3_result_subtype() */ + /* | */ + /* Uses cache ______ | __ calls sqlite3_value_subtype() */ + /* | | | */ + /* Num args _________ | | | ___ Flags */ + /* | | | | | */ + /* | | | | | */ + JFUNCTION(json, 1, 1, 1, 0, 0, jsonRemoveFunc), + JFUNCTION(json_array, -1, 0, 1, 1, 0, jsonArrayFunc), + JFUNCTION(json_array_length, 1, 1, 0, 0, 0, jsonArrayLengthFunc), + JFUNCTION(json_array_length, 2, 1, 0, 0, 0, jsonArrayLengthFunc), + JFUNCTION(json_error_position,1, 1, 0, 0, 0, jsonErrorFunc), + JFUNCTION(json_extract, -1, 1, 1, 0, 0, jsonExtractFunc), + JFUNCTION(->, 2, 1, 1, 0, JSON_JSON, jsonExtractFunc), + JFUNCTION(->>, 2, 1, 0, 0, JSON_SQL, jsonExtractFunc), + JFUNCTION(json_insert, -1, 1, 1, 1, 0, jsonSetFunc), + JFUNCTION(json_object, -1, 0, 1, 1, 0, jsonObjectFunc), + JFUNCTION(json_patch, 2, 1, 1, 0, 0, jsonPatchFunc), + JFUNCTION(json_quote, 1, 0, 1, 1, 0, jsonQuoteFunc), + JFUNCTION(json_remove, -1, 1, 1, 0, 0, jsonRemoveFunc), + JFUNCTION(json_replace, -1, 1, 1, 1, 0, jsonReplaceFunc), + JFUNCTION(json_set, -1, 1, 1, 1, JSON_ISSET, jsonSetFunc), + JFUNCTION(json_type, 1, 1, 0, 0, 0, jsonTypeFunc), + JFUNCTION(json_type, 2, 1, 0, 0, 0, jsonTypeFunc), + JFUNCTION(json_valid, 1, 1, 0, 0, 0, jsonValidFunc), #if SQLITE_DEBUG - JFUNCTION(json_parse, 1, 0, jsonParseFunc), - JFUNCTION(json_test1, 1, 0, jsonTest1Func), + JFUNCTION(json_parse, 1, 1, 1, 0, 0, jsonParseFunc), + JFUNCTION(json_test1, 1, 1, 0, 1, 0, jsonTest1Func), #endif WAGGREGATE(json_group_array, 1, 0, 0, jsonArrayStep, jsonArrayFinal, jsonArrayValue, jsonGroupInverse, - SQLITE_SUBTYPE|SQLITE_UTF8|SQLITE_DETERMINISTIC), + SQLITE_SUBTYPE|SQLITE_RESULT_SUBTYPE|SQLITE_UTF8| + SQLITE_DETERMINISTIC), WAGGREGATE(json_group_object, 2, 0, 0, jsonObjectStep, jsonObjectFinal, jsonObjectValue, jsonGroupInverse, - SQLITE_SUBTYPE|SQLITE_UTF8|SQLITE_DETERMINISTIC) + SQLITE_SUBTYPE|SQLITE_RESULT_SUBTYPE|SQLITE_UTF8| + SQLITE_DETERMINISTIC) }; sqlite3InsertBuiltinFuncs(aJsonFunc, ArraySize(aJsonFunc)); #endif diff --git a/src/main.c b/src/main.c index fbe00f5fa9..6acfdc325d 100644 --- a/src/main.c +++ b/src/main.c @@ -1914,7 +1914,7 @@ int sqlite3CreateFunc( assert( SQLITE_FUNC_CONSTANT==SQLITE_DETERMINISTIC ); assert( SQLITE_FUNC_DIRECT==SQLITE_DIRECTONLY ); extraFlags = enc & (SQLITE_DETERMINISTIC|SQLITE_DIRECTONLY| - SQLITE_SUBTYPE|SQLITE_INNOCUOUS); + SQLITE_SUBTYPE|SQLITE_INNOCUOUS|SQLITE_RESULT_SUBTYPE); enc &= (SQLITE_FUNC_ENCMASK|SQLITE_ANY); /* The SQLITE_INNOCUOUS flag is the same bit as SQLITE_FUNC_UNSAFE. But diff --git a/src/os_unix.c b/src/os_unix.c index a33e6f4dff..cd3e0fc54d 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -4980,12 +4980,15 @@ static int unixShmLock( ** It is not permitted to block on the RECOVER lock. */ #ifdef SQLITE_ENABLE_SETLK_TIMEOUT - assert( (flags & SQLITE_SHM_UNLOCK) || pDbFd->iBusyTimeout==0 || ( - (ofst!=2) /* not RECOVER */ - && (ofst!=1 || (p->exclMask|p->sharedMask)==0) - && (ofst!=0 || (p->exclMask|p->sharedMask)<3) - && (ofst<3 || (p->exclMask|p->sharedMask)<(1<exclMask|p->sharedMask); + assert( (flags & SQLITE_SHM_UNLOCK) || pDbFd->iBusyTimeout==0 || ( + (ofst!=2) /* not RECOVER */ + && (ofst!=1 || lockMask==0 || lockMask==2) + && (ofst!=0 || lockMask<3) + && (ofst<3 || lockMask<(1<fd; } diff --git a/src/select.c b/src/select.c index 7a79385e0b..2b28d9ca5e 100644 --- a/src/select.c +++ b/src/select.c @@ -7397,6 +7397,7 @@ int sqlite3Select( TREETRACE(0x1000,pParse,p, ("LEFT-JOIN simplifies to JOIN on term %d\n",i)); pItem->fg.jointype &= ~(JT_LEFT|JT_OUTER); + unsetJoinExpr(p->pWhere, pItem->iCursor, 0); } } if( pItem->fg.jointype & JT_LTORJ ){ @@ -7411,17 +7412,15 @@ int sqlite3Select( TREETRACE(0x1000,pParse,p, ("RIGHT-JOIN simplifies to JOIN on term %d\n",j)); pI2->fg.jointype &= ~(JT_RIGHT|JT_OUTER); + unsetJoinExpr(p->pWhere, pI2->iCursor, 1); } } } - for(j=pTabList->nSrc-1; j>=i; j--){ + for(j=pTabList->nSrc-1; j>=0; j--){ pTabList->a[j].fg.jointype &= ~JT_LTORJ; if( pTabList->a[j].fg.jointype & JT_RIGHT ) break; } } - assert( pItem->iCursor>=0 ); - unsetJoinExpr(p->pWhere, pItem->iCursor, - pTabList->a[0].fg.jointype & JT_LTORJ); } /* No further action if this term of the FROM clause is not a subquery */ diff --git a/src/shell.c.in b/src/shell.c.in index a32c4f5d57..b3b97e1cec 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -566,12 +566,32 @@ static char *dynamicContinuePrompt(void){ #endif /* !defined(SQLITE_OMIT_DYNAPROMPT) */ /* From here onward, fgets() is redirected to the console_io library. */ -#define fgets(b,n,f) fgetsUtf8(b,n,f) +#define fgets(b,n,f) fGetsUtf8(b,n,f) /* Also, (varargs) utf8_printf(f,z,...),printf(z,...) so redirected. */ -#define utf8_printf fprintfUtf8 -#define printf printfUtf8 +#define utf8_printf fPrintfUtf8 +#define printf oPrintfUtf8 /* And, utf8_print(f,z) is redirected to fputsUtf8(z,f) in the library. */ -#define utf8_print(f,z) fputsUtf8(z,f) +#define utf8_print(f,z) fPutsUtf8(z,f) +/* + * Define macros for writing output text in various ways: + * sputz(s, z) => emit 0-terminated string z to given stream s + * sputf(s, f, ...) => emit varargs per format f to given stream s + * oputz(z) => emit 0-terminated string z to default stream + * oputf(f, ...) => emit varargs per format f to default stream + * eputz(z) => emit 0-terminated string z to error stream + * eputf(f, ...) => emit varargs per format f to error stream + * + * Note that the default stream is whatever has been last set via: + * setDefaultOutputStream(FILE *pf) + * This is normally the stream that CLI normal output goes to. + * For the stand-alone CLI, it is stdout with no .output redirect. + */ +#define sputz(s,z) fPutsUtf8(z,s) +#define sputf fPrintfUtf8 +#define oputz(z) oPutsUtf8(z) +#define oputf oPrintfUtf8 +#define eputz(z) ePutsUtf8(z) +#define eputf ePrintfUtf8 /* Indicate out-of-memory and exit. */ static void shell_out_of_memory(void){ @@ -697,7 +717,7 @@ static int strlenChar(const char *z){ ** Otherwise return 0. */ static FILE * openChrSource(const char *zFile){ -#ifdef _WIN32 +#if defined(_WIN32) || defined(WIN32) struct _stat x = {0}; # define STAT_CHR_SRC(mode) ((mode & (_S_IFCHR|_S_IFIFO|_S_IFREG))!=0) /* On Windows, open first, then check the stream nature. This order @@ -1033,7 +1053,7 @@ static void shellDtostr( char z[400]; if( n<1 ) n = 1; if( n>350 ) n = 350; - snprintf(z, sizeof(z)-1, "%#+.*e", n, r); + sqlite3_snprintf(sizeof(z), z, "%#+.*e", n, r); sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT); } @@ -7739,6 +7759,7 @@ FROM (\ sqlite3_exec(*pDb,"drop table if exists ColNames;" "drop view if exists RepeatedNames;",0,0,0); #endif +#undef SHELL_COLFIX_DB rc = sqlite3_exec(*pDb, zTabMake, 0, 0, 0); rc_err_oom_die(rc); } @@ -9796,8 +9817,14 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_db_config( p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->scanstatsOn, (int*)0 ); -#ifndef SQLITE_ENABLE_STMT_SCANSTATUS +#if !defined(SQLITE_ENABLE_STMT_SCANSTATUS) utf8_print(stderr, "Warning: .scanstats not available in this build.\n"); +#elif !defined(SQLITE_ENABLE_BYTECODE_VTAB) + if( p->scanstatsOn==3 ){ + raw_printf(stderr, + "Warning: \".scanstats vm\" not available in this build.\n" + ); + } #endif }else{ utf8_print(stderr, "Usage: .scanstats on|off|est\n"); @@ -10502,7 +10529,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } consoleRestore(); x = zCmd!=0 ? system(zCmd) : 1; - consoleClassifySetup(stdin, stdout, stderr); + consoleRenewSetup(); sqlite3_free(zCmd); if( x ) utf8_printf(stderr, "System command returns %d\n", x); }else @@ -11815,7 +11842,7 @@ static void main_init(ShellState *data) { /* ** Output text to the console in a font that attracts extra attention. */ -#ifdef _WIN32 +#if defined(_WIN32) || defined(WIN32) static void printBold(const char *zText){ #if !SQLITE_OS_WINRT HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); @@ -11825,14 +11852,14 @@ static void printBold(const char *zText){ FOREGROUND_RED|FOREGROUND_INTENSITY ); #endif - printf("%s", zText); + oputf("%s", zText); #if !SQLITE_OS_WINRT SetConsoleTextAttribute(out, defaultScreenInfo.wAttributes); #endif } #else static void printBold(const char *zText){ - printf("\033[1m%s\033[0m", zText); + oputf("\033[1m%s\033[0m", zText); } #endif @@ -11880,7 +11907,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ # define data shellState #else ShellState data; - ConsoleStdConsStreams csStreams = CSCS_NoConsole; + StreamsAreConsole consStreams = SAC_NoConsole; #endif const char *zInitFile = 0; int i; @@ -11902,9 +11929,9 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ stdout_is_console = 1; data.wasm.zDefaultDbName = "/fiddle.sqlite3"; #else - csStreams = consoleClassifySetup(stdin, stdout, stderr); - stdin_is_interactive = (csStreams & CSCS_InConsole)!=0; - stdout_is_console = (csStreams & CSCS_OutConsole)!=0; + consStreams = consoleClassifySetup(stdin, stdout, stderr); + stdin_is_interactive = (consStreams & SAC_InConsole)!=0; + stdout_is_console = (consStreams & SAC_OutConsole)!=0; atexit(consoleRestore); #endif atexit(sayAbnormalExit); @@ -12174,7 +12201,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ if( pVfs ){ sqlite3_vfs_register(pVfs, 1); }else{ - utf8_printf(stderr, "no such VFS: \"%s\"\n", zVfs); + eputf("no such VFS: \"%s\"\n", zVfs); exit(1); } } @@ -12184,7 +12211,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ data.pAuxDb->zDbFilename = ":memory:"; warnInmemoryDb = argc==1; #else - utf8_printf(stderr,"%s: Error: no database filename specified\n", Argv0); + eputf("%s: Error: no database filename specified\n", Argv0); return 1; #endif } @@ -12301,8 +12328,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ }else if( cli_strcmp(z,"-bail")==0 ){ /* No-op. The bail_on_error flag should already be set. */ }else if( cli_strcmp(z,"-version")==0 ){ - printf("%s %s (%d-bit)\n", sqlite3_libversion(), sqlite3_sourceid(), - 8*(int)sizeof(char*)); + oputf("%s %s (%d-bit)\n", sqlite3_libversion(), sqlite3_sourceid(), + 8*(int)sizeof(char*)); return 0; }else if( cli_strcmp(z,"-interactive")==0 ){ /* already handled */ @@ -12358,18 +12385,18 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ open_db(&data, 0); rc = shell_exec(&data, z, &zErrMsg); if( zErrMsg!=0 ){ - utf8_printf(stderr,"Error: %s\n", zErrMsg); + eputf("Error: %s\n", zErrMsg); if( bail_on_error ) return rc!=0 ? rc : 1; }else if( rc!=0 ){ - utf8_printf(stderr,"Error: unable to process SQL \"%s\"\n", z); + eputf("Error: unable to process SQL \"%s\"\n", z); if( bail_on_error ) return rc; } } #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) }else if( cli_strncmp(z, "-A", 2)==0 ){ if( nCmd>0 ){ - utf8_printf(stderr, "Error: cannot mix regular SQL or dot-commands" - " with \"%s\"\n", z); + eputf("Error: cannot mix regular SQL or dot-commands" + " with \"%s\"\n", z); return 1; } open_db(&data, OPEN_DB_ZIPFILE); @@ -12387,8 +12414,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ }else if( cli_strcmp(z,"-unsafe-testing")==0 ){ /* Acted upon in first pass. */ }else{ - utf8_printf(stderr,"%s: Error: unknown option: %s\n", Argv0, z); - utf8_print(stderr,"Use -help for a list of options.\n"); + eputf("%s: Error: unknown option: %s\n", Argv0, z); + eputz("Use -help for a list of options.\n"); return 1; } data.cMode = data.mode; @@ -12412,9 +12439,9 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ rc = shell_exec(&data, azCmd[i], &zErrMsg); if( zErrMsg || rc ){ if( zErrMsg!=0 ){ - utf8_printf(stderr,"Error: %s\n", zErrMsg); + eputf("Error: %s\n", zErrMsg); }else{ - utf8_printf(stderr,"Error: unable to process SQL: %s\n", azCmd[i]); + eputf("Error: unable to process SQL: %s\n", azCmd[i]); } sqlite3_free(zErrMsg); free(azCmd); @@ -12435,15 +12462,15 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ #elif SHELL_CON_TRANSLATE==2 zCharset = " (MBCS console I/O)"; #endif - printf( + oputf( "SQLite version %s %.19s%s\n" /*extra-version-info*/ "Enter \".help\" for usage hints.\n", sqlite3_libversion(), sqlite3_sourceid(), zCharset ); if( warnInmemoryDb ){ - printf("Connected to a "); + oputz("Connected to a "); printBold("transient in-memory database"); - printf(".\nUse \".open FILENAME\" to reopen on a " + oputz(".\nUse \".open FILENAME\" to reopen on a " "persistent database.\n"); } zHistory = getenv("SQLITE_HISTORY"); diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 01a7f4d4e0..53c037c3ba 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -5573,13 +5573,27 @@ int sqlite3_create_window_function( ** ** ** [[SQLITE_SUBTYPE]]

SQLITE_SUBTYPE
-** The SQLITE_SUBTYPE flag indicates to SQLite that a function may call +** The SQLITE_SUBTYPE flag indicates to SQLite that a function might call ** [sqlite3_value_subtype()] to inspect the sub-types of its arguments. -** Specifying this flag makes no difference for scalar or aggregate user -** functions. However, if it is not specified for a user-defined window -** function, then any sub-types belonging to arguments passed to the window -** function may be discarded before the window function is called (i.e. -** sqlite3_value_subtype() will always return 0). +** This flag instructs SQLite to omit some corner-case optimizations that +** might disrupt the operation of the [sqlite3_value_subtype()] function, +** causing it to return zero rather than the correct subtype(). +** SQL functions that invokes [sqlite3_value_subtype()] should have this +** property. If the SQLITE_SUBTYPE property is omitted, then the return +** value from [sqlite3_value_subtype()] might sometimes be zero even though +** a non-zero subtype was specified by the function argument expression. +** +** [[SQLITE_RESULT_SUBTYPE]]
SQLITE_RESULT_SUBTYPE
+** The SQLITE_RESULT_SUBTYPE flag indicates to SQLite that a function might call +** [sqlite3_result_subtype()] to cause a sub-type to be associated with its +** result. +** Every function that invokes [sqlite3_result_subtype()] should have this +** property. If it does not, then the call to [sqlite3_result_subtype()] +** might become a no-op if the function is used as term in an +** [expression index]. On the other hand, SQL functions that never invoke +** [sqlite3_result_subtype()] should avoid setting this property, as the +** purpose of this property is to disable certain optimizations that are +** incompatible with subtypes. **
** */ @@ -5587,6 +5601,7 @@ int sqlite3_create_window_function( #define SQLITE_DIRECTONLY 0x000080000 #define SQLITE_SUBTYPE 0x000100000 #define SQLITE_INNOCUOUS 0x000200000 +#define SQLITE_RESULT_SUBTYPE 0x001000000 /* ** CAPI3REF: Deprecated Functions @@ -5783,6 +5798,12 @@ int sqlite3_value_encoding(sqlite3_value*); ** information can be used to pass a limited amount of context from ** one SQL function to another. Use the [sqlite3_result_subtype()] ** routine to set the subtype for the return value of an SQL function. +** +** Every [application-defined SQL function] that invoke this interface +** should include the [SQLITE_SUBTYPE] property in the text +** encoding argument when the function is [sqlite3_create_function|registered]. +** If the [SQLITE_SUBTYPE] property is omitted, then sqlite3_value_subtype() +** might return zero instead of the upstream subtype in some corner cases. */ unsigned int sqlite3_value_subtype(sqlite3_value*); @@ -5913,14 +5934,22 @@ sqlite3 *sqlite3_context_db_handle(sqlite3_context*); **
  • ^(when sqlite3_set_auxdata() is invoked again on the same ** parameter)^, or **
  • ^(during the original sqlite3_set_auxdata() call when a memory -** allocation error occurs.)^ +** allocation error occurs.)^ +**
  • ^(during the original sqlite3_set_auxdata() call if the function +** is evaluated during query planning instead of during query execution, +** as sometimes happens with [SQLITE_ENABLE_STAT4].)^ ** -** Note the last bullet in particular. The destructor X in +** Note the last two bullets in particular. The destructor X in ** sqlite3_set_auxdata(C,N,P,X) might be called immediately, before the ** sqlite3_set_auxdata() interface even returns. Hence sqlite3_set_auxdata() ** should be called near the end of the function implementation and the ** function implementation should not make any use of P after -** sqlite3_set_auxdata() has been called. +** sqlite3_set_auxdata() has been called. Furthermore, a call to +** sqlite3_get_auxdata() that occurs immediately after a corresponding call +** to sqlite3_set_auxdata() might still return NULL if an out-of-memory +** condition occurred during the sqlite3_set_auxdata() call or if the +** function is being evaluated during query planning rather than during +** query execution. ** ** ^(In practice, auxiliary data is preserved between function calls for ** function parameters that are compile-time constants, including literal @@ -6194,6 +6223,20 @@ int sqlite3_result_zeroblob64(sqlite3_context*, sqlite3_uint64 n); ** higher order bits are discarded. ** The number of subtype bytes preserved by SQLite might increase ** in future releases of SQLite. +** +** Every [application-defined SQL function] that invokes this interface +** should include the [SQLITE_RESULT_SUBTYPE] property in its +** text encoding argument when the SQL function is +** [sqlite3_create_function|registered]. If the [SQLITE_RESULT_SUBTYPE] +** property is omitted from the function that invokes sqlite3_result_subtype(), +** then in some cases the sqlite3_result_subtype() might fail to set +** the result subtype. +** +** If SQLite is compiled with -DSQLITE_STRICT_SUBTYPE=1, then any +** SQL function that invokes the sqlite3_result_subtype() interface +** and that does not have the SQLITE_RESULT_SUBTYPE property will raise +** an error. Future versions of SQLite might enable -DSQLITE_STRICT_SUBTYPE=1 +** by default. */ void sqlite3_result_subtype(sqlite3_context*,unsigned int); diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 23beb48de3..bb61cb6916 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -2014,14 +2014,15 @@ struct FuncDestructor { #define SQLITE_FUNC_SLOCHNG 0x2000 /* "Slow Change". Value constant during a ** single query - might change over time */ #define SQLITE_FUNC_TEST 0x4000 /* Built-in testing functions */ -/* 0x8000 -- available for reuse */ +#define SQLITE_FUNC_RUNONLY 0x8000 /* Cannot be used by valueFromFunction */ #define SQLITE_FUNC_WINDOW 0x00010000 /* Built-in window-only function */ #define SQLITE_FUNC_INTERNAL 0x00040000 /* For use by NestedParse() only */ #define SQLITE_FUNC_DIRECT 0x00080000 /* Not for use in TRIGGERs or VIEWs */ -#define SQLITE_FUNC_SUBTYPE 0x00100000 /* Result likely to have sub-type */ +/* SQLITE_SUBTYPE 0x00100000 // Consumer of subtypes */ #define SQLITE_FUNC_UNSAFE 0x00200000 /* Function has side effects */ #define SQLITE_FUNC_INLINE 0x00400000 /* Functions implemented in-line */ #define SQLITE_FUNC_BUILTIN 0x00800000 /* This is a built-in function */ +/* SQLITE_RESULT_SUBTYPE 0x01000000 // Generator of subtypes */ #define SQLITE_FUNC_ANYORDER 0x08000000 /* count/min/max aggregate */ /* Identifier numbers for each in-line function */ @@ -2113,9 +2114,10 @@ struct FuncDestructor { #define MFUNCTION(zName, nArg, xPtr, xFunc) \ {nArg, SQLITE_FUNC_BUILTIN|SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \ xPtr, 0, xFunc, 0, 0, 0, #zName, {0} } -#define JFUNCTION(zName, nArg, iArg, xFunc) \ - {nArg, SQLITE_FUNC_BUILTIN|SQLITE_DETERMINISTIC|\ - SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \ +#define JFUNCTION(zName, nArg, bUseCache, bWS, bRS, iArg, xFunc) \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_DETERMINISTIC|SQLITE_FUNC_CONSTANT|\ + SQLITE_UTF8|((bUseCache)*SQLITE_FUNC_RUNONLY)|\ + ((bRS)*SQLITE_SUBTYPE)|((bWS)*SQLITE_RESULT_SUBTYPE), \ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } #define INLINE_FUNC(zName, nArg, iArg, mFlags) \ {nArg, SQLITE_FUNC_BUILTIN|\ diff --git a/src/treeview.c b/src/treeview.c index 1fad8673dd..2576532b65 100644 --- a/src/treeview.c +++ b/src/treeview.c @@ -781,7 +781,7 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ assert( pExpr->x.pList->nExpr==2 ); pY = pExpr->x.pList->a[0].pExpr; pZ = pExpr->x.pList->a[1].pExpr; - sqlite3TreeViewLine(pView, "BETWEEN"); + sqlite3TreeViewLine(pView, "BETWEEN%s", zFlgs); sqlite3TreeViewExpr(pView, pX, 1); sqlite3TreeViewExpr(pView, pY, 1); sqlite3TreeViewExpr(pView, pZ, 0); diff --git a/src/vdbe.c b/src/vdbe.c index 221e8847db..544c8d8457 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -2033,7 +2033,7 @@ case OP_AddImm: { /* in1 */ pIn1 = &aMem[pOp->p1]; memAboutToChange(p, pIn1); sqlite3VdbeMemIntegerify(pIn1); - pIn1->u.i += pOp->p2; + *(u64*)&pIn1->u.i += (u64)pOp->p2; break; } diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 6724035fd5..570cb3d8b6 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -539,6 +539,18 @@ void sqlite3_result_subtype(sqlite3_context *pCtx, unsigned int eSubtype){ #ifdef SQLITE_ENABLE_API_ARMOR if( pCtx==0 ) return; #endif +#if defined(SQLITE_STRICT_SUBTYPE) && SQLITE_STRICT_SUBTYPE+0!=0 + if( pCtx->pFunc!=0 + && (pCtx->pFunc->funcFlags & SQLITE_RESULT_SUBTYPE)==0 + ){ + char zErr[200]; + sqlite3_snprintf(sizeof(zErr), zErr, + "misuse of sqlite3_result_subtype() by %s()", + pCtx->pFunc->zName); + sqlite3_result_error(pCtx, zErr, -1); + return; + } +#endif /* SQLITE_STRICT_SUBTYPE */ pOut = pCtx->pOut; assert( sqlite3_mutex_held(pOut->db->mutex) ); pOut->eSubtype = eSubtype & 0xff; diff --git a/src/vdbeaux.c b/src/vdbeaux.c index 54317b816f..1d9921b193 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -1526,7 +1526,7 @@ static void SQLITE_NOINLINE vdbeChangeP4Full( int n ){ if( pOp->p4type ){ - freeP4(p->db, pOp->p4type, pOp->p4.p); + assert( pOp->p4type > P4_FREE_IF_LE ); pOp->p4type = 0; pOp->p4.p = 0; } diff --git a/src/vdbemem.c b/src/vdbemem.c index eee828143a..d527164685 100644 --- a/src/vdbemem.c +++ b/src/vdbemem.c @@ -1515,7 +1515,7 @@ static int valueFromFunction( #endif assert( pFunc ); if( (pFunc->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG))==0 - || (pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL) + || (pFunc->funcFlags & (SQLITE_FUNC_NEEDCOLL|SQLITE_FUNC_RUNONLY))!=0 ){ return SQLITE_OK; } diff --git a/src/wal.c b/src/wal.c index d63c13ffc4..655d78f591 100644 --- a/src/wal.c +++ b/src/wal.c @@ -4248,9 +4248,14 @@ int sqlite3WalCheckpoint( /* Read the wal-index header. */ SEH_TRY { if( rc==SQLITE_OK ){ + /* For a passive checkpoint, do not re-enable blocking locks after + ** reading the wal-index header. A passive checkpoint should not block + ** or invoke the busy handler. The only lock such a checkpoint may + ** attempt to obtain is a lock on a read-slot, and it should give up + ** immediately and do a partial checkpoint if it cannot obtain it. */ walDisableBlocking(pWal); rc = walIndexReadHdr(pWal, &isChanged); - (void)walEnableBlocking(pWal); + if( eMode2!=SQLITE_CHECKPOINT_PASSIVE ) (void)walEnableBlocking(pWal); if( isChanged && pWal->pDbFd->pMethods->iVersion>=3 ){ sqlite3OsUnfetch(pWal->pDbFd, 0, 0); } diff --git a/src/where.c b/src/where.c index 05ae24f7bc..cfd5d5e1a5 100644 --- a/src/where.c +++ b/src/where.c @@ -5810,6 +5810,20 @@ static SQLITE_NOINLINE void whereAddIndexedExpr( continue; } if( sqlite3ExprIsConstant(pExpr) ) continue; + if( pExpr->op==TK_FUNCTION ){ + /* Functions that might set a subtype should not be replaced by the + ** value taken from an expression index since the index omits the + ** subtype. https://sqlite.org/forum/forumpost/68d284c86b082c3e */ + int n; + FuncDef *pDef; + sqlite3 *db = pParse->db; + assert( ExprUseXList(pExpr) ); + n = pExpr->x.pList ? pExpr->x.pList->nExpr : 0; + pDef = sqlite3FindFunction(db, pExpr->u.zToken, n, ENC(db), 0); + if( pDef==0 || (pDef->funcFlags & SQLITE_RESULT_SUBTYPE)!=0 ){ + continue; + } + } p = sqlite3DbMallocRaw(pParse->db, sizeof(IndexedExpr)); if( p==0 ) break; p->pIENext = pParse->pIdxEpr; diff --git a/src/window.c b/src/window.c index 2c449592d7..62df349fb3 100644 --- a/src/window.c +++ b/src/window.c @@ -1038,7 +1038,7 @@ int sqlite3WindowRewrite(Parse *pParse, Select *p){ assert( ExprUseXList(pWin->pOwner) ); assert( pWin->pWFunc!=0 ); pArgs = pWin->pOwner->x.pList; - if( pWin->pWFunc->funcFlags & SQLITE_FUNC_SUBTYPE ){ + if( pWin->pWFunc->funcFlags & SQLITE_SUBTYPE ){ selectWindowRewriteEList(pParse, pMWin, pSrc, pArgs, pTab, &pSublist); pWin->iArgCol = (pSublist ? pSublist->nExpr : 0); pWin->bExprArgs = 1; diff --git a/test/date.test b/test/date.test index fb76dac8ac..19cecc2db3 100644 --- a/test/date.test +++ b/test/date.test @@ -146,6 +146,8 @@ datetest 2.49 {datetime('2003-10-22 12:24','0000 second')} {2003-10-22 12:24:00} datetest 2.50 {datetime('2003-10-22 12:24','0001 second')} {2003-10-22 12:24:01} datetest 2.51 {datetime('2003-10-22 12:24','nonsense')} NULL +datetest 2.60 {datetime('2023-02-31')} {2023-03-03 00:00:00} + datetest 3.1 {strftime('%d','2003-10-31 12:34:56.432')} 31 datetest 3.2.1 {strftime('pre%fpost','2003-10-31 12:34:56.432')} pre56.432post datetest 3.2.2 {strftime('%f','2003-10-31 12:34:59.9999999')} 59.999 @@ -452,6 +454,9 @@ datetest 13.31 {date('2001-01-01','+1.5 years')} {2002-07-02} datetest 13.32 {date('2002-01-01','+1.5 years')} {2003-07-02} datetest 13.33 {date('2002-01-01','-1.5 years')} {2000-07-02} datetest 13.34 {date('2001-01-01','-1.5 years')} {1999-07-02} +datetest 13.35 {date('2023-02-28')} {2023-02-28} +datetest 13.36 {date('2023-02-29')} {2023-03-01} +datetest 13.37 {date('2023-04-31')} {2023-05-01} # Test for issues reported by BareFeet (list.sql at tandb.com.au) # on mailing list on 2008-06-12. diff --git a/test/indexexpr1.test b/test/indexexpr1.test index 51ef73bbf5..0316ee9d42 100644 --- a/test/indexexpr1.test +++ b/test/indexexpr1.test @@ -616,4 +616,18 @@ do_execsql_test indexexpr1-2200 { ) v ON v.type = 0 AND v.tag = u.tag; } {7 100 8 101} +# 2023-11-08 Forum post https://sqlite.org/forum/forumpost/68d284c86b082c3e +# +# Functions that return subtypes and that are indexed cannot be used to +# cover function calls from the main table, since the indexed value does +# not know the subtype. +# +reset_db +do_execsql_test indexexpr1-2300 { + CREATE TABLE t1(x INT, y TEXT); + INSERT INTO t1(x,y) VALUES(1,'{b:5}'); + CREATE INDEX t1j ON t1(json(y)); + SELECT json_insert('{}', '$.a', json(y)) FROM t1; +} {{{"a":{"b":5}}}} + finish_test diff --git a/test/joinH.test b/test/joinH.test index 0fed7f2aa0..3702266804 100644 --- a/test/joinH.test +++ b/test/joinH.test @@ -250,5 +250,63 @@ do_catchsql_test 9.11 { SELECT oid FROM wo2 JOIN (wo3 JOIN x3) } {0 99} +reset_db +do_execsql_test 10.0 { + CREATE TABLE rt0 (c0 INTEGER, c1 INTEGER, c2 INTEGER, c3 INTEGER, c4 INTEGER); + CREATE TABLE rt3 (c3 INTEGER); + + INSERT INTO rt0(c3, c1) VALUES (x'', '1'); + INSERT INTO rt0(c3, c1) VALUES ('-1', -1e500); + INSERT INTO rt0(c3, c1) VALUES (1, x''); + + CREATE VIEW v6(c0, c1, c2) AS SELECT 0, 0, 0; +} + +do_execsql_test 10.1 { + SELECT COUNT(*) FROM rt0 LEFT JOIN rt3 JOIN v6 ON ((CASE v6.c0 WHEN rt0.c4 THEN rt3.c3 END) NOT BETWEEN (rt0.c4) AND (NULL)) WHERE (rt0.c1); -- 2 +} {0} + +do_execsql_test 10.2 { + SELECT COUNT(*) FROM rt0 LEFT JOIN rt3 RIGHT OUTER JOIN v6 ON ((CASE v6.c0 WHEN rt0.c4 THEN rt3.c3 END) NOT BETWEEN (rt0.c4) AND (NULL)) WHERE (rt0.c1); -- 2 +} {0} + +#------------------------------------------------------------------------- + +do_execsql_test 11.1 { + CREATE TABLE t1(a, b); + CREATE TABLE t2(c, d); + CREATE TABLE t3(e, f); + + INSERT INTO t1 VALUES(1, 1); + INSERT INTO t2 VALUES(2, 2); + INSERT INTO t3 VALUES(3, 3); +} + +do_execsql_test 11.2 { + SELECT * FROM t1 LEFT JOIN t2 RIGHT JOIN t3 ON (t2.c=10) +} {{} {} {} {} 3 3} + +do_execsql_test 11.3 { + SELECT * FROM t1 LEFT JOIN t2 RIGHT JOIN t3 ON (t2.c=10) WHERE t1.a=1 +} {} + +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 12.1 { + CREATE TABLE t1(a1 INT, b1 TEXT); + INSERT INTO t1 VALUES(88,''); + CREATE TABLE t2(c2 INT, d2 TEXT); + INSERT INTO t2 VALUES(88,''); + CREATE TABLE t3(e3 TEXT PRIMARY KEY); + INSERT INTO t3 VALUES(''); +} + +do_execsql_test 12.2 { + SELECT * FROM t1 LEFT JOIN t2 ON true RIGHT JOIN t3 ON d2=e3 WHERE c2 BETWEEN NULL AND a1; +} +do_execsql_test 12.3 { + SELECT * FROM t1 LEFT JOIN t2 ON true RIGHT JOIN t3 ON d2=e3 WHERE c2 BETWEEN NULL AND a1; +} finish_test diff --git a/test/json501.test b/test/json501.test index a37326973a..3318eea7f2 100644 --- a/test/json501.test +++ b/test/json501.test @@ -300,5 +300,10 @@ do_execsql_test 12.4 { || ' "xyz"}')->>'a'; } xyz +# 2023-11-08 forum/forumpost/ddcad3e884 +# +do_execsql_test 13.1 { + SELECT json('{x:''a "b" c''}'); +} {{{"x":"a \"b\" c"}}} finish_test diff --git a/test/releasetest_data.tcl b/test/releasetest_data.tcl deleted file mode 100644 index 4ed57a9c8e..0000000000 --- a/test/releasetest_data.tcl +++ /dev/null @@ -1,845 +0,0 @@ -# 2019 August 01 -# -# The author disclaims copyright to this source code. In place of -# a legal notice, here is a blessing: -# -# May you do good and not evil. -# May you find forgiveness for yourself and forgive others. -# May you share freely, never taking more than you give. -# -#*********************************************************************** -# -# This file implements a program that produces scripts (either shell scripts -# or batch files) to implement a particular test that is part of the SQLite -# release testing procedure. For example, to run veryquick.test with a -# specified set of -D compiler switches. -# -# A "configuration" is a set of options passed to [./configure] and [make] -# to build the SQLite library in a particular fashion. A "platform" is a -# list of tests; most platforms are named after the hardware/OS platform -# that the tests will be run on as part of the release procedure. Each -# "test" is a combination of a configuration and a makefile target (e.g. -# "fulltest"). The program may be invoked as follows: -# -set USAGE { -$argv0 script ?-msvc? CONFIGURATION TARGET - Given a configuration and make target, return a bash (or, if -msvc - is specified, batch) script to execute the test. The first argument - passed to the script must be a directory containing SQLite source code. - -$argv0 configurations - List available configurations. - -$argv0 platforms - List available platforms. - -$argv0 tests ?-nodebug? PLATFORM - List tests in a specified platform. If the -nodebug switch is - specified, synthetic debug/ndebug configurations are omitted. Each - test is a combination of a configuration and a makefile target. -} - -# Omit comments (text between # and \n) in a long multi-line string. -# -proc strip_comments {in} { - regsub -all {#[^\n]*\n} $in {} out - return $out -} - -array set ::Configs [strip_comments { - "Default" { - -O2 - --disable-amalgamation --disable-shared - --enable-session - -DSQLITE_ENABLE_RBU - } - "All-Debug" { - --enable-debug --enable-all - } - "All-O0" { - -O0 --enable-all - } - "Sanitize" { - CC=clang -fsanitize=address,undefined - -DSQLITE_ENABLE_STAT4 - -DCONFIG_SLOWDOWN_FACTOR=5.0 - --enable-debug - --enable-all - } - "Stdcall" { - -DUSE_STDCALL=1 - -O2 - } - "Have-Not" { - # The "Have-Not" configuration sets all possible -UHAVE_feature options - # in order to verify that the code works even on platforms that lack - # these support services. - -DHAVE_FDATASYNC=0 - -DHAVE_GMTIME_R=0 - -DHAVE_ISNAN=0 - -DHAVE_LOCALTIME_R=0 - -DHAVE_LOCALTIME_S=0 - -DHAVE_MALLOC_USABLE_SIZE=0 - -DHAVE_STRCHRNUL=0 - -DHAVE_USLEEP=0 - -DHAVE_UTIME=0 - } - "Unlock-Notify" { - -O2 - -DSQLITE_ENABLE_UNLOCK_NOTIFY - -DSQLITE_THREADSAFE - -DSQLITE_TCL_DEFAULT_FULLMUTEX=1 - } - "User-Auth" { - -O2 - -DSQLITE_USER_AUTHENTICATION=1 - } - "Secure-Delete" { - -O2 - -DSQLITE_SECURE_DELETE=1 - -DSQLITE_SOUNDEX=1 - } - "Update-Delete-Limit" { - -O2 - -DSQLITE_DEFAULT_FILE_FORMAT=4 - -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1 - -DSQLITE_ENABLE_STMT_SCANSTATUS - -DSQLITE_LIKE_DOESNT_MATCH_BLOBS - -DSQLITE_ENABLE_CURSOR_HINTS - } - "Check-Symbols" { - -DSQLITE_MEMDEBUG=1 - -DSQLITE_ENABLE_FTS3_PARENTHESIS=1 - -DSQLITE_ENABLE_FTS3=1 - -DSQLITE_ENABLE_RTREE=1 - -DSQLITE_ENABLE_MEMSYS5=1 - -DSQLITE_ENABLE_MEMSYS3=1 - -DSQLITE_ENABLE_COLUMN_METADATA=1 - -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1 - -DSQLITE_SECURE_DELETE=1 - -DSQLITE_SOUNDEX=1 - -DSQLITE_ENABLE_ATOMIC_WRITE=1 - -DSQLITE_ENABLE_MEMORY_MANAGEMENT=1 - -DSQLITE_ENABLE_OVERSIZE_CELL_CHECK=1 - -DSQLITE_ENABLE_STAT4 - -DSQLITE_ENABLE_STMT_SCANSTATUS - --enable-fts5 --enable-session - } - "Debug-One" { - --disable-shared - -O2 -funsigned-char - -DSQLITE_DEBUG=1 - -DSQLITE_MEMDEBUG=1 - -DSQLITE_MUTEX_NOOP=1 - -DSQLITE_TCL_DEFAULT_FULLMUTEX=1 - -DSQLITE_ENABLE_FTS3=1 - -DSQLITE_ENABLE_RTREE=1 - -DSQLITE_ENABLE_MEMSYS5=1 - -DSQLITE_ENABLE_COLUMN_METADATA=1 - -DSQLITE_ENABLE_STAT4 - -DSQLITE_ENABLE_HIDDEN_COLUMNS - -DSQLITE_MAX_ATTACHED=125 - -DSQLITE_MUTATION_TEST - --enable-fts5 - } - "Debug-Two" { - -DSQLITE_DEFAULT_MEMSTATUS=0 - -DSQLITE_MAX_EXPR_DEPTH=0 - --enable-debug - } - "Fast-One" { - -O6 - -DSQLITE_ENABLE_FTS4=1 - -DSQLITE_ENABLE_RTREE=1 - -DSQLITE_ENABLE_STAT4 - -DSQLITE_ENABLE_RBU - -DSQLITE_MAX_ATTACHED=125 - -DSQLITE_MAX_MMAP_SIZE=12884901888 - -DSQLITE_ENABLE_SORTER_MMAP=1 - -DLONGDOUBLE_TYPE=double - --enable-session - } - "Device-One" { - -O2 - -DSQLITE_DEBUG=1 - -DSQLITE_DEFAULT_AUTOVACUUM=1 - -DSQLITE_DEFAULT_CACHE_SIZE=64 - -DSQLITE_DEFAULT_PAGE_SIZE=1024 - -DSQLITE_DEFAULT_TEMP_CACHE_SIZE=32 - -DSQLITE_DISABLE_LFS=1 - -DSQLITE_ENABLE_ATOMIC_WRITE=1 - -DSQLITE_ENABLE_IOTRACE=1 - -DSQLITE_ENABLE_MEMORY_MANAGEMENT=1 - -DSQLITE_MAX_PAGE_SIZE=4096 - -DSQLITE_OMIT_LOAD_EXTENSION=1 - -DSQLITE_OMIT_PROGRESS_CALLBACK=1 - -DSQLITE_OMIT_VIRTUALTABLE=1 - -DSQLITE_ENABLE_HIDDEN_COLUMNS - -DSQLITE_TEMP_STORE=3 - } - "Device-Two" { - -DSQLITE_4_BYTE_ALIGNED_MALLOC=1 - -DSQLITE_DEFAULT_AUTOVACUUM=1 - -DSQLITE_DEFAULT_CACHE_SIZE=1000 - -DSQLITE_DEFAULT_LOCKING_MODE=0 - -DSQLITE_DEFAULT_PAGE_SIZE=1024 - -DSQLITE_DEFAULT_TEMP_CACHE_SIZE=1000 - -DSQLITE_DISABLE_LFS=1 - -DSQLITE_ENABLE_FTS3=1 - -DSQLITE_ENABLE_MEMORY_MANAGEMENT=1 - -DSQLITE_ENABLE_RTREE=1 - -DSQLITE_MAX_COMPOUND_SELECT=50 - -DSQLITE_MAX_PAGE_SIZE=32768 - -DSQLITE_OMIT_TRACE=1 - -DSQLITE_TEMP_STORE=3 - -DSQLITE_THREADSAFE=2 - --enable-fts5 --enable-session - } - "Locking-Style" { - -O2 - -DSQLITE_ENABLE_LOCKING_STYLE=1 - } - "Apple" { - -Os - -DHAVE_GMTIME_R=1 - -DHAVE_ISNAN=1 - -DHAVE_LOCALTIME_R=1 - -DHAVE_PREAD=1 - -DHAVE_PWRITE=1 - -DHAVE_UTIME=1 - -DSQLITE_DEFAULT_CACHE_SIZE=1000 - -DSQLITE_DEFAULT_CKPTFULLFSYNC=1 - -DSQLITE_DEFAULT_MEMSTATUS=1 - -DSQLITE_DEFAULT_PAGE_SIZE=1024 - -DSQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS=1 - -DSQLITE_ENABLE_API_ARMOR=1 - -DSQLITE_ENABLE_AUTO_PROFILE=1 - -DSQLITE_ENABLE_FLOCKTIMEOUT=1 - -DSQLITE_ENABLE_FTS3=1 - -DSQLITE_ENABLE_FTS3_PARENTHESIS=1 - -DSQLITE_ENABLE_FTS3_TOKENIZER=1 - -DSQLITE_ENABLE_PERSIST_WAL=1 - -DSQLITE_ENABLE_PURGEABLE_PCACHE=1 - -DSQLITE_ENABLE_RTREE=1 - -DSQLITE_ENABLE_SNAPSHOT=1 - # -DSQLITE_ENABLE_SQLLOG=1 - -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1 - -DSQLITE_MAX_LENGTH=2147483645 - -DSQLITE_MAX_VARIABLE_NUMBER=500000 - # -DSQLITE_MEMDEBUG=1 - -DSQLITE_NO_SYNC=1 - -DSQLITE_OMIT_AUTORESET=1 - -DSQLITE_OMIT_LOAD_EXTENSION=1 - -DSQLITE_PREFER_PROXY_LOCKING=1 - -DSQLITE_SERIES_CONSTRAINT_VERIFY=1 - -DSQLITE_THREADSAFE=2 - -DSQLITE_USE_URI=1 - -DSQLITE_WRITE_WALFRAME_PREBUFFERED=1 - -DUSE_GUARDED_FD=1 - -DUSE_PREAD=1 - --enable-fts5 - } - "Extra-Robustness" { - -DSQLITE_ENABLE_OVERSIZE_CELL_CHECK=1 - -DSQLITE_MAX_ATTACHED=62 - } - "Devkit" { - -DSQLITE_DEFAULT_FILE_FORMAT=4 - -DSQLITE_MAX_ATTACHED=30 - -DSQLITE_ENABLE_COLUMN_METADATA - -DSQLITE_ENABLE_FTS4 - -DSQLITE_ENABLE_FTS5 - -DSQLITE_ENABLE_FTS4_PARENTHESIS - -DSQLITE_DISABLE_FTS4_DEFERRED - -DSQLITE_ENABLE_RTREE - --enable-fts5 - } - "No-lookaside" { - -DSQLITE_TEST_REALLOC_STRESS=1 - -DSQLITE_OMIT_LOOKASIDE=1 - } - "Valgrind" { - -DSQLITE_ENABLE_STAT4 - -DSQLITE_ENABLE_FTS4 - -DSQLITE_ENABLE_RTREE - -DSQLITE_ENABLE_HIDDEN_COLUMNS - -DLONGDOUBLE_TYPE=double - -DCONFIG_SLOWDOWN_FACTOR=8.0 - } - - "Windows-Memdebug" { - MEMDEBUG=1 - DEBUG=3 - } - "Windows-Win32Heap" { - WIN32HEAP=1 - DEBUG=4 - } - - # The next group of configurations are used only by the - # Failure-Detection platform. They are all the same, but we need - # different names for them all so that they results appear in separate - # subdirectories. - # - Fail0 {-O0} - Fail2 {-O0} - Fail3 {-O0} - Fail4 {-O0} - FuzzFail1 {-O0} - FuzzFail2 {-O0} -}] -if {$tcl_platform(os)=="Darwin"} { - lappend Configs(Apple) -DSQLITE_ENABLE_LOCKING_STYLE=1 -} - -array set ::Platforms [strip_comments { - Linux-x86_64 { - "Check-Symbols*" "" checksymbols - "Fast-One" QUICKTEST_INCLUDE=rbu.test "fuzztest test" - "Debug-One" "" "mptest test" - "Debug-Two" "" test - "Have-Not" "" test - "Secure-Delete" "" test - "Unlock-Notify" QUICKTEST_INCLUDE=notify2.test test - "User-Auth" "" tcltest - "Update-Delete-Limit" "" test - "Extra-Robustness" "" test - "Device-Two" "" "threadtest test" - "No-lookaside" "" test - "Devkit" "" test - "Apple" "" test - "Sanitize*" "" test - "Device-One" "" alltest - "Default" "" "threadtest fuzztest alltest" - "Valgrind*" "" valgrindtest - } - Linux-i686 { - "Devkit" "" test - "Have-Not" "" test - "Unlock-Notify" QUICKTEST_INCLUDE=notify2.test test - "Device-One" "" test - "Device-Two" "" test - "Default" "" "threadtest fuzztest alltest" - } - Darwin-i386 { - "Locking-Style" "" "mptest test" - "Have-Not" "" test - "Apple" "" "threadtest fuzztest alltest" - } - Darwin-x86_64 { - "Locking-Style" "" "mptest test" - "Have-Not" "" test - "Apple" "" "threadtest fuzztest alltest" - } - Darwin-arm64 { - "Locking-Style" "" "mptest test" - "Have-Not" "" test - "Apple" "" "threadtest fuzztest alltest" - } - "Windows NT-intel" { - "Stdcall" "" test - "Have-Not" "" test - "Windows-Memdebug*" "" test - "Windows-Win32Heap*" "" test - "Default" "" "mptest fulltestonly" - } - "Windows NT-amd64" { - "Stdcall" "" test - "Have-Not" "" test - "Windows-Memdebug*" "" test - "Windows-Win32Heap*" "" test - "Default" "" "mptest fulltestonly" - } - - # The Failure-Detection platform runs various tests that deliberately - # fail. This is used as a test of this script to verify that this script - # correctly identifies failures. - # - Failure-Detection { - Fail0* "TEST_FAILURE=0" test - Sanitize* "TEST_FAILURE=1" test - Fail2* "TEST_FAILURE=2" valgrindtest - Fail3* "TEST_FAILURE=3" valgrindtest - Fail4* "TEST_FAILURE=4" test - FuzzFail1* "TEST_FAILURE=5" test - FuzzFail2* "TEST_FAILURE=5" valgrindtest - } -}] - -#-------------------------------------------------------------------------- -#-------------------------------------------------------------------------- -#-------------------------------------------------------------------------- -# End of configuration section. -#-------------------------------------------------------------------------- -#-------------------------------------------------------------------------- -#-------------------------------------------------------------------------- - -# Configuration verification: Check that each entry in the list of configs -# specified for each platforms exists. -# -foreach {key value} [array get ::Platforms] { - foreach {v vars t} $value { - if {[string range $v end end]=="*"} { - set v [string range $v 0 end-1] - } - if {0==[info exists ::Configs($v)]} { - puts stderr "No such configuration: \"$v\"" - exit -1 - } - } -} - -proc usage {} { - global argv0 - puts stderr [subst $::USAGE] - exit 1 -} - -proc is_prefix {p str min} { - set n [string length $p] - if {$n<$min} { return 0 } - if {[string range $str 0 [expr $n-1]]!=$p} { return 0 } - return 1 -} - -proc main_configurations {} { - foreach k [lsort [array names ::Configs]] { - puts $k - } -} - -proc main_platforms {} { - foreach k [lsort [array names ::Platforms]] { - puts "\"$k\"" - } -} - -proc main_script {args} { - set bMsvc 0 - set nArg [llength $args] - if {$nArg==3} { - if {![is_prefix [lindex $args 0] -msvc 2]} usage - set bMsvc 1 - } elseif {$nArg<2 || $nArg>3} { - usage - } - set config [lindex $args end-1] - set target [lindex $args end] - - set opts [list] ;# OPTS value - set cflags [expr {$bMsvc ? "-Zi" : "-g"}] ;# CFLAGS value - set makeOpts [list] ;# Extra args for [make] - set configOpts [list] ;# Extra args for [configure] - - if {$::tcl_platform(platform)=="windows" || $bMsvc} { - lappend opts -DSQLITE_OS_WIN=1 - } else { - lappend opts -DSQLITE_OS_UNIX=1 - } - - # Figure out if this is a synthetic ndebug or debug configuration. - # - set bRemoveDebug 0 - if {[string match *-ndebug $config]} { - set bRemoveDebug 1 - set config [string range $config 0 end-7] - } - if {[string match *-debug $config]} { - lappend opts -DSQLITE_DEBUG - lappend opts -DSQLITE_EXTRA_IFNULLROW - set config [string range $config 0 end-6] - } - regexp {^(.*)-[0-9]+} $config -> config - - # Ensure that the named configuration exists. - # - if {![info exists ::Configs($config)]} { - puts stderr "No such config: $config" - exit 1 - } - - # Loop through the parameters of the nominated configuration, updating - # $opts, $cflags, $makeOpts and $configOpts along the way. Rules are as - # follows: - # - # 1. If the parameter begins with a "*", discard it. - # - # 2. If $bRemoveDebug is set and the parameter is -DSQLITE_DEBUG or - # -DSQLITE_DEBUG=1, discard it - # - # 3. If the parameter begins with "-D", add it to $opts. - # - # 4. If the parameter begins with "--" add it to $configOpts. Unless - # this command is preparing a script for MSVC - then add an - # equivalent to $makeOpts or $opts. - # - # 5. If the parameter begins with "-" add it to $cflags. If in MSVC - # mode and the parameter is an -O option, instead add - # an OPTIMIZATIONS= switch to $makeOpts. - # - # 6. If none of the above apply, add the parameter to $makeOpts - # - foreach param $::Configs($config) { - if {[string range $param 0 0]=="*"} continue - - if {$bRemoveDebug} { - if {$param=="-DSQLITE_DEBUG" || $param=="-DSQLITE_DEBUG=1" - || $param=="-DSQLITE_MEMDEBUG" || $param=="-DSQLITE_MEMDEBUG=1" - || $param=="--enable-debug" - } { - continue - } - } - - if {[string range $param 0 1]=="-D"} { - lappend opts $param - continue - } - - if {[string range $param 0 1]=="--"} { - if {$bMsvc} { - switch -- $param { - --disable-amalgamation { - lappend makeOpts USE_AMALGAMATION=0 - } - --disable-shared { - lappend makeOpts USE_CRT_DLL=0 DYNAMIC_SHELL=0 - } - --enable-fts5 { - lappend opts -DSQLITE_ENABLE_FTS5 - } - --enable-shared { - lappend makeOpts USE_CRT_DLL=1 DYNAMIC_SHELL=1 - } - --enable-session { - lappend opts -DSQLITE_ENABLE_PREUPDATE_HOOK - lappend opts -DSQLITE_ENABLE_SESSION - } - default { - error "Cannot translate $param for MSVC" - } - } - } else { - lappend configOpts $param - } - - continue - } - - if {[string range $param 0 0]=="-"} { - if {$bMsvc && [regexp -- {^-O(\d+)$} $param -> level]} { - lappend makeOpts OPTIMIZATIONS=$level - } else { - lappend cflags $param - } - continue - } - - lappend makeOpts $param - } - - # Some configurations specify -DHAVE_USLEEP=0. For all others, add - # -DHAVE_USLEEP=1. - # - if {[lsearch $opts "-DHAVE_USLEEP=0"]<0} { - lappend opts -DHAVE_USLEEP=1 - } - - if {$bMsvc==0} { - puts {set -e} - puts {} - puts {if [ "$#" -ne 1 ] ; then} - puts { echo "Usage: $0 " } - puts { exit -1 } - puts {fi } - puts {SRCDIR=$1} - puts {} - puts "TCL=\"[::tcl::pkgconfig get libdir,install]\"" - - puts "\$SRCDIR/configure --with-tcl=\$TCL $configOpts" - puts {} - puts {OPTS=" -DSQLITE_NO_SYNC=1"} - foreach o $opts { - puts "OPTS=\"\$OPTS $o\"" - } - puts {} - puts "CFLAGS=\"$cflags\"" - puts {} - puts "make $target \"CFLAGS=\$CFLAGS\" \"OPTS=\$OPTS\" $makeOpts" - } else { - - puts {set SRCDIR=%1} - set makecmd "nmake /f %SRCDIR%\\Makefile.msc TOP=%SRCDIR% $target " - append makecmd "\"CFLAGS=$cflags\" \"OPTS=$opts\" $makeOpts" - - puts "set TMP=%CD%" - puts $makecmd - } -} - -proc main_trscript {args} { - set bMsvc 0 - set nArg [llength $args] - if {$nArg==3} { - if {![is_prefix [lindex $args 0] -msvc 2]} usage - set bMsvc 1 - } elseif {$nArg<2 || $nArg>3} { - usage - } - set config [lindex $args end-1] - set srcdir [lindex $args end] - - set opts [list] ;# OPTS value - set cflags [expr {$bMsvc ? "-Zi" : "-g"}] ;# CFLAGS value - set makeOpts [list] ;# Extra args for [make] - set configOpts [list] ;# Extra args for [configure] - - if {$::tcl_platform(platform)=="windows" || $bMsvc} { - lappend opts -DSQLITE_OS_WIN=1 - } else { - lappend opts -DSQLITE_OS_UNIX=1 - } - - # Figure out if this is a synthetic ndebug or debug configuration. - # - set bRemoveDebug 0 - if {[string match *-ndebug $config]} { - set bRemoveDebug 1 - set config [string range $config 0 end-7] - } - if {[string match *-debug $config]} { - lappend opts -DSQLITE_DEBUG - lappend opts -DSQLITE_EXTRA_IFNULLROW - set config [string range $config 0 end-6] - } - regexp {^(.*)-[0-9]+} $config -> config - - # Ensure that the named configuration exists. - # - if {![info exists ::Configs($config)]} { - puts stderr "No such config: $config" - exit 1 - } - - # Loop through the parameters of the nominated configuration, updating - # $opts, $cflags, $makeOpts and $configOpts along the way. Rules are as - # follows: - # - # 1. If the parameter begins with a "*", discard it. - # - # 2. If $bRemoveDebug is set and the parameter is -DSQLITE_DEBUG or - # -DSQLITE_DEBUG=1, discard it - # - # 3. If the parameter begins with "-D", add it to $opts. - # - # 4. If the parameter begins with "--" add it to $configOpts. Unless - # this command is preparing a script for MSVC - then add an - # equivalent to $makeOpts or $opts. - # - # 5. If the parameter begins with "-" add it to $cflags. If in MSVC - # mode and the parameter is an -O option, instead add - # an OPTIMIZATIONS= switch to $makeOpts. - # - # 6. If none of the above apply, add the parameter to $makeOpts - # - foreach param $::Configs($config) { - if {[string range $param 0 0]=="*"} continue - - if {$bRemoveDebug} { - if {$param=="-DSQLITE_DEBUG" || $param=="-DSQLITE_DEBUG=1" - || $param=="-DSQLITE_MEMDEBUG" || $param=="-DSQLITE_MEMDEBUG=1" - || $param=="--enable-debug" - } { - continue - } - } - - if {[string range $param 0 1]=="-D"} { - lappend opts $param - continue - } - - if {[string range $param 0 1]=="--"} { - if {$bMsvc} { - switch -- $param { - --disable-amalgamation { - lappend makeOpts USE_AMALGAMATION=0 - } - --disable-shared { - lappend makeOpts USE_CRT_DLL=0 DYNAMIC_SHELL=0 - } - --enable-fts5 { - lappend opts -DSQLITE_ENABLE_FTS5 - } - --enable-shared { - lappend makeOpts USE_CRT_DLL=1 DYNAMIC_SHELL=1 - } - --enable-session { - lappend opts -DSQLITE_ENABLE_PREUPDATE_HOOK - lappend opts -DSQLITE_ENABLE_SESSION - } - --enable-all { - } - --enable-debug { - # lappend makeOpts OPTIMIZATIONS=0 - lappend opts -DSQLITE_DEBUG - } - default { - error "Cannot translate $param for MSVC" - } - } - } else { - lappend configOpts $param - } - - continue - } - - if {[string range $param 0 0]=="-"} { - if {$bMsvc && [regexp -- {^-O(\d+)$} $param -> level]} { - lappend makeOpts OPTIMIZATIONS=$level - } else { - lappend cflags $param - } - continue - } - - lappend makeOpts $param - } - - # Some configurations specify -DHAVE_USLEEP=0. For all others, add - # -DHAVE_USLEEP=1. - # - if {[lsearch $opts "-DHAVE_USLEEP=0"]<0} { - lappend opts -DHAVE_USLEEP=1 - } - - if {$bMsvc==0} { - puts {set -e} - puts {} - puts {if [ "$#" -ne 1 ] ; then} - puts { echo "Usage: $0 " } - puts { exit -1 } - puts {fi } - puts "SRCDIR=\"$srcdir\"" - puts {} - puts "TCL=\"[::tcl::pkgconfig get libdir,install]\"" - - puts {if [ ! -f Makefile ] ; then} - puts " \$SRCDIR/configure --with-tcl=\$TCL $configOpts" - puts {fi} - puts {} - if {[info exists ::env(OPTS)]} { - puts "# From environment variable:" - puts "OPTS=$::env(OPTS)" - puts "" - } - puts {OPTS="$OPTS -DSQLITE_NO_SYNC=1"} - foreach o $opts { - puts "OPTS=\"\$OPTS $o\"" - } - puts {} - puts "CFLAGS=\"$cflags\"" - puts {} - puts "make \$1 \"CFLAGS=\$CFLAGS\" \"OPTS=\$OPTS\" $makeOpts" - } else { - - set srcdir [file nativename [file normalize $srcdir]] - # set srcdir [string map [list "\\" "\\\\"] $srcdir] - - puts {set TARGET=%1} - set makecmd "nmake /f $srcdir\\Makefile.msc TOP=\"$srcdir\" %TARGET% " - append makecmd "\"CFLAGS=$cflags\" \"OPTS=$opts\" $makeOpts" - - puts "set TMP=%CD%" - puts $makecmd - } -} - -proc main_tests {args} { - set bNodebug 0 - set nArg [llength $args] - if {$nArg==2} { - if {[is_prefix [lindex $args 0] -nodebug 2]} { - set bNodebug 1 - } elseif {[is_prefix [lindex $args 0] -debug 2]} { - set bNodebug 0 - } else usage - } elseif {$nArg==0 || $nArg>2} { - usage - } - set p [lindex $args end] - if {![info exists ::Platforms($p)]} { - puts stderr "No such platform: $p" - exit 1 - } - - set lTest [list] - - foreach {config vars target} $::Platforms($p) { - if {[string range $config end end]=="*"} { - set config [string range $config 0 end-1] - } elseif {$bNodebug==0} { - set dtarget test - if {[lsearch $target fuzztest]<0 && [lsearch $target test]<0} { - set dtarget tcltest - } - if {$vars!=""} { set dtarget "$vars $dtarget" } - - if {[string first SQLITE_DEBUG $::Configs($config)]>=0 - || [string first --enable-debug $::Configs($config)]>=0 - } { - lappend lTest "$config-ndebug \"$dtarget\"" - } else { - lappend lTest "$config-debug \"$dtarget\"" - } - } - - if {[llength $target]==1 && ([string match "*TEST_FAILURE*" $vars] || ( - [lsearch $target "valgrindtest"]<0 - && [lsearch $target "alltest"]<0 - && [lsearch $target "fulltestonly"]<0 - && ![string match Sanitize* $config] - ))} { - if {$vars!=""} { set target "$vars $target" } - lappend lTest "$config \"$target\"" - } else { - set idir -1 - foreach t $target { - if {$t=="valgrindtest" || $t=="alltest" || $t=="fulltestonly" - || [string match Sanitize* $config] - } { - if {$vars!=""} { set t "$vars $t" } - for {set ii 1} {$ii<=4} {incr ii} { - lappend lTest "$config-[incr idir] \"TCLTEST_PART=$ii/4 $t\"" - } - } else { - if {$vars!=""} { set t "$vars $t" } - lappend lTest "$config-[incr idir] \"$t\"" - } - } - } - } - - foreach l $lTest { - puts $l - } - -} - -if {[llength $argv]==0} { usage } -set cmd [lindex $argv 0] -set n [expr [llength $argv]-1] -if {[string match ${cmd}* configurations] && $n==0} { - main_configurations -} elseif {[string match ${cmd}* script]} { - main_script {*}[lrange $argv 1 end] -} elseif {[string match ${cmd}* trscript]} { - main_trscript {*}[lrange $argv 1 end] -} elseif {[string match ${cmd}* platforms] && $n==0} { - main_platforms -} elseif {[string match ${cmd}* tests]} { - main_tests {*}[lrange $argv 1 end] -} else { - usage -} diff --git a/test/snapshot_up.test b/test/snapshot_up.test index de8e5afab4..5f389e7be5 100644 --- a/test/snapshot_up.test +++ b/test/snapshot_up.test @@ -27,6 +27,8 @@ if {[permutation]=="inmemory_journal"} { return } +db timeout 1000 + do_execsql_test 1.0 { CREATE TABLE t1(a, b, c); PRAGMA journal_mode = wal; diff --git a/test/testrunner_data.tcl b/test/testrunner_data.tcl index 9032ced4dd..c4e24c4382 100644 --- a/test/testrunner_data.tcl +++ b/test/testrunner_data.tcl @@ -267,6 +267,7 @@ namespace eval trd { -DSQLITE_ENABLE_PERSIST_WAL=1 -DSQLITE_ENABLE_PURGEABLE_PCACHE=1 -DSQLITE_ENABLE_RTREE=1 + -DSQLITE_ENABLE_SETLK_TIMEOUT=2 -DSQLITE_ENABLE_SNAPSHOT=1 -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1 -DSQLITE_MAX_LENGTH=2147483645 diff --git a/test/wapp.tcl b/test/wapp.tcl deleted file mode 100644 index 53c21e892f..0000000000 --- a/test/wapp.tcl +++ /dev/null @@ -1,987 +0,0 @@ -# Copyright (c) 2017 D. Richard Hipp -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the Simplified BSD License (also -# known as the "2-Clause License" or "FreeBSD License".) -# -# This program is distributed in the hope that it will be useful, -# but without any warranty; without even the implied warranty of -# merchantability or fitness for a particular purpose. -# -#--------------------------------------------------------------------------- -# -# Design rules: -# -# (1) All identifiers in the global namespace begin with "wapp" -# -# (2) Indentifiers intended for internal use only begin with "wappInt" -# -package require Tcl 8.6 - -# Add text to the end of the HTTP reply. No interpretation or transformation -# of the text is performs. The argument should be enclosed within {...} -# -proc wapp {txt} { - global wapp - dict append wapp .reply $txt -} - -# Add text to the page under construction. Do no escaping on the text. -# -# Though "unsafe" in general, there are uses for this kind of thing. -# For example, if you want to return the complete, unmodified content of -# a file: -# -# set fd [open content.html rb] -# wapp-unsafe [read $fd] -# close $fd -# -# You could do the same thing using ordinary "wapp" instead of "wapp-unsafe". -# The difference is that wapp-safety-check will complain about the misuse -# of "wapp", but it assumes that the person who write "wapp-unsafe" understands -# the risks. -# -# Though occasionally necessary, the use of this interface should be minimized. -# -proc wapp-unsafe {txt} { - global wapp - dict append wapp .reply $txt -} - -# Add text to the end of the reply under construction. The following -# substitutions are made: -# -# %html(...) Escape text for inclusion in HTML -# %url(...) Escape text for use as a URL -# %qp(...) Escape text for use as a URI query parameter -# %string(...) Escape text for use within a JSON string -# %unsafe(...) No transformations of the text -# -# The substitutions above terminate at the first ")" character. If the -# text of the TCL string in ... contains ")" characters itself, use instead: -# -# %html%(...)% -# %url%(...)% -# %qp%(...)% -# %string%(...)% -# %unsafe%(...)% -# -# In other words, use "%(...)%" instead of "(...)" to include the TCL string -# to substitute. -# -# The %unsafe substitution should be avoided whenever possible, obviously. -# In addition to the substitutions above, the text also does backslash -# escapes. -# -# The wapp-trim proc works the same as wapp-subst except that it also removes -# whitespace from the left margin, so that the generated HTML/CSS/Javascript -# does not appear to be indented when delivered to the client web browser. -# -if {$tcl_version>=8.7} { - proc wapp-subst {txt} { - global wapp - regsub -all -command \ - {%(html|url|qp|string|unsafe){1,1}?(|%)\((.+)\)\2} $txt wappInt-enc txt - dict append wapp .reply [subst -novariables -nocommand $txt] - } - proc wapp-trim {txt} { - global wapp - regsub -all {\n\s+} [string trim $txt] \n txt - regsub -all -command \ - {%(html|url|qp|string|unsafe){1,1}?(|%)\((.+)\)\2} $txt wappInt-enc txt - dict append wapp .reply [subst -novariables -nocommand $txt] - } - proc wappInt-enc {all mode nu1 txt} { - return [uplevel 2 "wappInt-enc-$mode \"$txt\""] - } -} else { - proc wapp-subst {txt} { - global wapp - regsub -all {%(html|url|qp|string|unsafe){1,1}?(|%)\((.+)\)\2} $txt \ - {[wappInt-enc-\1 "\3"]} txt - dict append wapp .reply [uplevel 1 [list subst -novariables $txt]] - } - proc wapp-trim {txt} { - global wapp - regsub -all {\n\s+} [string trim $txt] \n txt - regsub -all {%(html|url|qp|string|unsafe){1,1}?(|%)\((.+)\)\2} $txt \ - {[wappInt-enc-\1 "\3"]} txt - dict append wapp .reply [uplevel 1 [list subst -novariables $txt]] - } -} - -# There must be a wappInt-enc-NAME routine for each possible substitution -# in wapp-subst. Thus there are routines for "html", "url", "qp", and "unsafe". -# -# wappInt-enc-html Escape text so that it is safe to use in the -# body of an HTML document. -# -# wappInt-enc-url Escape text so that it is safe to pass as an -# argument to href= and src= attributes in HTML. -# -# wappInt-enc-qp Escape text so that it is safe to use as the -# value of a query parameter in a URL or in -# post data or in a cookie. -# -# wappInt-enc-string Escape ", ', \, and < for using inside of a -# javascript string literal. The < character -# is escaped to prevent "" from causing -# problems in embedded javascript. -# -# wappInt-enc-unsafe Perform no encoding at all. Unsafe. -# -proc wappInt-enc-html {txt} { - return [string map {& & < < > > \" " \\ \} $txt] -} -proc wappInt-enc-unsafe {txt} { - return $txt -} -proc wappInt-enc-url {s} { - if {[regsub -all {[^-{}@~?=#_.:/a-zA-Z0-9]} $s {[wappInt-%HHchar {&}]} s]} { - set s [subst -novar -noback $s] - } - if {[regsub -all {[{}]} $s {[wappInt-%HHchar \\&]} s]} { - set s [subst -novar -noback $s] - } - return $s -} -proc wappInt-enc-qp {s} { - if {[regsub -all {[^-{}_.a-zA-Z0-9]} $s {[wappInt-%HHchar {&}]} s]} { - set s [subst -novar -noback $s] - } - if {[regsub -all {[{}]} $s {[wappInt-%HHchar \\&]} s]} { - set s [subst -novar -noback $s] - } - return $s -} -proc wappInt-enc-string {s} { - return [string map {\\ \\\\ \" \\\" ' \\' < \\u003c} $s] -} - -# This is a helper routine for wappInt-enc-url and wappInt-enc-qp. It returns -# an appropriate %HH encoding for the single character c. If c is a unicode -# character, then this routine might return multiple bytes: %HH%HH%HH -# -proc wappInt-%HHchar {c} { - if {$c==" "} {return +} - return [regsub -all .. [binary encode hex [encoding convertto utf-8 $c]] {%&}] -} - - -# Undo the www-url-encoded format. -# -# HT: This code stolen from ncgi.tcl -# -proc wappInt-decode-url {str} { - set str [string map [list + { } "\\" "\\\\" \[ \\\[ \] \\\]] $str] - regsub -all -- \ - {%([Ee][A-Fa-f0-9])%([89ABab][A-Fa-f0-9])%([89ABab][A-Fa-f0-9])} \ - $str {[encoding convertfrom utf-8 [binary decode hex \1\2\3]]} str - regsub -all -- \ - {%([CDcd][A-Fa-f0-9])%([89ABab][A-Fa-f0-9])} \ - $str {[encoding convertfrom utf-8 [binary decode hex \1\2]]} str - regsub -all -- {%([0-7][A-Fa-f0-9])} $str {\\u00\1} str - return [subst -novar $str] -} - -# Reset the document back to an empty string. -# -proc wapp-reset {} { - global wapp - dict set wapp .reply {} -} - -# Change the mime-type of the result document. -# -proc wapp-mimetype {x} { - global wapp - dict set wapp .mimetype $x -} - -# Change the reply code. -# -proc wapp-reply-code {x} { - global wapp - dict set wapp .reply-code $x -} - -# Set a cookie -# -proc wapp-set-cookie {name value} { - global wapp - dict lappend wapp .new-cookies $name $value -} - -# Unset a cookie -# -proc wapp-clear-cookie {name} { - wapp-set-cookie $name {} -} - -# Add extra entries to the reply header -# -proc wapp-reply-extra {name value} { - global wapp - dict lappend wapp .reply-extra $name $value -} - -# Specifies how the web-page under construction should be cached. -# The argument should be one of: -# -# no-cache -# max-age=N (for some integer number of seconds, N) -# private,max-age=N -# -proc wapp-cache-control {x} { - wapp-reply-extra Cache-Control $x -} - -# Redirect to a different web page -# -proc wapp-redirect {uri} { - wapp-reply-code {307 Redirect} - wapp-reply-extra Location $uri -} - -# Return the value of a wapp parameter -# -proc wapp-param {name {dflt {}}} { - global wapp - if {![dict exists $wapp $name]} {return $dflt} - return [dict get $wapp $name] -} - -# Return true if a and only if the wapp parameter $name exists -# -proc wapp-param-exists {name} { - global wapp - return [dict exists $wapp $name] -} - -# Set the value of a wapp parameter -# -proc wapp-set-param {name value} { - global wapp - dict set wapp $name $value -} - -# Return all parameter names that match the GLOB pattern, or all -# names if the GLOB pattern is omitted. -# -proc wapp-param-list {{glob {*}}} { - global wapp - return [dict keys $wapp $glob] -} - -# By default, Wapp does not decode query parameters and POST parameters -# for cross-origin requests. This is a security restriction, designed to -# help prevent cross-site request forgery (CSRF) attacks. -# -# As a consequence of this restriction, URLs for sites generated by Wapp -# that contain query parameters will not work as URLs found in other -# websites. You cannot create a link from a second website into a Wapp -# website if the link contains query planner, by default. -# -# Of course, it is sometimes desirable to allow query parameters on external -# links. For URLs for which this is safe, the application should invoke -# wapp-allow-xorigin-params. This procedure tells Wapp that it is safe to -# go ahead and decode the query parameters even for cross-site requests. -# -# In other words, for Wapp security is the default setting. Individual pages -# need to actively disable the cross-site request security if those pages -# are safe for cross-site access. -# -proc wapp-allow-xorigin-params {} { - global wapp - if {![dict exists $wapp .qp] && ![dict get $wapp SAME_ORIGIN]} { - wappInt-decode-query-params - } -} - -# Set the content-security-policy. -# -# The default content-security-policy is very strict: "default-src 'self'" -# The default policy prohibits the use of in-line javascript or CSS. -# -# Provide an alternative CSP as the argument. Or use "off" to disable -# the CSP completely. -# -proc wapp-content-security-policy {val} { - global wapp - if {$val=="off"} { - dict unset wapp .csp - } else { - dict set wapp .csp $val - } -} - -# Examine the bodys of all procedures in this program looking for -# unsafe calls to various Wapp interfaces. Return a text string -# containing warnings. Return an empty string if all is ok. -# -# This routine is advisory only. It misses some constructs that are -# dangerous and flags others that are safe. -# -proc wapp-safety-check {} { - set res {} - foreach p [info procs] { - set ln 0 - foreach x [split [info body $p] \n] { - incr ln - if {[regexp {^[ \t]*wapp[ \t]+([^\n]+)} $x all tail] - && [string index $tail 0]!="\173" - && [regexp {[[$]} $tail] - } { - append res "$p:$ln: unsafe \"wapp\" call: \"[string trim $x]\"\n" - } - if {[regexp {^[ \t]*wapp-(subst|trim)[ \t]+[^\173]} $x all cx]} { - append res "$p:$ln: unsafe \"wapp-$cx\" call: \"[string trim $x]\"\n" - } - } - } - return $res -} - -# Return a string that descripts the current environment. Applications -# might find this useful for debugging. -# -proc wapp-debug-env {} { - global wapp - set out {} - foreach var [lsort [dict keys $wapp]] { - if {[string index $var 0]=="."} continue - append out "$var = [list [dict get $wapp $var]]\n" - } - append out "\[pwd\] = [list [pwd]]\n" - return $out -} - -# Tracing function for each HTTP request. This is overridden by wapp-start -# if tracing is enabled. -# -proc wappInt-trace {} {} - -# Start up a listening socket. Arrange to invoke wappInt-new-connection -# for each inbound HTTP connection. -# -# port Listen on this TCP port. 0 means to select a port -# that is not currently in use -# -# wappmode One of "scgi", "remote-scgi", "server", or "local". -# -# fromip If not {}, then reject all requests from IP addresses -# other than $fromip -# -proc wappInt-start-listener {port wappmode fromip} { - if {[string match *scgi $wappmode]} { - set type SCGI - set server [list wappInt-new-connection \ - wappInt-scgi-readable $wappmode $fromip] - } else { - set type HTTP - set server [list wappInt-new-connection \ - wappInt-http-readable $wappmode $fromip] - } - if {$wappmode=="local" || $wappmode=="scgi"} { - set x [socket -server $server -myaddr 127.0.0.1 $port] - } else { - set x [socket -server $server $port] - } - set coninfo [chan configure $x -sockname] - set port [lindex $coninfo 2] - if {$wappmode=="local"} { - wappInt-start-browser http://127.0.0.1:$port/ - } elseif {$fromip!=""} { - puts "Listening for $type requests on TCP port $port from IP $fromip" - } else { - puts "Listening for $type requests on TCP port $port" - } -} - -# Start a web-browser and point it at $URL -# -proc wappInt-start-browser {url} { - global tcl_platform - if {$tcl_platform(platform)=="windows"} { - exec cmd /c start $url & - } elseif {$tcl_platform(os)=="Darwin"} { - exec open $url & - } elseif {[catch {exec xdg-open $url}]} { - exec firefox $url & - } -} - -# This routine is a "socket -server" callback. The $chan, $ip, and $port -# arguments are added by the socket command. -# -# Arrange to invoke $callback when content is available on the new socket. -# The $callback will process inbound HTTP or SCGI content. Reject the -# request if $fromip is not an empty string and does not match $ip. -# -proc wappInt-new-connection {callback wappmode fromip chan ip port} { - upvar #0 wappInt-$chan W - if {$fromip!="" && ![string match $fromip $ip]} { - close $chan - return - } - set W [dict create REMOTE_ADDR $ip REMOTE_PORT $port WAPP_MODE $wappmode \ - .header {}] - fconfigure $chan -blocking 0 -translation binary - fileevent $chan readable [list $callback $chan] -} - -# Close an input channel -# -proc wappInt-close-channel {chan} { - if {$chan=="stdout"} { - # This happens after completing a CGI request - exit 0 - } else { - unset ::wappInt-$chan - close $chan - } -} - -# Process new text received on an inbound HTTP request -# -proc wappInt-http-readable {chan} { - if {[catch [list wappInt-http-readable-unsafe $chan] msg]} { - puts stderr "$msg\n$::errorInfo" - wappInt-close-channel $chan - } -} -proc wappInt-http-readable-unsafe {chan} { - upvar #0 wappInt-$chan W wapp wapp - if {![dict exists $W .toread]} { - # If the .toread key is not set, that means we are still reading - # the header - set line [string trimright [gets $chan]] - set n [string length $line] - if {$n>0} { - if {[dict get $W .header]=="" || [regexp {^\s+} $line]} { - dict append W .header $line - } else { - dict append W .header \n$line - } - if {[string length [dict get $W .header]]>100000} { - error "HTTP request header too big - possible DOS attack" - } - } elseif {$n==0} { - # We have reached the blank line that terminates the header. - global argv0 - set a0 [file normalize $argv0] - dict set W SCRIPT_FILENAME $a0 - dict set W DOCUMENT_ROOT [file dir $a0] - if {[wappInt-parse-header $chan]} { - catch {close $chan} - return - } - set len 0 - if {[dict exists $W CONTENT_LENGTH]} { - set len [dict get $W CONTENT_LENGTH] - } - if {$len>0} { - # Still need to read the query content - dict set W .toread $len - } else { - # There is no query content, so handle the request immediately - set wapp $W - wappInt-handle-request $chan 0 - } - } - } else { - # If .toread is set, that means we are reading the query content. - # Continue reading until .toread reaches zero. - set got [read $chan [dict get $W .toread]] - dict append W CONTENT $got - dict set W .toread [expr {[dict get $W .toread]-[string length $got]}] - if {[dict get $W .toread]<=0} { - # Handle the request as soon as all the query content is received - set wapp $W - wappInt-handle-request $chan 0 - } - } -} - -# Decode the HTTP request header. -# -# This routine is always running inside of a [catch], so if -# any problems arise, simply raise an error. -# -proc wappInt-parse-header {chan} { - upvar #0 wappInt-$chan W - set hdr [split [dict get $W .header] \n] - if {$hdr==""} {return 1} - set req [lindex $hdr 0] - dict set W REQUEST_METHOD [set method [lindex $req 0]] - if {[lsearch {GET HEAD POST} $method]<0} { - error "unsupported request method: \"[dict get $W REQUEST_METHOD]\"" - } - set uri [lindex $req 1] - set split_uri [split $uri ?] - set uri0 [lindex $split_uri 0] - if {![regexp {^/[-.a-z0-9_/]*$} $uri0]} { - error "invalid request uri: \"$uri0\"" - } - dict set W REQUEST_URI $uri0 - dict set W PATH_INFO $uri0 - set uri1 [lindex $split_uri 1] - dict set W QUERY_STRING $uri1 - set n [llength $hdr] - for {set i 1} {$i<$n} {incr i} { - set x [lindex $hdr $i] - if {![regexp {^(.+): +(.*)$} $x all name value]} { - error "invalid header line: \"$x\"" - } - set name [string toupper $name] - switch -- $name { - REFERER {set name HTTP_REFERER} - USER-AGENT {set name HTTP_USER_AGENT} - CONTENT-LENGTH {set name CONTENT_LENGTH} - CONTENT-TYPE {set name CONTENT_TYPE} - HOST {set name HTTP_HOST} - COOKIE {set name HTTP_COOKIE} - ACCEPT-ENCODING {set name HTTP_ACCEPT_ENCODING} - default {set name .hdr:$name} - } - dict set W $name $value - } - return 0 -} - -# Decode the QUERY_STRING parameters from a GET request or the -# application/x-www-form-urlencoded CONTENT from a POST request. -# -# This routine sets the ".qp" element of the ::wapp dict as a signal -# that query parameters have already been decoded. -# -proc wappInt-decode-query-params {} { - global wapp - dict set wapp .qp 1 - if {[dict exists $wapp QUERY_STRING]} { - foreach qterm [split [dict get $wapp QUERY_STRING] &] { - set qsplit [split $qterm =] - set nm [lindex $qsplit 0] - if {[regexp {^[a-z][a-z0-9]*$} $nm]} { - dict set wapp $nm [wappInt-decode-url [lindex $qsplit 1]] - } - } - } - if {[dict exists $wapp CONTENT_TYPE] && [dict exists $wapp CONTENT]} { - set ctype [dict get $wapp CONTENT_TYPE] - if {$ctype=="application/x-www-form-urlencoded"} { - foreach qterm [split [string trim [dict get $wapp CONTENT]] &] { - set qsplit [split $qterm =] - set nm [lindex $qsplit 0] - if {[regexp {^[a-z][-a-z0-9_]*$} $nm]} { - dict set wapp $nm [wappInt-decode-url [lindex $qsplit 1]] - } - } - } elseif {[string match multipart/form-data* $ctype]} { - regexp {^(.*?)\r\n(.*)$} [dict get $wapp CONTENT] all divider body - set ndiv [string length $divider] - while {[string length $body]} { - set idx [string first $divider $body] - set unit [string range $body 0 [expr {$idx-3}]] - set body [string range $body [expr {$idx+$ndiv+2}] end] - if {[regexp {^Content-Disposition: form-data; (.*?)\r\n\r\n(.*)$} \ - $unit unit hdr content]} { - if {[regexp {name="(.*)"; filename="(.*)"\r\nContent-Type: (.*?)$}\ - $hdr hr name filename mimetype]} { - dict set wapp $name.filename \ - [string map [list \\\" \" \\\\ \\] $filename] - dict set wapp $name.mimetype $mimetype - dict set wapp $name.content $content - } elseif {[regexp {name="(.*)"} $hdr hr name]} { - dict set wapp $name $content - } - } - } - } - } -} - -# Invoke application-supplied methods to generate a reply to -# a single HTTP request. -# -# This routine always runs within [catch], so handle exceptions by -# invoking [error]. -# -proc wappInt-handle-request {chan useCgi} { - global wapp - dict set wapp .reply {} - dict set wapp .mimetype {text/html; charset=utf-8} - dict set wapp .reply-code {200 Ok} - dict set wapp .csp {default-src 'self'} - - # Set up additional CGI environment values - # - if {![dict exists $wapp HTTP_HOST]} { - dict set wapp BASE_URL {} - } elseif {[dict exists $wapp HTTPS]} { - dict set wapp BASE_URL https://[dict get $wapp HTTP_HOST] - } else { - dict set wapp BASE_URL http://[dict get $wapp HTTP_HOST] - } - if {![dict exists $wapp REQUEST_URI]} { - dict set wapp REQUEST_URI / - } elseif {[regsub {\?.*} [dict get $wapp REQUEST_URI] {} newR]} { - # Some servers (ex: nginx) append the query parameters to REQUEST_URI. - # These need to be stripped off - dict set wapp REQUEST_URI $newR - } - if {[dict exists $wapp SCRIPT_NAME]} { - dict append wapp BASE_URL [dict get $wapp SCRIPT_NAME] - } else { - dict set wapp SCRIPT_NAME {} - } - if {![dict exists $wapp PATH_INFO]} { - # If PATH_INFO is missing (ex: nginx) then construct it - set URI [dict get $wapp REQUEST_URI] - set skip [string length [dict get $wapp SCRIPT_NAME]] - dict set wapp PATH_INFO [string range $URI $skip end] - } - if {[regexp {^/([^/]+)(.*)$} [dict get $wapp PATH_INFO] all head tail]} { - dict set wapp PATH_HEAD $head - dict set wapp PATH_TAIL [string trimleft $tail /] - } else { - dict set wapp PATH_INFO {} - dict set wapp PATH_HEAD {} - dict set wapp PATH_TAIL {} - } - dict set wapp SELF_URL [dict get $wapp BASE_URL]/[dict get $wapp PATH_HEAD] - - # Parse query parameters from the query string, the cookies, and - # POST data - # - if {[dict exists $wapp HTTP_COOKIE]} { - foreach qterm [split [dict get $wapp HTTP_COOKIE] {;}] { - set qsplit [split [string trim $qterm] =] - set nm [lindex $qsplit 0] - if {[regexp {^[a-z][-a-z0-9_]*$} $nm]} { - dict set wapp $nm [wappInt-decode-url [lindex $qsplit 1]] - } - } - } - set same_origin 0 - if {[dict exists $wapp HTTP_REFERER]} { - set referer [dict get $wapp HTTP_REFERER] - set base [dict get $wapp BASE_URL] - if {$referer==$base || [string match $base/* $referer]} { - set same_origin 1 - } - } - dict set wapp SAME_ORIGIN $same_origin - if {$same_origin} { - wappInt-decode-query-params - } - - # Invoke the application-defined handler procedure for this page - # request. If an error occurs while running that procedure, generate - # an HTTP reply that contains the error message. - # - wapp-before-dispatch-hook - wappInt-trace - set mname [dict get $wapp PATH_HEAD] - if {[catch { - if {$mname!="" && [llength [info proc wapp-page-$mname]]>0} { - wapp-page-$mname - } else { - wapp-default - } - } msg]} { - if {[wapp-param WAPP_MODE]=="local" || [wapp-param WAPP_MODE]=="server"} { - puts "ERROR: $::errorInfo" - } - wapp-reset - wapp-reply-code "500 Internal Server Error" - wapp-mimetype text/html - wapp-trim { -

    Wapp Application Error

    -
    %html($::errorInfo)
    - } - dict unset wapp .new-cookies - } - - # Transmit the HTTP reply - # - if {$chan=="stdout"} { - puts $chan "Status: [dict get $wapp .reply-code]\r" - } else { - puts $chan "HTTP/1.1 [dict get $wapp .reply-code]\r" - puts $chan "Server: wapp\r" - puts $chan "Connection: close\r" - } - if {[dict exists $wapp .reply-extra]} { - foreach {name value} [dict get $wapp .reply-extra] { - puts $chan "$name: $value\r" - } - } - if {[dict exists $wapp .csp]} { - puts $chan "Content-Security-Policy: [dict get $wapp .csp]\r" - } - set mimetype [dict get $wapp .mimetype] - puts $chan "Content-Type: $mimetype\r" - if {[dict exists $wapp .new-cookies]} { - foreach {nm val} [dict get $wapp .new-cookies] { - if {[regexp {^[a-z][-a-z0-9_]*$} $nm]} { - if {$val==""} { - puts $chan "Set-Cookie: $nm=; HttpOnly; Path=/; Max-Age=1\r" - } else { - set val [wappInt-enc-url $val] - puts $chan "Set-Cookie: $nm=$val; HttpOnly; Path=/\r" - } - } - } - } - if {[string match text/* $mimetype]} { - set reply [encoding convertto utf-8 [dict get $wapp .reply]] - if {[regexp {\ygzip\y} [wapp-param HTTP_ACCEPT_ENCODING]]} { - catch { - set x [zlib gzip $reply] - set reply $x - puts $chan "Content-Encoding: gzip\r" - } - } - } else { - set reply [dict get $wapp .reply] - } - puts $chan "Content-Length: [string length $reply]\r" - puts $chan \r - puts -nonewline $chan $reply - flush $chan - wappInt-close-channel $chan -} - -# This routine runs just prior to request-handler dispatch. The -# default implementation is a no-op, but applications can override -# to do additional transformations or checks. -# -proc wapp-before-dispatch-hook {} {return} - -# Process a single CGI request -# -proc wappInt-handle-cgi-request {} { - global wapp env - foreach key { - CONTENT_LENGTH - CONTENT_TYPE - DOCUMENT_ROOT - HTTP_ACCEPT_ENCODING - HTTP_COOKIE - HTTP_HOST - HTTP_REFERER - HTTP_USER_AGENT - HTTPS - PATH_INFO - QUERY_STRING - REMOTE_ADDR - REQUEST_METHOD - REQUEST_URI - REMOTE_USER - SCRIPT_FILENAME - SCRIPT_NAME - SERVER_NAME - SERVER_PORT - SERVER_PROTOCOL - } { - if {[info exists env($key)]} { - dict set wapp $key $env($key) - } - } - set len 0 - if {[dict exists $wapp CONTENT_LENGTH]} { - set len [dict get $wapp CONTENT_LENGTH] - } - if {$len>0} { - fconfigure stdin -translation binary - dict set wapp CONTENT [read stdin $len] - } - dict set wapp WAPP_MODE cgi - fconfigure stdout -translation binary - wappInt-handle-request stdout 1 -} - -# Process new text received on an inbound SCGI request -# -proc wappInt-scgi-readable {chan} { - if {[catch [list wappInt-scgi-readable-unsafe $chan] msg]} { - puts stderr "$msg\n$::errorInfo" - wappInt-close-channel $chan - } -} -proc wappInt-scgi-readable-unsafe {chan} { - upvar #0 wappInt-$chan W wapp wapp - if {![dict exists $W .toread]} { - # If the .toread key is not set, that means we are still reading - # the header. - # - # An SGI header is short. This implementation assumes the entire - # header is available all at once. - # - dict set W .remove_addr [dict get $W REMOTE_ADDR] - set req [read $chan 15] - set n [string length $req] - scan $req %d:%s len hdr - incr len [string length "$len:,"] - append hdr [read $chan [expr {$len-15}]] - foreach {nm val} [split $hdr \000] { - if {$nm==","} break - dict set W $nm $val - } - set len 0 - if {[dict exists $W CONTENT_LENGTH]} { - set len [dict get $W CONTENT_LENGTH] - } - if {$len>0} { - # Still need to read the query content - dict set W .toread $len - } else { - # There is no query content, so handle the request immediately - dict set W SERVER_ADDR [dict get $W .remove_addr] - set wapp $W - wappInt-handle-request $chan 0 - } - } else { - # If .toread is set, that means we are reading the query content. - # Continue reading until .toread reaches zero. - set got [read $chan [dict get $W .toread]] - dict append W CONTENT $got - dict set W .toread [expr {[dict get $W .toread]-[string length $got]}] - if {[dict get $W .toread]<=0} { - # Handle the request as soon as all the query content is received - dict set W SERVER_ADDR [dict get $W .remove_addr] - set wapp $W - wappInt-handle-request $chan 0 - } - } -} - -# Start up the wapp framework. Parameters are a list passed as the -# single argument. -# -# -server $PORT Listen for HTTP requests on this TCP port $PORT -# -# -local $PORT Listen for HTTP requests on 127.0.0.1:$PORT -# -# -scgi $PORT Listen for SCGI requests on 127.0.0.1:$PORT -# -# -remote-scgi $PORT Listen for SCGI requests on TCP port $PORT -# -# -cgi Handle a single CGI request -# -# With no arguments, the behavior is called "auto". In "auto" mode, -# if the GATEWAY_INTERFACE environment variable indicates CGI, then run -# as CGI. Otherwise, start an HTTP server bound to the loopback address -# only, on an arbitrary TCP port, and automatically launch a web browser -# on that TCP port. -# -# Additional options: -# -# -fromip GLOB Reject any incoming request where the remote -# IP address does not match the GLOB pattern. This -# value defaults to '127.0.0.1' for -local and -scgi. -# -# -nowait Do not wait in the event loop. Return immediately -# after all event handlers are established. -# -# -trace "puts" each request URL as it is handled, for -# debugging -# -# -lint Run wapp-safety-check on the application instead -# of running the application itself -# -# -Dvar=value Set TCL global variable "var" to "value" -# -# -proc wapp-start {arglist} { - global env - set mode auto - set port 0 - set nowait 0 - set fromip {} - set n [llength $arglist] - for {set i 0} {$i<$n} {incr i} { - set term [lindex $arglist $i] - if {[string match --* $term]} {set term [string range $term 1 end]} - switch -glob -- $term { - -server { - incr i; - set mode "server" - set port [lindex $arglist $i] - } - -local { - incr i; - set mode "local" - set fromip 127.0.0.1 - set port [lindex $arglist $i] - } - -scgi { - incr i; - set mode "scgi" - set fromip 127.0.0.1 - set port [lindex $arglist $i] - } - -remote-scgi { - incr i; - set mode "remote-scgi" - set port [lindex $arglist $i] - } - -cgi { - set mode "cgi" - } - -fromip { - incr i - set fromip [lindex $arglist $i] - } - -nowait { - set nowait 1 - } - -trace { - proc wappInt-trace {} { - set q [wapp-param QUERY_STRING] - set uri [wapp-param BASE_URL][wapp-param PATH_INFO] - if {$q!=""} {append uri ?$q} - puts $uri - } - } - -lint { - set res [wapp-safety-check] - if {$res!=""} { - puts "Potential problems in this code:" - puts $res - exit 1 - } else { - exit - } - } - -D*=* { - if {[regexp {^.D([^=]+)=(.*)$} $term all var val]} { - set ::$var $val - } - } - default { - error "unknown option: $term" - } - } - } - if {$mode=="auto"} { - if {[info exists env(GATEWAY_INTERFACE)] - && [string match CGI/1.* $env(GATEWAY_INTERFACE)]} { - set mode cgi - } else { - set mode local - } - } - if {$mode=="cgi"} { - wappInt-handle-cgi-request - } else { - wappInt-start-listener $port $mode $fromip - if {!$nowait} { - vwait ::forever - } - } -} - -# Call this version 1.0 -package provide wapp 1.0 diff --git a/test/wapptest.tcl b/test/wapptest.tcl deleted file mode 100755 index d37b2e48c6..0000000000 --- a/test/wapptest.tcl +++ /dev/null @@ -1,909 +0,0 @@ -#!/bin/sh -# \ -exec wapptclsh "$0" ${1+"$@"} - -# package required wapp -source [file join [file dirname [info script]] wapp.tcl] - -# Variables set by the "control" form: -# -# G(platform) - User selected platform. -# G(cfgglob) - Glob pattern that all configurations must match -# G(test) - Set to "Normal", "Veryquick", "Smoketest" or "Build-Only". -# G(keep) - Boolean. True to delete no files after each test. -# G(msvc) - Boolean. True to use MSVC as the compiler. -# G(tcl) - Use Tcl from this directory for builds. -# G(jobs) - How many sub-processes to run simultaneously. -# -set G(platform) $::tcl_platform(os)-$::tcl_platform(machine) -set G(cfgglob) * -set G(test) Normal -set G(keep) 1 -set G(msvc) 0 -set G(tcl) [::tcl::pkgconfig get libdir,install] -set G(jobs) 3 -set G(debug) 0 - -set G(noui) 0 -set G(stdout) 0 - - -proc wapptest_init {} { - global G - - set lSave [list platform test keep msvc tcl jobs debug noui stdout cfgglob] - foreach k $lSave { set A($k) $G($k) } - array unset G - foreach k $lSave { set G($k) $A($k) } - - # The root of the SQLite source tree. - set G(srcdir) [file dirname [file dirname [info script]]] - - set G(sqlite_version) "unknown" - - # Either "config", "running" or "stopped": - set G(state) "config" - - set G(hostname) "(unknown host)" - catch { set G(hostname) [exec hostname] } - set G(host) $G(hostname) - append G(host) " $::tcl_platform(os) $::tcl_platform(osVersion)" - append G(host) " $::tcl_platform(machine) $::tcl_platform(byteOrder)" -} - -proc wapptest_run {} { - global G - set_test_array - set G(state) "running" - - wapptest_openlog - - wapptest_output "Running the following for $G(platform). $G(jobs) jobs." - foreach t $G(test_array) { - set config [dict get $t config] - set target [dict get $t target] - wapptest_output [format " %-25s%s" $config $target] - } - wapptest_output [string repeat * 70] -} - -proc releasetest_data {args} { - global G - set rtd [file join $G(srcdir) test releasetest_data.tcl] - set fd [open "|[info nameofexecutable] $rtd $args" r+] - set ret [read $fd] - close $fd - return $ret -} - -# Generate the text for the box at the top of the UI. The current SQLite -# version, according to fossil, along with a warning if there are -# uncommitted changes in the checkout. -# -proc generate_fossil_info {} { - global G - set pwd [pwd] - cd $G(srcdir) - set rc [catch { - set r1 [exec fossil info] - set r2 [exec fossil changes] - }] - cd $pwd - if {$rc} return - - foreach line [split $r1 "\n"] { - if {[regexp {^checkout: *(.*)$} $line -> co]} { - wapp-trim {
    %html($co) } - } - } - - if {[string trim $r2]!=""} { - wapp-trim { -
    - WARNING: Uncommitted changes in checkout - - } - } -} - -# If the application is in "config" state, set the contents of the -# ::G(test_array) global to reflect the tests that will be run. If the -# app is in some other state ("running" or "stopped"), this command -# is a no-op. -# -proc set_test_array {} { - global G - if { $G(state)=="config" } { - set G(test_array) [list] - set debug "-debug" - if {$G(debug)==0} { set debug "-nodebug"} - foreach {config target} [releasetest_data tests $debug $G(platform)] { - - # All configuration names must match $g(cfgglob), which defaults to * - # - if {![string match -nocase $G(cfgglob) $config]} continue - - # If using MSVC, do not run sanitize or valgrind tests. Or the - # checksymbols test. - if {$G(msvc) && ( - "Sanitize" == $config - || "checksymbols" in $target - || "valgrindtest" in $target - )} { - continue - } - - # If the test mode is not "Normal", override the target. - # - if {$target!="checksymbols" && $G(platform)!="Failure-Detection"} { - switch -- $G(test) { - Veryquick { set target quicktest } - Smoketest { set target smoketest } - Build-Only { - set target testfixture - if {$::tcl_platform(platform)=="windows"} { - set target testfixture.exe - } - } - } - } - - lappend G(test_array) [dict create config $config target $target] - } - } -} - -proc count_tests_and_errors {name logfile} { - global G - - set fd [open $logfile rb] - set seen 0 - while {![eof $fd]} { - set line [gets $fd] - if {[regexp {(\d+) errors out of (\d+) tests} $line all nerr ntest]} { - incr G(test.$name.nError) $nerr - incr G(test.$name.nTest) $ntest - set seen 1 - if {$nerr>0} { - set G(test.$name.errmsg) $line - } - } - if {[regexp {runtime error: +(.*)} $line all msg]} { - # skip over "value is outside range" errors - if {[regexp {.* is outside the range of representable} $line]} { - # noop - } else { - incr G(test.$name.nError) - if {$G(test.$name.errmsg)==""} { - set G(test.$name.errmsg) $msg - } - } - } - if {[regexp {fatal error +(.*)} $line all msg]} { - incr G(test.$name.nError) - if {$G(test.$name.errmsg)==""} { - set G(test.$name.errmsg) $msg - } - } - if {[regexp {ERROR SUMMARY: (\d+) errors.*} $line all cnt] && $cnt>0} { - incr G(test.$name.nError) - if {$G(test.$name.errmsg)==""} { - set G(test.$name.errmsg) $all - } - } - if {[regexp {^VERSION: 3\.\d+.\d+} $line]} { - set v [string range $line 9 end] - if {$G(sqlite_version) eq "unknown"} { - set G(sqlite_version) $v - } elseif {$G(sqlite_version) ne $v} { - set G(test.$name.errmsg) "version conflict: {$G(sqlite_version)} vs. {$v}" - } - } - } - close $fd - if {$G(test) == "Build-Only"} { - incr G(test.$name.nTest) - if {$G(test.$name.nError)>0} { - set errmsg "Build failed" - } - } elseif {!$seen} { - set G(test.$name.errmsg) "Test did not complete" - if {[file readable core]} { - append G(test.$name.errmsg) " - core file exists" - } - } -} - -proc wapptest_output {str} { - global G - if {$G(stdout)} { puts $str } - if {[info exists G(log)]} { - puts $G(log) $str - flush $G(log) - } -} -proc wapptest_openlog {} { - global G - set G(log) [open wapptest-out.txt w+] -} -proc wapptest_closelog {} { - global G - close $G(log) - unset G(log) -} - -proc format_seconds {seconds} { - set min [format %.2d [expr ($seconds / 60) % 60]] - set hr [format %.2d [expr $seconds / 3600]] - set sec [format %.2d [expr $seconds % 60]] - return "$hr:$min:$sec" -} - -# This command is invoked once a slave process has finished running its -# tests, successfully or otherwise. Parameter $name is the name of the -# test, $rc the exit code returned by the slave process. -# -proc slave_test_done {name rc} { - global G - set G(test.$name.done) [clock seconds] - set G(test.$name.nError) 0 - set G(test.$name.nTest) 0 - set G(test.$name.errmsg) "" - if {$rc} { - incr G(test.$name.nError) - } - if {[file exists $G(test.$name.log)]} { - count_tests_and_errors $name $G(test.$name.log) - } - - # If the "keep files" checkbox is clear, delete all files except for - # the executables and test logs. And any core file that is present. - if {$G(keep)==0} { - set keeplist { - testfixture testfixture.exe - sqlite3 sqlite3.exe - test.log test-out.txt - core - wapptest_make.sh - wapptest_configure.sh - wapptest_run.tcl - } - foreach f [glob -nocomplain [file join $G(test.$name.dir) *]] { - set t [file tail $f] - if {[lsearch $keeplist $t]<0} { - catch { file delete -force $f } - } - } - } - - # Format a message regarding the success or failure of hte test. - set t [format_seconds [expr $G(test.$name.done) - $G(test.$name.start)]] - set res "OK" - if {$G(test.$name.nError)} { set res "FAILED" } - set dots [string repeat . [expr 60 - [string length $name]]] - set msg "$name $dots $res ($t)" - - wapptest_output $msg - if {[info exists G(test.$name.errmsg)] && $G(test.$name.errmsg)!=""} { - wapptest_output " $G(test.$name.errmsg)" - } -} - -# This is a fileevent callback invoked each time a file-descriptor that -# connects this process to a slave process is readable. -# -proc slave_fileevent {name} { - global G - set fd $G(test.$name.channel) - - if {[eof $fd]} { - fconfigure $fd -blocking 1 - set rc [catch { close $fd }] - unset G(test.$name.channel) - slave_test_done $name $rc - } else { - set line [gets $fd] - if {[string trim $line] != ""} { puts "Trace : $name - \"$line\"" } - } - - do_some_stuff -} - -# Return the contents of the "slave script" - the script run by slave -# processes to actually perform the test. All it does is execute the -# test script already written to disk (wapptest_cmd.sh or wapptest_cmd.bat). -# -proc wapptest_slave_script {} { - global G - if {$G(msvc)==0} { - set dir [file join .. $G(srcdir)] - set res [subst -nocommands { - set rc [catch "exec sh wapptest_cmd.sh {$dir} >>& test.log" ] - exit [set rc] - }] - } else { - set dir [file nativename [file normalize $G(srcdir)]] - set dir [string map [list "\\" "\\\\"] $dir] - set res [subst -nocommands { - set rc [catch "exec wapptest_cmd.bat {$dir} >>& test.log" ] - exit [set rc] - }] - } - - set res -} - - -# Launch a slave process to run a test. -# -proc slave_launch {name target dir} { - global G - - catch { file mkdir $dir } msg - foreach f [glob -nocomplain [file join $dir *]] { - catch { file delete -force $f } - } - set G(test.$name.dir) $dir - - # Write the test command to wapptest_cmd.sh|bat. - # - set ext sh - if {$G(msvc)} { set ext bat } - set fd1 [open [file join $dir wapptest_cmd.$ext] w] - if {$G(msvc)} { - puts $fd1 [releasetest_data script -msvc $name $target] - } else { - puts $fd1 [releasetest_data script $name $target] - } - close $fd1 - - # Write the wapptest_run.tcl script to the test directory. To run the - # commands in the other two files. - # - set fd3 [open [file join $dir wapptest_run.tcl] w] - puts $fd3 [wapptest_slave_script] - close $fd3 - - set pwd [pwd] - cd $dir - set fd [open "|[info nameofexecutable] wapptest_run.tcl" r+] - cd $pwd - - set G(test.$name.channel) $fd - fconfigure $fd -blocking 0 - fileevent $fd readable [list slave_fileevent $name] -} - -proc do_some_stuff {} { - global G - - # Count the number of running jobs. A running job has an entry named - # "channel" in its dictionary. - set nRunning 0 - set bFinished 1 - foreach j $G(test_array) { - set name [dict get $j config] - if { [info exists G(test.$name.channel)]} { incr nRunning } - if {![info exists G(test.$name.done)]} { set bFinished 0 } - } - - if {$bFinished} { - set nError 0 - set nTest 0 - set nConfig 0 - foreach j $G(test_array) { - set name [dict get $j config] - incr nError $G(test.$name.nError) - incr nTest $G(test.$name.nTest) - incr nConfig - } - set G(result) "$nError errors from $nTest tests in $nConfig configurations." - wapptest_output [string repeat * 70] - wapptest_output $G(result) - catch { - append G(result) " SQLite version $G(sqlite_version)" - wapptest_output " SQLite version $G(sqlite_version)" - } - set G(state) "stopped" - wapptest_closelog - if {$G(noui)} { exit 0 } - } else { - set nLaunch [expr $G(jobs) - $nRunning] - foreach j $G(test_array) { - if {$nLaunch<=0} break - set name [dict get $j config] - if { ![info exists G(test.$name.channel)] - && ![info exists G(test.$name.done)] - } { - - set target [dict get $j target] - set dir [string tolower [string map {" " _ "-" _} $name]] - set G(test.$name.start) [clock seconds] - set G(test.$name.log) [file join $dir test.log] - - slave_launch $name $target $dir - - incr nLaunch -1 - } - } - } -} - -proc generate_select_widget {label id lOpt opt} { - wapp-trim { - - } -} - -proc generate_main_page {{extra {}}} { - global G - set_test_array - - set hostname $G(hostname) - wapp-trim { - - - %html($hostname): wapptest.tcl - - - - } - - set host $G(host) - wapp-trim { -
    %string($host) - } - generate_fossil_info - wapp-trim { -
    -
    -
    - } - - # Build the "platform" select widget. - set lOpt [releasetest_data platforms] - generate_select_widget Platform control_platform $lOpt $G(platform) - - # Build the "test" select widget. - set lOpt [list Normal Veryquick Smoketest Build-Only] - generate_select_widget Test control_test $lOpt $G(test) - - # Build the "jobs" select widget. Options are 1 to 8. - generate_select_widget Jobs control_jobs {1 2 3 4 5 6 7 8 12 16} $G(jobs) - - switch $G(state) { - config { - set txt "Run Tests!" - set id control_run - } - running { - set txt "STOP Tests!" - set id control_stop - } - stopped { - set txt "Reset!" - set id control_reset - } - } - wapp-trim { -
    - - -
    - } - - wapp-trim { -

    - - - - - - - - - - - } - wapp-trim { -
    - } - wapp-trim { -
    -
    - } - wapp-page-tests - - set script "script/$G(state).js" - wapp-trim { -
    - - - - } -} - -proc wapp-default {} { - generate_main_page -} - -proc wapp-page-tests {} { - global G - wapp-trim { } - foreach t $G(test_array) { - set config [dict get $t config] - set target [dict get $t target] - - set class "testwait" - set seconds "" - - if {[info exists G(test.$config.log)]} { - if {[info exists G(test.$config.channel)]} { - set class "testrunning" - set seconds [expr [clock seconds] - $G(test.$config.start)] - } elseif {[info exists G(test.$config.done)]} { - if {$G(test.$config.nError)>0} { - set class "testfail" - } else { - set class "testdone" - } - set seconds [expr $G(test.$config.done) - $G(test.$config.start)] - } - set seconds [format_seconds $seconds] - } - - wapp-trim { - - -
    %html($config) - %html($target) - %html($seconds) - - } - if {[info exists G(test.$config.log)]} { - set log $G(test.$config.log) - set uri "log/$log" - wapp-trim { - %html($log) - } - } - if {[info exists G(test.$config.errmsg)] && $G(test.$config.errmsg)!=""} { - set errmsg $G(test.$config.errmsg) - wapp-trim { -
    %html($errmsg) - } - } - } - - wapp-trim {
    } - - if {[info exists G(result)]} { - set res $G(result) - wapp-trim { -
    %string($res)
    - } - } -} - -# URI: /control -# -# Whenever the form at the top of the application page is submitted, it -# is submitted here. -# -proc wapp-page-control {} { - global G - if {$::G(state)=="config"} { - set lControls [list platform test tcl jobs keep msvc debug] - set G(msvc) 0 - set G(keep) 0 - set G(debug) 0 - } else { - set lControls [list jobs] - } - foreach v $lControls { - if {[wapp-param-exists control_$v]} { - set G($v) [wapp-param control_$v] - } - } - - if {[wapp-param-exists control_run]} { - # This is a "run test" command. - wapptest_run - } - - if {[wapp-param-exists control_stop]} { - # A "STOP tests" command. - set G(state) "stopped" - set G(result) "Test halted by user" - foreach j $G(test_array) { - set name [dict get $j config] - if { [info exists G(test.$name.channel)] } { - close $G(test.$name.channel) - unset G(test.$name.channel) - slave_test_done $name 1 - } - } - wapptest_closelog - } - - if {[wapp-param-exists control_reset]} { - # A "reset app" command. - set G(state) "config" - wapptest_init - } - - if {$::G(state) == "running"} { - do_some_stuff - } - wapp-redirect / -} - -# URI: /style.css -# -# Return the stylesheet for the application main page. -# -proc wapp-page-style.css {} { - wapp-subst { - - /* The boxes with black borders use this class */ - .border { - border: 3px groove #444444; - padding: 1em; - margin-top: 1em; - margin-bottom: 1em; - } - - /* Float to the right (used for the Run/Stop/Reset button) */ - .right { float: right; } - - /* Style for the large red warning at the top of the page */ - .warning { - color: red; - font-weight: bold; - } - - /* Styles used by cells in the test table */ - .padleft { padding-left: 5ex; } - .nowrap { white-space: nowrap; } - - /* Styles for individual tests, depending on the outcome */ - .testwait { } - .testrunning { color: blue } - .testdone { color: green } - .testfail { color: red } - } -} - -# URI: /script/${state}.js -# -# The last part of this URI is always "config.js", "running.js" or -# "stopped.js", depending on the state of the application. It returns -# the javascript part of the front-end for the requested state to the -# browser. -# -proc wapp-page-script {} { - regexp {[^/]*$} [wapp-param REQUEST_URI] script - - set tcl $::G(tcl) - set keep $::G(keep) - set msvc $::G(msvc) - set debug $::G(debug) - - wapp-subst { - var lElem = \["control_platform", "control_test", "control_msvc", - "control_jobs", "control_debug" - \]; - lElem.forEach(function(e) { - var elem = document.getElementById(e); - elem.addEventListener("change", function() { control.submit() } ); - }) - - elem = document.getElementById("control_tcl"); - elem.value = "%string($tcl)" - - elem = document.getElementById("control_keep"); - elem.checked = %string($keep); - - elem = document.getElementById("control_msvc"); - elem.checked = %string($msvc); - - elem = document.getElementById("control_debug"); - elem.checked = %string($debug); - } - - if {$script != "config.js"} { - wapp-subst { - var lElem = \["control_platform", "control_test", - "control_tcl", "control_keep", "control_msvc", - "control_debug" - \]; - lElem.forEach(function(e) { - var elem = document.getElementById(e); - elem.disabled = true; - }) - } - } - - if {$script == "running.js"} { - wapp-subst { - function reload_tests() { - fetch('tests') - .then( data => data.text() ) - .then( data => { - document.getElementById("tests").innerHTML = data; - }) - .then( data => { - if( document.getElementById("result") ){ - document.location = document.location; - } else { - setTimeout(reload_tests, 1000) - } - }); - } - - setTimeout(reload_tests, 1000) - } - } -} - -# URI: /env -# -# This is for debugging only. Serves no other purpose. -# -proc wapp-page-env {} { - wapp-allow-xorigin-params - wapp-trim { -

    Wapp Environment

    \n
    -    
    %html([wapp-debug-env])
    - } -} - -# URI: /log/dirname/test.log -# -# This URI reads file "dirname/test.log" from disk, wraps it in a
    -# block, and returns it to the browser. Use for viewing log files.
    -#
    -proc wapp-page-log {} {
    -  set log [string range [wapp-param REQUEST_URI] 5 end]
    -  set fd [open $log]
    -  set data [read $fd]
    -  close $fd
    -  wapp-trim {
    -    
    -    %html($data)
    -    
    - } -} - -# Print out a usage message. Then do [exit 1]. -# -proc wapptest_usage {} { - puts stderr { -This Tcl script is used to test various configurations of SQLite. By -default it uses "wapp" to provide an interactive interface. Supported -command line options (all optional) are: - - --platform PLATFORM (which tests to run) - --config GLOB (only run configurations matching GLOB) - --smoketest (run "make smoketest" only) - --veryquick (run veryquick.test only) - --buildonly (build executables, do not run tests) - --jobs N (number of concurrent jobs) - --tcl DIR (where to find tclConfig.sh) - --deletefiles (delete extra files after each test) - --msvc (Use MS Visual C) - --debug (Also run [n]debugging versions of tests) - --noui (do not use wapp) - } - exit 1 -} - -# Sort command line arguments into two groups: those that belong to wapp, -# and those that belong to the application. -set WAPPARG(-server) 1 -set WAPPARG(-local) 1 -set WAPPARG(-scgi) 1 -set WAPPARG(-remote-scgi) 1 -set WAPPARG(-fromip) 1 -set WAPPARG(-nowait) 0 -set WAPPARG(-cgi) 0 -set lWappArg [list] -set lTestArg [list] -for {set i 0} {$i < [llength $argv]} {incr i} { - set arg [lindex $argv $i] - if {[string range $arg 0 1]=="--"} { - set arg [string range $arg 1 end] - } - if {[info exists WAPPARG($arg)]} { - lappend lWappArg $arg - if {$WAPPARG($arg)} { - incr i - lappend lWappArg [lindex $argv $i] - } - } else { - lappend lTestArg $arg - } -} - -wapptest_init -for {set i 0} {$i < [llength $lTestArg]} {incr i} { - set opt [lindex $lTestArg $i] - if {[string range $opt 0 1]=="--"} { - set opt [string range $opt 1 end] - } - switch -- $opt { - -platform { - if {$i==[llength $lTestArg]-1} { wapptest_usage } - incr i - set arg [lindex $lTestArg $i] - set lPlatform [releasetest_data platforms] - if {[lsearch $lPlatform $arg]<0} { - puts stderr "No such platform: $arg. Platforms are: $lPlatform" - exit -1 - } - set G(platform) $arg - } - - -smoketest { set G(test) Smoketest } - -veryquick { set G(test) Veryquick } - -buildonly { set G(test) Build-Only } - -jobs { - if {$i==[llength $lTestArg]-1} { wapptest_usage } - incr i - set G(jobs) [lindex $lTestArg $i] - } - - -tcl { - if {$i==[llength $lTestArg]-1} { wapptest_usage } - incr i - set G(tcl) [lindex $lTestArg $i] - } - - -deletefiles { - set G(keep) 0 - } - - -msvc { - set G(msvc) 1 - } - - -debug { - set G(debug) 1 - } - - -noui { - set G(noui) 1 - set G(stdout) 1 - } - - -config { - if {$i==[llength $lTestArg]-1} { wapptest_usage } - incr i - set G(cfgglob) [lindex $lTestArg $i] - } - - -stdout { - set G(stdout) 1 - } - - default { - puts stderr "Unrecognized option: [lindex $lTestArg $i]" - wapptest_usage - } - } -} - -if {$G(noui)==0} { - wapp-start $lWappArg -} else { - wapptest_run - do_some_stuff - vwait forever -}