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 {
-
}
- 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
-}