Merge trunk into wasi-patches branch.
FossilOrigin-Name: 64cf34a85505814b4c93e82855c25559c8096bb3ce38cdc5e0fb9218c6bcce32
This commit is contained in:
+10
@@ -446,6 +446,7 @@ TESTSRC += \
|
||||
$(TOP)/ext/expert/test_expert.c \
|
||||
$(TOP)/ext/misc/amatch.c \
|
||||
$(TOP)/ext/misc/appendvfs.c \
|
||||
$(TOP)/ext/misc/basexx.c \
|
||||
$(TOP)/ext/misc/carray.c \
|
||||
$(TOP)/ext/misc/cksumvfs.c \
|
||||
$(TOP)/ext/misc/closure.c \
|
||||
@@ -778,6 +779,15 @@ sqlite3.c: .target_source $(TOP)/tool/mksqlite3c.tcl
|
||||
cp tsrc/sqlite3ext.h .
|
||||
cp $(TOP)/ext/session/sqlite3session.h .
|
||||
|
||||
sqlite3r.h: sqlite3.h
|
||||
$(TCLSH_CMD) $(TOP)/tool/mksqlite3h.tcl $(TOP) --enable-recover >sqlite3r.h
|
||||
|
||||
sqlite3r.c: sqlite3.c sqlite3r.h
|
||||
cp $(TOP)/ext/recover/sqlite3recover.c tsrc/
|
||||
cp $(TOP)/ext/recover/sqlite3recover.h tsrc/
|
||||
cp $(TOP)/ext/recover/dbdata.c tsrc/
|
||||
$(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl --enable-recover $(AMALGAMATION_LINE_MACROS)
|
||||
|
||||
sqlite3ext.h: .target_source
|
||||
cp tsrc/sqlite3ext.h .
|
||||
|
||||
|
||||
@@ -1561,6 +1561,7 @@ TESTEXT = \
|
||||
$(TOP)\ext\expert\test_expert.c \
|
||||
$(TOP)\ext\misc\amatch.c \
|
||||
$(TOP)\ext\misc\appendvfs.c \
|
||||
$(TOP)\ext\misc\basexx.c \
|
||||
$(TOP)\ext\misc\carray.c \
|
||||
$(TOP)\ext\misc\cksumvfs.c \
|
||||
$(TOP)\ext\misc\closure.c \
|
||||
|
||||
Executable
+269
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
** 2022-11-18
|
||||
**
|
||||
** 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 is a SQLite extension for converting in either direction
|
||||
** between a (binary) blob and base64 text. Base64 can transit a
|
||||
** sane USASCII channel unmolested. It also plays nicely in CSV or
|
||||
** written as TCL brace-enclosed literals or SQL string literals,
|
||||
** and can be used unmodified in XML-like documents.
|
||||
**
|
||||
** This is an independent implementation of conversions specified in
|
||||
** RFC 4648, done on the above date by the author (Larry Brasfield)
|
||||
** who thereby has the right to put this into the public domain.
|
||||
**
|
||||
** The conversions meet RFC 4648 requirements, provided that this
|
||||
** C source specifies that line-feeds are included in the encoded
|
||||
** data to limit visible line lengths to 72 characters and to
|
||||
** terminate any encoded blob having non-zero length.
|
||||
**
|
||||
** Length limitations are not imposed except that the runtime
|
||||
** SQLite string or blob length limits are respected. Otherwise,
|
||||
** any length binary sequence can be represented and recovered.
|
||||
** Generated base64 sequences, with their line-feeds included,
|
||||
** can be concatenated; the result converted back to binary will
|
||||
** be the concatenation of the represented binary sequences.
|
||||
**
|
||||
** This SQLite3 extension creates a function, base64(x), which
|
||||
** either: converts text x containing base64 to a returned blob;
|
||||
** or converts a blob x to returned text containing base64. An
|
||||
** error will be thrown for other input argument types.
|
||||
**
|
||||
** This code relies on UTF-8 encoding only with respect to the
|
||||
** meaning of the first 128 (7-bit) codes matching that of USASCII.
|
||||
** It will fail miserably if somehow made to try to convert EBCDIC.
|
||||
** Because it is table-driven, it could be enhanced to handle that,
|
||||
** but the world and SQLite have moved on from that anachronism.
|
||||
**
|
||||
** To build the extension:
|
||||
** Set shell variable SQDIR=<your favorite SQLite checkout directory>
|
||||
** *Nix: gcc -O2 -shared -I$SQDIR -fPIC -o base64.so base64.c
|
||||
** OSX: gcc -O2 -dynamiclib -fPIC -I$SQDIR -o base64.dylib base64.c
|
||||
** Win32: gcc -O2 -shared -I%SQDIR% -o base64.dll base64.c
|
||||
** Win32: cl /Os -I%SQDIR% base64.c -link -dll -out:base64.dll
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#ifndef SQLITE_SHELL_EXTFUNCS /* Guard for #include as built-in extension. */
|
||||
#include "sqlite3ext.h"
|
||||
#endif
|
||||
|
||||
SQLITE_EXTENSION_INIT1;
|
||||
|
||||
#define PC 0x80 /* pad character */
|
||||
#define WS 0x81 /* whitespace */
|
||||
#define ND 0x82 /* Not above or digit-value */
|
||||
#define PAD_CHAR '='
|
||||
|
||||
#ifndef UBYTE_TYPEDEF
|
||||
typedef unsigned char ubyte;
|
||||
# define UBYTE_TYPEDEF
|
||||
#endif
|
||||
|
||||
static const ubyte b64DigitValues[128] = {
|
||||
/* HT LF VT FF CR */
|
||||
ND,ND,ND,ND, ND,ND,ND,ND, ND,WS,WS,WS, WS,WS,ND,ND,
|
||||
/* US */
|
||||
ND,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,ND,
|
||||
/*sp + / */
|
||||
WS,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,62, ND,ND,ND,63,
|
||||
/* 0 1 5 9 = */
|
||||
52,53,54,55, 56,57,58,59, 60,61,ND,ND, ND,PC,ND,ND,
|
||||
/* A O */
|
||||
ND, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
|
||||
/* P Z */
|
||||
15,16,17,18, 19,20,21,22, 23,24,25,ND, ND,ND,ND,ND,
|
||||
/* a o */
|
||||
ND,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
|
||||
/* p z */
|
||||
41,42,43,44, 45,46,47,48, 49,50,51,ND, ND,ND,ND,ND
|
||||
};
|
||||
|
||||
static const char b64Numerals[64+1]
|
||||
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
#define BX_DV_PROTO(c) ((((ubyte)(c))<0x80)? b64DigitValues[c] : 0x80)
|
||||
#define IS_BX_DIGIT(bdp) (((ubyte)(bdp))<0x80)
|
||||
#define IS_BX_WS(bdp) ((bdp)==WS)
|
||||
#define IS_BX_PAD(bdp) ((bdp)==PC)
|
||||
#define BX_NUMERAL(dv) (b64Numerals[(ubyte)(dv)])
|
||||
/* Width of base64 lines. Should be an integer multiple of 4. */
|
||||
#define B64_DARK_MAX 72
|
||||
|
||||
/* Encode a byte buffer into base64 text with linefeeds appended to limit
|
||||
** encoded group lengths to B64_DARK_MAX or to terminate the last group.
|
||||
*/
|
||||
static char* toBase64( ubyte *pIn, int nbIn, char *pOut ){
|
||||
int nCol = 0;
|
||||
while( nbIn >= 3 ){
|
||||
/* Do the bit-shuffle, exploiting unsigned input to avoid masking. */
|
||||
pOut[0] = BX_NUMERAL(pIn[0]>>2);
|
||||
pOut[1] = BX_NUMERAL(((pIn[0]<<4)|(pIn[1]>>4))&0x3f);
|
||||
pOut[2] = BX_NUMERAL(((pIn[1]&0xf)<<2)|(pIn[2]>>6));
|
||||
pOut[3] = BX_NUMERAL(pIn[2]&0x3f);
|
||||
pOut += 4;
|
||||
nbIn -= 3;
|
||||
pIn += 3;
|
||||
if( (nCol += 4)>=B64_DARK_MAX || nbIn<=0 ){
|
||||
*pOut++ = '\n';
|
||||
nCol = 0;
|
||||
}
|
||||
}
|
||||
if( nbIn > 0 ){
|
||||
signed char nco = nbIn+1;
|
||||
int nbe;
|
||||
unsigned long qv = *pIn++;
|
||||
for( nbe=1; nbe<3; ++nbe ){
|
||||
qv <<= 8;
|
||||
if( nbe<nbIn ) qv |= *pIn++;
|
||||
}
|
||||
for( nbe=3; nbe>=0; --nbe ){
|
||||
char ce = (nbe<nco)? BX_NUMERAL((ubyte)(qv & 0x3f)) : PAD_CHAR;
|
||||
qv >>= 6;
|
||||
pOut[nbe] = ce;
|
||||
}
|
||||
pOut += 4;
|
||||
*pOut++ = '\n';
|
||||
}
|
||||
*pOut = 0;
|
||||
return pOut;
|
||||
}
|
||||
|
||||
/* Skip over text which is not base64 numeral(s). */
|
||||
static char * skipNonB64( char *s ){
|
||||
char c;
|
||||
while( (c = *s) && !IS_BX_DIGIT(BX_DV_PROTO(c)) ) ++s;
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Decode base64 text into a byte buffer. */
|
||||
static ubyte* fromBase64( char *pIn, int ncIn, ubyte *pOut ){
|
||||
if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn;
|
||||
while( ncIn>0 && *pIn!=PAD_CHAR ){
|
||||
static signed char nboi[] = { 0, 0, 1, 2, 3 };
|
||||
char *pUse = skipNonB64(pIn);
|
||||
unsigned long qv = 0L;
|
||||
int nti, nbo, nac;
|
||||
ncIn -= (pUse - pIn);
|
||||
pIn = pUse;
|
||||
nti = (ncIn>4)? 4 : ncIn;
|
||||
ncIn -= nti;
|
||||
nbo = nboi[nti];
|
||||
if( nbo==0 ) break;
|
||||
for( nac=0; nac<4; ++nac ){
|
||||
char c = (nac<nti)? *pIn++ : b64Numerals[0];
|
||||
ubyte bdp = BX_DV_PROTO(c);
|
||||
switch( bdp ){
|
||||
case ND:
|
||||
/* Treat dark non-digits as pad, but they terminate decode too. */
|
||||
ncIn = 0;
|
||||
/* fall thru */
|
||||
case WS:
|
||||
/* Treat whitespace as pad and terminate this group.*/
|
||||
nti = nac;
|
||||
/* fall thru */
|
||||
case PC:
|
||||
bdp = 0;
|
||||
--nbo;
|
||||
/* fall thru */
|
||||
default: /* bdp is the digit value. */
|
||||
qv = qv<<6 | bdp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch( nbo ){
|
||||
case 3:
|
||||
pOut[2] = (qv) & 0xff;
|
||||
case 2:
|
||||
pOut[1] = (qv>>8) & 0xff;
|
||||
case 1:
|
||||
pOut[0] = (qv>>16) & 0xff;
|
||||
}
|
||||
pOut += nbo;
|
||||
}
|
||||
return pOut;
|
||||
}
|
||||
|
||||
/* This function does the work for the SQLite base64(x) UDF. */
|
||||
static void base64(sqlite3_context *context, int na, sqlite3_value *av[]){
|
||||
int nb, nc, nv = sqlite3_value_bytes(av[0]);
|
||||
int nvMax = sqlite3_limit(sqlite3_context_db_handle(context),
|
||||
SQLITE_LIMIT_LENGTH, -1);
|
||||
char *cBuf;
|
||||
ubyte *bBuf;
|
||||
assert(na==1);
|
||||
switch( sqlite3_value_type(av[0]) ){
|
||||
case SQLITE_BLOB:
|
||||
nb = nv;
|
||||
nc = 4*(nv+2/3); /* quads needed */
|
||||
nc += (nc+(B64_DARK_MAX-1))/B64_DARK_MAX + 1; /* LFs and a 0-terminator */
|
||||
if( nvMax < nc ){
|
||||
sqlite3_result_error(context, "blob expanded to base64 too big", -1);
|
||||
return;
|
||||
}
|
||||
cBuf = sqlite3_malloc(nc);
|
||||
if( !cBuf ) goto memFail;
|
||||
bBuf = (ubyte*)sqlite3_value_blob(av[0]);
|
||||
nc = (int)(toBase64(bBuf, nb, cBuf) - cBuf);
|
||||
sqlite3_result_text(context, cBuf, nc, sqlite3_free);
|
||||
break;
|
||||
case SQLITE_TEXT:
|
||||
nc = nv;
|
||||
nb = 3*((nv+3)/4); /* may overestimate due to LF and padding */
|
||||
if( nvMax < nb ){
|
||||
sqlite3_result_error(context, "blob from base64 may be too big", -1);
|
||||
return;
|
||||
}else if( nb<1 ){
|
||||
nb = 1;
|
||||
}
|
||||
bBuf = sqlite3_malloc(nb);
|
||||
if( !bBuf ) goto memFail;
|
||||
cBuf = (char *)sqlite3_value_text(av[0]);
|
||||
nb = (int)(fromBase64(cBuf, nc, bBuf) - bBuf);
|
||||
sqlite3_result_blob(context, bBuf, nb, sqlite3_free);
|
||||
break;
|
||||
default:
|
||||
sqlite3_result_error(context, "base64 accepts only blob or text", -1);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
memFail:
|
||||
sqlite3_result_error(context, "base64 OOM", -1);
|
||||
}
|
||||
|
||||
/*
|
||||
** Establish linkage to running SQLite library.
|
||||
*/
|
||||
#ifndef SQLITE_SHELL_EXTFUNCS
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
int sqlite3_base_init
|
||||
#else
|
||||
static int sqlite3_base64_init
|
||||
#endif
|
||||
(sqlite3 *db, char **pzErr, const sqlite3_api_routines *pApi){
|
||||
SQLITE_EXTENSION_INIT2(pApi);
|
||||
(void)pzErr;
|
||||
return sqlite3_create_function
|
||||
(db, "base64", 1,
|
||||
SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|SQLITE_DIRECTONLY|SQLITE_UTF8,
|
||||
0, base64, 0, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
** Define some macros to allow this extension to be built into the shell
|
||||
** conveniently, in conjunction with use of SQLITE_SHELL_EXTFUNCS. This
|
||||
** allows shell.c, as distributed, to have this extension built in.
|
||||
*/
|
||||
#define BASE64_INIT(db) sqlite3_base64_init(db, 0, 0)
|
||||
#define BASE64_EXPOSE(db, pzErr) /* Not needed, ..._init() does this. */
|
||||
@@ -0,0 +1,437 @@
|
||||
/*
|
||||
** 2022-11-16
|
||||
**
|
||||
** 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 is a utility for converting binary to base85 or vice-versa.
|
||||
** It can be built as a standalone program or an SQLite3 extension.
|
||||
**
|
||||
** Much like base64 representations, base85 can be sent through a
|
||||
** sane USASCII channel unmolested. It also plays nicely in CSV or
|
||||
** written as TCL brace-enclosed literals or SQL string literals.
|
||||
** It is not suited for unmodified use in XML-like documents.
|
||||
**
|
||||
** The encoding used resembles Ascii85, but was devised by the author
|
||||
** (Larry Brasfield) before Mozilla, Adobe, ZMODEM or other Ascii85
|
||||
** variant sources existed, in the 1984 timeframe on a VAX mainframe.
|
||||
** Further, this is an independent implementation of a base85 system.
|
||||
** Hence, the author has rightfully put this into the public domain.
|
||||
**
|
||||
** Base85 numerals are taken from the set of 7-bit USASCII codes,
|
||||
** excluding control characters and Space ! " ' ( ) { | } ~ Del
|
||||
** in code order representing digit values 0 to 84 (base 10.)
|
||||
**
|
||||
** Groups of 4 bytes, interpreted as big-endian 32-bit values,
|
||||
** are represented as 5-digit base85 numbers with MS to LS digit
|
||||
** order. Groups of 1-3 bytes are represented with 2-4 digits,
|
||||
** still big-endian but 8-24 bit values. (Using big-endian yields
|
||||
** the simplest transition to byte groups smaller than 4 bytes.
|
||||
** These byte groups can also be considered base-256 numbers.)
|
||||
** Groups of 0 bytes are represented with 0 digits and vice-versa.
|
||||
** No pad characters are used; Encoded base85 numeral sequence
|
||||
** (aka "group") length maps 1-to-1 to the decoded binary length.
|
||||
**
|
||||
** Any character not in the base85 numeral set delimits groups.
|
||||
** When base85 is streamed or stored in containers of indefinite
|
||||
** size, newline is used to separate it into sub-sequences of no
|
||||
** more than 80 digits so that fgets() can be used to read it.
|
||||
**
|
||||
** Length limitations are not imposed except that the runtime
|
||||
** SQLite string or blob length limits are respected. Otherwise,
|
||||
** any length binary sequence can be represented and recovered.
|
||||
** Base85 sequences can be concatenated by separating them with
|
||||
** a non-base85 character; the conversion to binary will then
|
||||
** be the concatenation of the represented binary sequences.
|
||||
|
||||
** The standalone program either converts base85 on stdin to create
|
||||
** a binary file or converts a binary file to base85 on stdout.
|
||||
** Read or make it blurt its help for invocation details.
|
||||
**
|
||||
** The SQLite3 extension creates a function, base85(x), which will
|
||||
** either convert text base85 to a blob or a blob to text base85
|
||||
** and return the result (or throw an error for other types.)
|
||||
** Unless built with OMIT_BASE85_CHECKER defined, it also creates a
|
||||
** function, is_base85(t), which returns 1 iff the text t contains
|
||||
** nothing other than base85 numerals and whitespace, or 0 otherwise.
|
||||
**
|
||||
** To build the extension:
|
||||
** Set shell variable SQDIR=<your favorite SQLite checkout directory>
|
||||
** and variable OPTS to -DOMIT_BASE85_CHECKER if is_base85() unwanted.
|
||||
** *Nix: gcc -O2 -shared -I$SQDIR $OPTS -fPIC -o base85.so base85.c
|
||||
** OSX: gcc -O2 -dynamiclib -fPIC -I$SQDIR $OPTS -o base85.dylib base85.c
|
||||
** Win32: gcc -O2 -shared -I%SQDIR% %OPTS% -o base85.dll base85.c
|
||||
** Win32: cl /Os -I%SQDIR% %OPTS% base85.c -link -dll -out:base85.dll
|
||||
**
|
||||
** To build the standalone program, define PP symbol BASE85_STANDALONE. Eg.
|
||||
** *Nix or OSX: gcc -O2 -DBASE85_STANDALONE base85.c -o base85
|
||||
** Win32: gcc -O2 -DBASE85_STANDALONE -o base85.exe base85.c
|
||||
** Win32: cl /Os /MD -DBASE85_STANDALONE base85.c
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <memory.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#ifndef OMIT_BASE85_CHECKER
|
||||
# include <ctype.h>
|
||||
#endif
|
||||
|
||||
#ifndef BASE85_STANDALONE
|
||||
|
||||
#ifndef SQLITE_SHELL_EXTFUNCS /* Guard for #include as built-in extension. */
|
||||
# include "sqlite3ext.h"
|
||||
#endif
|
||||
|
||||
SQLITE_EXTENSION_INIT1;
|
||||
|
||||
#else
|
||||
|
||||
# ifdef _WIN32
|
||||
# include <io.h>
|
||||
# include <fcntl.h>
|
||||
# else
|
||||
# define setmode(fd,m)
|
||||
# endif
|
||||
|
||||
static char *zHelp =
|
||||
"Usage: base85 <dirFlag> <binFile>\n"
|
||||
" <dirFlag> is either -r to read or -w to write <binFile>,\n"
|
||||
" content to be converted to/from base85 on stdout/stdin.\n"
|
||||
" <binFile> names a binary file to be rendered or created.\n"
|
||||
" Or, the name '-' refers to the stdin or stdout stream.\n"
|
||||
;
|
||||
|
||||
static void sayHelp(){
|
||||
printf("%s", zHelp);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef UBYTE_TYPEDEF
|
||||
typedef unsigned char ubyte;
|
||||
# define UBYTE_TYPEDEF
|
||||
#endif
|
||||
|
||||
/* Classify c according to interval within USASCII set w.r.t. base85
|
||||
* Values of 1 and 3 are base85 numerals. Values of 0, 2, or 4 are not.
|
||||
*/
|
||||
#define B85_CLASS( c ) (((c)>='#')+((c)>'&')+((c)>='*')+((c)>'z'))
|
||||
|
||||
/* Provide digitValue to b85Numeral offset as a function of above class. */
|
||||
static ubyte b85_cOffset[] = { 0, '#', 0, '*'-4, 0 };
|
||||
#define B85_DNOS( c ) b85_cOffset[B85_CLASS(c)]
|
||||
|
||||
/* Say whether c is a base85 numeral. */
|
||||
#define IS_B85( c ) (B85_CLASS(c) & 1)
|
||||
|
||||
#if 0 /* Not used, */
|
||||
static ubyte base85DigitValue( char c ){
|
||||
ubyte dv = (ubyte)(c - '#');
|
||||
if( dv>87 ) return 0xff;
|
||||
return (dv > 3)? dv-3 : dv;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Width of base64 lines. Should be an integer multiple of 5. */
|
||||
#define B85_DARK_MAX 80
|
||||
|
||||
|
||||
static char * skipNonB85( char *s ){
|
||||
char c;
|
||||
while( (c = *s) && !IS_B85(c) ) ++s;
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Convert small integer, known to be in 0..84 inclusive, to base85 numeral.
|
||||
* Do not use the macro form with argument expression having a side-effect.*/
|
||||
#if 0
|
||||
static char base85Numeral( ubyte b ){
|
||||
return (b < 4)? (char)(b + '#') : (char)(b - 4 + '*');
|
||||
}
|
||||
#else
|
||||
# define base85Numeral( dn )\
|
||||
((char)(((dn) < 4)? (char)((dn) + '#') : (char)((dn) - 4 + '*')))
|
||||
#endif
|
||||
|
||||
static char *putcs(char *pc, char *s){
|
||||
char c;
|
||||
while( (c = *s++)!=0 ) *pc++ = c;
|
||||
return pc;
|
||||
}
|
||||
|
||||
/* Encode a byte buffer into base85 text. If pSep!=0, it's a C string
|
||||
** to be appended to encoded groups to limit their length to B85_DARK_MAX
|
||||
** or to terminate the last group (to aid concatenation.)
|
||||
*/
|
||||
static char* toBase85( ubyte *pIn, int nbIn, char *pOut, char *pSep ){
|
||||
int nCol = 0;
|
||||
while( nbIn >= 4 ){
|
||||
int nco = 5;
|
||||
unsigned long qbv = (pIn[0]<<24)|(pIn[1]<<16)|(pIn[2]<<8)|pIn[3];
|
||||
while( nco > 0 ){
|
||||
unsigned nqv = (unsigned)(qbv/85UL);
|
||||
unsigned char dv = qbv - 85UL*nqv;
|
||||
qbv = nqv;
|
||||
pOut[--nco] = base85Numeral(dv);
|
||||
}
|
||||
nbIn -= 4;
|
||||
pIn += 4;
|
||||
pOut += 5;
|
||||
if( pSep && (nCol += 5)>=B85_DARK_MAX ){
|
||||
pOut = putcs(pOut, pSep);
|
||||
nCol = 0;
|
||||
}
|
||||
}
|
||||
if( nbIn > 0 ){
|
||||
int nco = nbIn + 1;
|
||||
unsigned long qv = *pIn++;
|
||||
int nbe = 1;
|
||||
while( nbe++ < nbIn ){
|
||||
qv = (qv<<8) | *pIn++;
|
||||
}
|
||||
nCol += nco;
|
||||
while( nco > 0 ){
|
||||
ubyte dv = (ubyte)(qv % 85);
|
||||
qv /= 85;
|
||||
pOut[--nco] = base85Numeral(dv);
|
||||
}
|
||||
pOut += (nbIn+1);
|
||||
}
|
||||
if( pSep && nCol>0 ) pOut = putcs(pOut, pSep);
|
||||
*pOut = 0;
|
||||
return pOut;
|
||||
}
|
||||
|
||||
/* Decode base85 text into a byte buffer. */
|
||||
static ubyte* fromBase85( char *pIn, int ncIn, ubyte *pOut ){
|
||||
if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn;
|
||||
while( ncIn>0 ){
|
||||
static signed char nboi[] = { 0, 0, 1, 2, 3, 4 };
|
||||
char *pUse = skipNonB85(pIn);
|
||||
unsigned long qv = 0L;
|
||||
int nti, nbo;
|
||||
ncIn -= (pUse - pIn);
|
||||
pIn = pUse;
|
||||
nti = (ncIn>5)? 5 : ncIn;
|
||||
nbo = nboi[nti];
|
||||
if( nbo==0 ) break;
|
||||
while( nti>0 ){
|
||||
char c = *pIn++;
|
||||
ubyte cdo = B85_DNOS(c);
|
||||
--ncIn;
|
||||
if( cdo==0 ) break;
|
||||
qv = 85 * qv + (c - cdo);
|
||||
--nti;
|
||||
}
|
||||
nbo -= nti; /* Adjust for early (non-digit) end of group. */
|
||||
switch( nbo ){
|
||||
case 4:
|
||||
*pOut++ = (qv >> 24)&0xff;
|
||||
case 3:
|
||||
*pOut++ = (qv >> 16)&0xff;
|
||||
case 2:
|
||||
*pOut++ = (qv >> 8)&0xff;
|
||||
case 1:
|
||||
*pOut++ = qv&0xff;
|
||||
case 0:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return pOut;
|
||||
}
|
||||
|
||||
#ifndef OMIT_BASE85_CHECKER
|
||||
/* Say whether input char sequence is all (base85 and/or whitespace).*/
|
||||
static int allBase85( char *p, int len ){
|
||||
char c;
|
||||
while( len-- > 0 && (c = *p++) != 0 ){
|
||||
if( !IS_B85(c) && !isspace(c) ) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef BASE85_STANDALONE
|
||||
|
||||
# ifndef OMIT_BASE85_CHECKER
|
||||
/* This function does the work for the SQLite is_base85(t) UDF. */
|
||||
static void is_base85(sqlite3_context *context, int na, sqlite3_value *av[]){
|
||||
assert(na==1);
|
||||
switch( sqlite3_value_type(av[0]) ){
|
||||
case SQLITE_TEXT:
|
||||
{
|
||||
int rv = allBase85( (char *)sqlite3_value_text(av[0]),
|
||||
sqlite3_value_bytes(av[0]) );
|
||||
sqlite3_result_int(context, rv);
|
||||
}
|
||||
break;
|
||||
case SQLITE_NULL:
|
||||
sqlite3_result_null(context);
|
||||
break;
|
||||
default:
|
||||
sqlite3_result_error(context, "is_base85 accepts only text or NULL", -1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
# endif
|
||||
|
||||
/* This function does the work for the SQLite base85(x) UDF. */
|
||||
static void base85(sqlite3_context *context, int na, sqlite3_value *av[]){
|
||||
int nb, nc, nv = sqlite3_value_bytes(av[0]);
|
||||
int nvMax = sqlite3_limit(sqlite3_context_db_handle(context),
|
||||
SQLITE_LIMIT_LENGTH, -1);
|
||||
char *cBuf;
|
||||
ubyte *bBuf;
|
||||
assert(na==1);
|
||||
switch( sqlite3_value_type(av[0]) ){
|
||||
case SQLITE_BLOB:
|
||||
nb = nv;
|
||||
/* ulongs tail newlines tailenc+nul*/
|
||||
nc = 5*(nv/4) + nv%4 + nv/64+1 + 2;
|
||||
if( nvMax < nc ){
|
||||
sqlite3_result_error(context, "blob expanded to base85 too big", -1);
|
||||
return;
|
||||
}
|
||||
cBuf = sqlite3_malloc(nc);
|
||||
if( !cBuf ) goto memFail;
|
||||
bBuf = (ubyte*)sqlite3_value_blob(av[0]);
|
||||
nc = (int)(toBase85(bBuf, nb, cBuf, "\n") - cBuf);
|
||||
sqlite3_result_text(context, cBuf, nc, sqlite3_free);
|
||||
break;
|
||||
case SQLITE_TEXT:
|
||||
nc = nv;
|
||||
nb = 4*(nv/5) + nv%5; /* may overestimate */
|
||||
if( nvMax < nb ){
|
||||
sqlite3_result_error(context, "blob from base85 may be too big", -1);
|
||||
return;
|
||||
}else if( nb<1 ){
|
||||
nb = 1;
|
||||
}
|
||||
bBuf = sqlite3_malloc(nb);
|
||||
if( !bBuf ) goto memFail;
|
||||
cBuf = (char *)sqlite3_value_text(av[0]);
|
||||
nb = (int)(fromBase85(cBuf, nc, bBuf) - bBuf);
|
||||
sqlite3_result_blob(context, bBuf, nb, sqlite3_free);
|
||||
break;
|
||||
default:
|
||||
sqlite3_result_error(context, "base85 accepts only blob or text.", -1);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
memFail:
|
||||
sqlite3_result_error(context, "base85 OOM", -1);
|
||||
}
|
||||
|
||||
/*
|
||||
** Establish linkage to running SQLite library.
|
||||
*/
|
||||
#ifndef SQLITE_SHELL_EXTFUNCS
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
int sqlite3_base_init
|
||||
#else
|
||||
static int sqlite3_base85_init
|
||||
#endif
|
||||
(sqlite3 *db, char **pzErr, const sqlite3_api_routines *pApi){
|
||||
SQLITE_EXTENSION_INIT2(pApi);
|
||||
(void)pzErr;
|
||||
# ifndef OMIT_BASE85_CHECKER
|
||||
{
|
||||
int rc = sqlite3_create_function
|
||||
(db, "is_base85", 1,
|
||||
SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|SQLITE_UTF8,
|
||||
0, is_base85, 0, 0);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
}
|
||||
# endif
|
||||
return sqlite3_create_function
|
||||
(db, "base85", 1,
|
||||
SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|SQLITE_DIRECTONLY|SQLITE_UTF8,
|
||||
0, base85, 0, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
** Define some macros to allow this extension to be built into the shell
|
||||
** conveniently, in conjunction with use of SQLITE_SHELL_EXTFUNCS. This
|
||||
** allows shell.c, as distributed, to have this extension built in.
|
||||
*/
|
||||
# define BASE85_INIT(db) sqlite3_base85_init(db, 0, 0)
|
||||
# define BASE85_EXPOSE(db, pzErr) /* Not needed, ..._init() does this. */
|
||||
|
||||
#else /* standalone program */
|
||||
|
||||
int main(int na, char *av[]){
|
||||
int cin;
|
||||
int rc = 0;
|
||||
ubyte bBuf[4*(B85_DARK_MAX/5)];
|
||||
char cBuf[5*(sizeof(bBuf)/4)+2];
|
||||
size_t nio;
|
||||
# ifndef OMIT_BASE85_CHECKER
|
||||
int b85Clean = 1;
|
||||
# endif
|
||||
char rw;
|
||||
FILE *fb = 0, *foc = 0;
|
||||
char fmode[3] = "xb";
|
||||
if( na < 3 || av[1][0]!='-' || (rw = av[1][1])==0 || (rw!='r' && rw!='w') ){
|
||||
sayHelp();
|
||||
return 0;
|
||||
}
|
||||
fmode[0] = rw;
|
||||
if( av[2][0]=='-' && av[2][1]==0 ){
|
||||
switch( rw ){
|
||||
case 'r':
|
||||
fb = stdin;
|
||||
setmode(fileno(stdin), O_BINARY);
|
||||
break;
|
||||
case 'w':
|
||||
fb = stdout;
|
||||
setmode(fileno(stdout), O_BINARY);
|
||||
break;
|
||||
}
|
||||
}else{
|
||||
fb = fopen(av[2], fmode);
|
||||
foc = fb;
|
||||
}
|
||||
if( !fb ){
|
||||
fprintf(stderr, "Cannot open %s for %c\n", av[2], rw);
|
||||
rc = 1;
|
||||
}else{
|
||||
switch( rw ){
|
||||
case 'r':
|
||||
while( (nio = fread( bBuf, 1, sizeof(bBuf), fb))>0 ){
|
||||
toBase85( bBuf, (int)nio, cBuf, 0 );
|
||||
fprintf(stdout, "%s\n", cBuf);
|
||||
}
|
||||
break;
|
||||
case 'w':
|
||||
while( 0 != fgets(cBuf, sizeof(cBuf), stdin) ){
|
||||
int nc = strlen(cBuf);
|
||||
size_t nbo = fromBase85( cBuf, nc, bBuf ) - bBuf;
|
||||
if( 1 != fwrite(bBuf, nbo, 1, fb) ) rc = 1;
|
||||
# ifndef OMIT_BASE85_CHECKER
|
||||
b85Clean &= allBase85( cBuf, nc );
|
||||
# endif
|
||||
}
|
||||
break;
|
||||
default:
|
||||
sayHelp();
|
||||
rc = 1;
|
||||
}
|
||||
if( foc ) fclose(foc);
|
||||
}
|
||||
# ifndef OMIT_BASE85_CHECKER
|
||||
if( !b85Clean ){
|
||||
fprintf(stderr, "Base85 input had non-base85 dark or control content.\n");
|
||||
}
|
||||
# endif
|
||||
return rc;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
** 2022-11-20
|
||||
**
|
||||
** 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 source allows multiple SQLite extensions to be either: combined
|
||||
** into a single runtime-loadable library; or built into the SQLite shell
|
||||
** using a preprocessing convention set by src/shell.c.in (and shell.c).
|
||||
**
|
||||
** Presently, it combines the base64.c and base85.c extensions. However,
|
||||
** it can be used as a template for other combinations.
|
||||
**
|
||||
** Example usages:
|
||||
**
|
||||
** - Build a runtime-loadable extension from SQLite checkout directory:
|
||||
** *Nix, OSX: gcc -O2 -shared -I. -fPIC -o basexx.so ext/misc/basexx.c
|
||||
** Win32: cl /Os -I. ext/misc/basexx.c -link -dll -out:basexx.dll
|
||||
**
|
||||
** - Incorporate as built-in in sqlite3 shell:
|
||||
** *Nix, OSX with gcc on a like platform:
|
||||
** export mop1=-DSQLITE_SHELL_EXTSRC=ext/misc/basexx.c
|
||||
** export mop2=-DSQLITE_SHELL_EXTFUNCS=BASEXX
|
||||
** make sqlite3 "OPTS=$mop1 $mop2"
|
||||
** Win32 with Microsoft toolset on Windows:
|
||||
** set mop1=-DSQLITE_SHELL_EXTSRC=ext/misc/basexx.c
|
||||
** set mop2=-DSQLITE_SHELL_EXTFUNCS=BASEXX
|
||||
** set mops="OPTS=%mop1% %mop2%"
|
||||
** nmake -f Makefile.msc sqlite3.exe %mops%
|
||||
*/
|
||||
|
||||
#ifndef SQLITE_SHELL_EXTFUNCS /* Guard for #include as built-in extension. */
|
||||
# include "sqlite3ext.h"
|
||||
SQLITE_EXTENSION_INIT1;
|
||||
#endif
|
||||
|
||||
static void init_api_ptr(const sqlite3_api_routines *pApi){
|
||||
SQLITE_EXTENSION_INIT2(pApi);
|
||||
}
|
||||
|
||||
#undef SQLITE_EXTENSION_INIT1
|
||||
#define SQLITE_EXTENSION_INIT1 /* */
|
||||
#undef SQLITE_EXTENSION_INIT2
|
||||
#define SQLITE_EXTENSION_INIT2(v) (void)v
|
||||
|
||||
/* These next 2 undef's are only needed because the entry point names
|
||||
* collide when formulated per the rules stated for loadable extension
|
||||
* entry point names that will be deduced from the file basenames.
|
||||
*/
|
||||
#undef sqlite3_base_init
|
||||
#define sqlite3_base_init sqlite3_base64_init
|
||||
#include "base64.c"
|
||||
|
||||
#undef sqlite3_base_init
|
||||
#define sqlite3_base_init sqlite3_base85_init
|
||||
#include "base85.c"
|
||||
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
int sqlite3_basexx_init(sqlite3 *db, char **pzErr,
|
||||
const sqlite3_api_routines *pApi){
|
||||
init_api_ptr(pApi);
|
||||
int rc1 = BASE64_INIT(db);
|
||||
int rc2 = BASE85_INIT(db);
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
if( rc1==SQLITE_OK && rc2==SQLITE_OK ){
|
||||
BASE64_EXPOSE(db, pzErr);
|
||||
BASE64_EXPOSE(db, pzErr);
|
||||
return SQLITE_OK;
|
||||
}else{
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
# define BASEXX_INIT(db) sqlite3_basexx_init(db, 0, 0)
|
||||
# define BASEXX_EXPOSE(db, pzErr) /* Not needed, ..._init() does this. */
|
||||
+4
-4
@@ -531,7 +531,7 @@ static void sha3Func(
|
||||
/* Compute a string using sqlite3_vsnprintf() with a maximum length
|
||||
** of 50 bytes and add it to the hash.
|
||||
*/
|
||||
static void hash_step_vformat(
|
||||
static void sha3_step_vformat(
|
||||
SHA3Context *p, /* Add content to this context */
|
||||
const char *zFormat,
|
||||
...
|
||||
@@ -627,7 +627,7 @@ static void sha3QueryFunc(
|
||||
z = sqlite3_sql(pStmt);
|
||||
if( z ){
|
||||
n = (int)strlen(z);
|
||||
hash_step_vformat(&cx,"S%d:",n);
|
||||
sha3_step_vformat(&cx,"S%d:",n);
|
||||
SHA3Update(&cx,(unsigned char*)z,n);
|
||||
}
|
||||
|
||||
@@ -671,14 +671,14 @@ static void sha3QueryFunc(
|
||||
case SQLITE_TEXT: {
|
||||
int n2 = sqlite3_column_bytes(pStmt, i);
|
||||
const unsigned char *z2 = sqlite3_column_text(pStmt, i);
|
||||
hash_step_vformat(&cx,"T%d:",n2);
|
||||
sha3_step_vformat(&cx,"T%d:",n2);
|
||||
SHA3Update(&cx, z2, n2);
|
||||
break;
|
||||
}
|
||||
case SQLITE_BLOB: {
|
||||
int n2 = sqlite3_column_bytes(pStmt, i);
|
||||
const unsigned char *z2 = sqlite3_column_blob(pStmt, i);
|
||||
hash_step_vformat(&cx,"B%d:",n2);
|
||||
sha3_step_vformat(&cx,"B%d:",n2);
|
||||
SHA3Update(&cx, z2, n2);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -273,5 +273,46 @@ do_execsql_test 15.1 {
|
||||
} {}
|
||||
do_recover_test 15
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 16.1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x);
|
||||
INSERT INTO t1 VALUES(1), (2), (3);
|
||||
} {wal}
|
||||
do_test 16.2 {
|
||||
set R [sqlite3_recover_init db main test.db2]
|
||||
$R run
|
||||
$R finish
|
||||
} {}
|
||||
do_execsql_test 16.3 {
|
||||
SELECT * FROM t1;
|
||||
} {1 2 3}
|
||||
|
||||
do_execsql_test 16.4 {
|
||||
BEGIN;
|
||||
SELECT * FROM t1;
|
||||
} {1 2 3}
|
||||
do_test 16.5 {
|
||||
set R [sqlite3_recover_init db main test.db2]
|
||||
$R run
|
||||
list [catch { $R finish } msg] $msg
|
||||
} {1 {cannot start a transaction within a transaction}}
|
||||
do_execsql_test 16.6 {
|
||||
SELECT * FROM t1;
|
||||
} {1 2 3}
|
||||
do_execsql_test 16.7 {
|
||||
INSERT INTO t1 VALUES(4);
|
||||
}
|
||||
do_test 16.8 {
|
||||
set R [sqlite3_recover_init db main test.db2]
|
||||
$R run
|
||||
list [catch { $R finish } msg] $msg
|
||||
} {1 {cannot start a transaction within a transaction}}
|
||||
do_execsql_test 16.9 {
|
||||
SELECT * FROM t1;
|
||||
COMMIT;
|
||||
} {1 2 3 4}
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -2017,6 +2017,7 @@ static void recoverFinalCleanup(sqlite3_recover *p){
|
||||
p->pTblList = 0;
|
||||
sqlite3_finalize(p->pGetPage);
|
||||
p->pGetPage = 0;
|
||||
sqlite3_file_control(p->dbIn, p->zDb, SQLITE_FCNTL_RESET_CACHE, 0);
|
||||
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
@@ -2315,6 +2316,7 @@ static int recoverVfsRead(sqlite3_file *pFd, void *aBuf, int nByte, i64 iOff){
|
||||
**
|
||||
** + first freelist page (32-bits at offset 32)
|
||||
** + size of freelist (32-bits at offset 36)
|
||||
** + the wal-mode flags (16-bits at offset 18)
|
||||
**
|
||||
** We also try to preserve the auto-vacuum, incr-value, user-version
|
||||
** and application-id fields - all 32 bit quantities at offsets
|
||||
@@ -2378,7 +2380,8 @@ static int recoverVfsRead(sqlite3_file *pFd, void *aBuf, int nByte, i64 iOff){
|
||||
if( p->pPage1Cache ){
|
||||
p->pPage1Disk = &p->pPage1Cache[nByte];
|
||||
memcpy(p->pPage1Disk, aBuf, nByte);
|
||||
|
||||
aHdr[18] = a[18];
|
||||
aHdr[19] = a[19];
|
||||
recoverPutU32(&aHdr[28], dbsz);
|
||||
recoverPutU32(&aHdr[56], enc);
|
||||
recoverPutU16(&aHdr[105], pgsz-nReserve);
|
||||
@@ -2574,6 +2577,7 @@ static void recoverStep(sqlite3_recover *p){
|
||||
recoverOpenOutput(p);
|
||||
|
||||
/* Open transactions on both the input and output databases. */
|
||||
sqlite3_file_control(p->dbIn, p->zDb, SQLITE_FCNTL_RESET_CACHE, 0);
|
||||
recoverExec(p, p->dbIn, "PRAGMA writable_schema = on");
|
||||
recoverExec(p, p->dbIn, "BEGIN");
|
||||
if( p->errCode==SQLITE_OK ) p->bCloseTransaction = 1;
|
||||
|
||||
@@ -289,7 +289,8 @@ sqlite3-api.jses += $(dir.api)/sqlite3-api-glue.js
|
||||
sqlite3-api.jses += $(sqlite3-api-build-version.js)
|
||||
sqlite3-api.jses += $(dir.api)/sqlite3-api-oo1.js
|
||||
sqlite3-api.jses += $(dir.api)/sqlite3-api-worker1.js
|
||||
sqlite3-api.jses += $(dir.api)/sqlite3-api-opfs.js
|
||||
sqlite3-api.jses += $(dir.api)/sqlite3-vfs-helper.js
|
||||
sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs.js
|
||||
sqlite3-api.jses += $(dir.api)/sqlite3-api-cleanup.js
|
||||
|
||||
# "External" API files which are part of our distribution
|
||||
|
||||
+16
-11
@@ -23,13 +23,13 @@ The overall idea is that the following files get concatenated
|
||||
together, in the listed order, the resulting file is loaded by a
|
||||
browser client:
|
||||
|
||||
- `sqlite3-api-prologue.js`\
|
||||
- **`sqlite3-api-prologue.js`**\
|
||||
Contains the initial bootstrap setup of the sqlite3 API
|
||||
objects. This is exposed as a function, rather than objects, so that
|
||||
the next step can pass in a config object which abstracts away parts
|
||||
of the WASM environment, to facilitate plugging it in to arbitrary
|
||||
WASM toolchains.
|
||||
- `../common/whwasmutil.js`\
|
||||
- **`../common/whwasmutil.js`**\
|
||||
A semi-third-party collection of JS/WASM utility code intended to
|
||||
replace much of the Emscripten glue. The sqlite3 APIs internally use
|
||||
these APIs instead of their Emscripten counterparts, in order to be
|
||||
@@ -38,47 +38,52 @@ browser client:
|
||||
toolchains. It is "semi-third-party" in that it was created in order
|
||||
to support this tree but is standalone and maintained together
|
||||
with...
|
||||
- `../jaccwabyt/jaccwabyt.js`\
|
||||
- **`../jaccwabyt/jaccwabyt.js`**\
|
||||
Another semi-third-party API which creates bindings between JS
|
||||
and C structs, such that changes to the struct state from either JS
|
||||
or C are visible to the other end of the connection. This is also an
|
||||
independent spinoff project, conceived for the sqlite3 project but
|
||||
maintained separately.
|
||||
- `sqlite3-api-glue.js`\
|
||||
- **`sqlite3-api-glue.js`**\
|
||||
Invokes functionality exposed by the previous two files to
|
||||
flesh out low-level parts of `sqlite3-api-prologue.js`. Most of
|
||||
these pieces related to the `sqlite3.capi.wasm` object.
|
||||
- `sqlite3-api-build-version.js`\
|
||||
- **`sqlite3-api-build-version.js`**\
|
||||
Gets created by the build process and populates the
|
||||
`sqlite3.version` object. This part is not critical, but records the
|
||||
version of the library against which this module was built.
|
||||
- `sqlite3-api-oo1.js`\
|
||||
- **`sqlite3-api-oo1.js`**\
|
||||
Provides a high-level object-oriented wrapper to the lower-level C
|
||||
API, colloquially known as OO API #1. Its API is similar to other
|
||||
high-level sqlite3 JS wrappers and should feel relatively familiar
|
||||
to anyone familiar with such APIs. That said, it is not a "required
|
||||
component" and can be elided from builds which do not want it.
|
||||
- `sqlite3-api-worker1.js`\
|
||||
- **`sqlite3-api-worker1.js`**\
|
||||
A Worker-thread-based API which uses OO API #1 to provide an
|
||||
interface to a database which can be driven from the main Window
|
||||
thread via the Worker message-passing interface. Like OO API #1,
|
||||
this is an optional component, offering one of any number of
|
||||
potential implementations for such an API.
|
||||
- `sqlite3-worker1.js`\
|
||||
- **`sqlite3-worker1.js`**\
|
||||
Is not part of the amalgamated sources and is intended to be
|
||||
loaded by a client Worker thread. It loads the sqlite3 module
|
||||
and runs the Worker #1 API which is implemented in
|
||||
`sqlite3-api-worker1.js`.
|
||||
- `sqlite3-worker1-promiser.js`\
|
||||
- **`sqlite3-worker1-promiser.js`**\
|
||||
Is likewise not part of the amalgamated sources and provides
|
||||
a Promise-based interface into the Worker #1 API. This is
|
||||
a far user-friendlier way to interface with databases running
|
||||
in a Worker thread.
|
||||
- `sqlite3-api-opfs.js`\
|
||||
- **`sqlite3-vfs-helper.js`**\
|
||||
This internal-use-only file installs `sqlite3.VfsHelper` for use by
|
||||
`sqlite3-*.js` files which create `sqlite3_vfs` implemenations.
|
||||
`sqlite3.VfsHelper` gets removed from the the `sqlite3` object after
|
||||
the library is finished initializing.
|
||||
- **`sqlite3-vfs-opfs.js`**\
|
||||
is an sqlite3 VFS implementation which supports Google Chrome's
|
||||
Origin-Private FileSystem (OPFS) as a storage layer to provide
|
||||
persistent storage for database files in a browser. It requires...
|
||||
- `sqlite3-opfs-async-proxy.js`\
|
||||
- **`sqlite3-opfs-async-proxy.js`**\
|
||||
is the asynchronous backend part of the OPFS proxy. It speaks
|
||||
directly to the (async) OPFS API and channels those results back
|
||||
to its synchronous counterpart. This file, because it must be
|
||||
|
||||
@@ -59,11 +59,8 @@ const toExportForES6 =
|
||||
li.pop();
|
||||
initModuleState.sqlite3Dir = li.join('/') + '/';
|
||||
}
|
||||
if(initModuleState.sqlite3Dir){
|
||||
initModuleState.sqlite3Dir = initModuleState.sqlite3Dir.replace(/[/]{2,}/g,'/');
|
||||
}
|
||||
|
||||
self.sqlite3InitModule = (...args)=>{
|
||||
self.sqlite3InitModule = function ff(...args){
|
||||
//console.warn("Using replaced sqlite3InitModule()",self.location);
|
||||
return originalInit(...args).then((EmscriptenModule)=>{
|
||||
if(self.window!==self &&
|
||||
@@ -79,10 +76,12 @@ const toExportForES6 =
|
||||
Emscripten details. */
|
||||
return EmscriptenModule;
|
||||
}
|
||||
EmscriptenModule.sqlite3.scriptInfo = initModuleState;
|
||||
//console.warn("sqlite3.scriptInfo =",EmscriptenModule.sqlite3.scriptInfo);
|
||||
const f = EmscriptenModule.sqlite3.asyncPostInit;
|
||||
delete EmscriptenModule.sqlite3.asyncPostInit;
|
||||
const s = EmscriptenModule.sqlite3;
|
||||
s.scriptInfo = initModuleState;
|
||||
//console.warn("sqlite3.scriptInfo =",s.scriptInfo);
|
||||
if(ff.__isUnderTest) s.__isUnderTest = true;
|
||||
const f = s.asyncPostInit;
|
||||
delete s.asyncPostInit;
|
||||
return f();
|
||||
}).catch((e)=>{
|
||||
console.error("Exception loading sqlite3 module:",e);
|
||||
|
||||
@@ -58,8 +58,6 @@ if('undefined' !== typeof Module){ // presumably an Emscripten build
|
||||
self.S = sqlite3;
|
||||
}
|
||||
|
||||
/* Clean up temporary references to our APIs... */
|
||||
delete sqlite3.util /* arguable, but these are (currently) internal-use APIs */;
|
||||
Module.sqlite3 = sqlite3 /* Needed for customized sqlite3InitModule() to be able to
|
||||
pass the sqlite3 object off to the client. */;
|
||||
}else{
|
||||
|
||||
@@ -69,6 +69,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
*/
|
||||
const aPtr = wasm.xWrap.argAdapter('*');
|
||||
wasm.xWrap.argAdapter('sqlite3*', aPtr)
|
||||
('sqlite3_filename', aPtr)
|
||||
('sqlite3_stmt*', aPtr)
|
||||
('sqlite3_context*', aPtr)
|
||||
('sqlite3_value*', aPtr)
|
||||
|
||||
@@ -55,12 +55,13 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
if(sqliteResultCode){
|
||||
if(dbPtr instanceof DB) dbPtr = dbPtr.pointer;
|
||||
toss3(
|
||||
"sqlite result code",sqliteResultCode+":",
|
||||
"sqlite3 result code",sqliteResultCode+":",
|
||||
(dbPtr
|
||||
? capi.sqlite3_errmsg(dbPtr)
|
||||
: capi.sqlite3_errstr(sqliteResultCode))
|
||||
);
|
||||
}
|
||||
return arguments[0];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -200,9 +201,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
*/
|
||||
dbCtorHelper.normalizeArgs = function(filename=':memory:',flags = 'c',vfs = null){
|
||||
const arg = {};
|
||||
if(1===arguments.length && 'object'===typeof arguments[0]){
|
||||
const x = arguments[0];
|
||||
Object.keys(x).forEach((k)=>arg[k] = x[k]);
|
||||
if(1===arguments.length && arguments[0] && 'object'===typeof arguments[0]){
|
||||
Object.assign(arg, arguments[0]);
|
||||
if(undefined===arg.flags) arg.flags = 'c';
|
||||
if(undefined===arg.vfs) arg.vfs = null;
|
||||
if(undefined===arg.filename) arg.filename = ':memory:';
|
||||
@@ -462,14 +462,16 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
Expects to be given a DB instance or an `sqlite3*` pointer (may
|
||||
be null) and an sqlite3 API result code. If the result code is
|
||||
not falsy, this function throws an SQLite3Error with an error
|
||||
message from sqlite3_errmsg(), using dbPtr as the db handle, or
|
||||
sqlite3_errstr() if dbPtr is falsy. Note that if it's passed a
|
||||
non-error code like SQLITE_ROW or SQLITE_DONE, it will still
|
||||
throw but the error string might be "Not an error." The various
|
||||
non-0 non-error codes need to be checked for in
|
||||
client code where they are expected.
|
||||
message from sqlite3_errmsg(), using db (or, if db is-a DB,
|
||||
db.pointer) as the db handle, or sqlite3_errstr() if db is
|
||||
falsy. Note that if it's passed a non-error code like SQLITE_ROW
|
||||
or SQLITE_DONE, it will still throw but the error string might be
|
||||
"Not an error." The various non-0 non-error codes need to be
|
||||
checked for in client code where they are expected.
|
||||
|
||||
If it does not throw, it returns its first argument.
|
||||
*/
|
||||
DB.checkRc = checkSqlite3Rc;
|
||||
DB.checkRc = (db,resultCode)=>checkSqlite3Rc(db,resultCode);
|
||||
|
||||
DB.prototype = {
|
||||
/** Returns true if this db handle is open, else false. */
|
||||
@@ -1130,6 +1132,14 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
this.exec("ROLLBACK to SAVEPOINT oo1; RELEASE SAVEPOINT oo1");
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
A convenience form of DB.checkRc(this,resultCode). If it does
|
||||
not throw, it returns this object.
|
||||
*/
|
||||
checkRc: function(resultCode){
|
||||
return DB.checkRc(this, resultCode);
|
||||
}
|
||||
}/*DB.prototype*/;
|
||||
|
||||
|
||||
@@ -11,76 +11,18 @@
|
||||
***********************************************************************
|
||||
|
||||
This file is intended to be combined at build-time with other
|
||||
related code, most notably a header and footer which wraps this whole
|
||||
file into an Emscripten Module.postRun() handler which has a parameter
|
||||
named "Module" (the Emscripten Module object). The exact requirements,
|
||||
conventions, and build process are very much under construction and
|
||||
will be (re)documented once they've stopped fluctuating so much.
|
||||
related code, most notably a header and footer which wraps this
|
||||
whole file into an Emscripten Module.postRun() handler which has a
|
||||
parameter named "Module" (the Emscripten Module object). The sqlite3
|
||||
JS API has no hard requirements on Emscripten, and does not expose
|
||||
any Emscripten APIs to clients. It is structured such that its build
|
||||
can be tweaked to include it in arbitrary WASM environments which
|
||||
supply the necessary underlying features (e.g. a POSIX file I/O
|
||||
layer).
|
||||
|
||||
Project home page: https://sqlite.org
|
||||
Main project home page: https://sqlite.org
|
||||
|
||||
Documentation home page: https://sqlite.org/wasm
|
||||
|
||||
Specific goals of this subproject:
|
||||
|
||||
- Except where noted in the non-goals, provide a more-or-less
|
||||
feature-complete wrapper to the sqlite3 C API, insofar as WASM
|
||||
feature parity with C allows for. In fact, provide at least 4
|
||||
APIs...
|
||||
|
||||
1) 1-to-1 bindings as exported from WASM, with no automatic
|
||||
type conversions between JS and C.
|
||||
|
||||
2) A binding of (1) which provides certain JS/C type conversions
|
||||
to greatly simplify its use.
|
||||
|
||||
3) A higher-level API, more akin to sql.js and node.js-style
|
||||
implementations. This one speaks directly to the low-level
|
||||
API. This API must be used from the same thread as the
|
||||
low-level API.
|
||||
|
||||
4) A second higher-level API which speaks to the previous APIs via
|
||||
worker messages. This one is intended for use in the main
|
||||
thread, with the lower-level APIs installed in a Worker thread,
|
||||
and talking to them via Worker messages. Because Workers are
|
||||
asynchronouns and have only a single message channel, some
|
||||
acrobatics are needed here to feed async work results back to
|
||||
the client (as we cannot simply pass around callbacks between
|
||||
the main and Worker threads).
|
||||
|
||||
- Insofar as possible, support client-side storage using JS
|
||||
filesystem APIs. As of this writing, such things are still very
|
||||
much under development.
|
||||
|
||||
Specific non-goals of this project:
|
||||
|
||||
- As WASM is a web-centric technology and UTF-8 is the King of
|
||||
Encodings in that realm, there are no currently plans to support
|
||||
the UTF16-related sqlite3 APIs. They would add a complication to
|
||||
the bindings for no appreciable benefit. Though web-related
|
||||
implementation details take priority, and the JavaScript
|
||||
components of the API specifically focus on browser clients, the
|
||||
lower-level WASM module "should" work in non-web WASM
|
||||
environments.
|
||||
|
||||
- Supporting old or niche-market platforms. WASM is built for a
|
||||
modern web and requires modern platforms.
|
||||
|
||||
- Though scalar User-Defined Functions (UDFs) may be created in
|
||||
JavaScript, there are currently no plans to add support for
|
||||
aggregate and window functions.
|
||||
|
||||
Attribution:
|
||||
|
||||
This project is endebted to the work of sql.js:
|
||||
|
||||
https://github.com/sql-js/sql.js
|
||||
|
||||
sql.js was an essential stepping stone in this code's development as
|
||||
it demonstrated how to handle some of the WASM-related voodoo (like
|
||||
handling pointers-to-pointers and adding JS implementations of
|
||||
C-bound callback functions). These APIs have a considerably
|
||||
different shape than sql.js's, however.
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -90,6 +32,10 @@
|
||||
for the current environment, and then optionally be removed from
|
||||
the global object using `delete self.sqlite3ApiBootstrap`.
|
||||
|
||||
This function is not intended for client-level use. It is intended
|
||||
for use in creating bundles configured for specific WASM
|
||||
environments.
|
||||
|
||||
This function expects a configuration object, intended to abstract
|
||||
away details specific to any given WASM environment, primarily so
|
||||
that it can be used without any _direct_ dependency on
|
||||
@@ -126,11 +72,11 @@
|
||||
environment. Defaults to `"free"`.
|
||||
|
||||
- `wasmfsOpfsDir`[^1]: if the environment supports persistent
|
||||
storage, this directory names the "mount point" for that
|
||||
directory. It must be prefixed by `/` and may contain only a
|
||||
single directory-name part. Using the root directory name is not
|
||||
supported by any current persistent backend. This setting is
|
||||
only used in WASMFS-enabled builds.
|
||||
storage using OPFS-over-WASMFS , this directory names the "mount
|
||||
point" for that directory. It must be prefixed by `/` and may
|
||||
contain only a single directory-name part. Using the root
|
||||
directory name is not supported by any current persistent
|
||||
backend. This setting is only used in WASMFS-enabled builds.
|
||||
|
||||
|
||||
[^1] = This property may optionally be a function, in which case this
|
||||
@@ -191,11 +137,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
|
||||
const capi = Object.create(null);
|
||||
/**
|
||||
Holds state which are specific to the WASM-related
|
||||
infrastructure and glue code. It is not expected that client
|
||||
code will normally need these, but they're exposed here in case
|
||||
it does. These APIs are _not_ to be considered an
|
||||
official/stable part of the sqlite3 WASM API. They may change
|
||||
as the developers' experience suggests appropriate changes.
|
||||
infrastructure and glue code.
|
||||
|
||||
Note that a number of members of this object are injected
|
||||
dynamically after the api object is fully constructed, so
|
||||
@@ -228,7 +170,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
|
||||
result of sqlite3.capi.sqlite3_js_rc_str() or (if that returns
|
||||
falsy) a synthesized string which contains that integer.
|
||||
|
||||
- If passed 2 arguments and the 2nd is a object, it bevaves
|
||||
- If passed 2 arguments and the 2nd is a object, it behaves
|
||||
like the Error(string,object) constructor except that the first
|
||||
argument is subject to the is-integer semantics from the
|
||||
previous point.
|
||||
@@ -670,8 +612,11 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
|
||||
isBindableTypedArray,
|
||||
isInt32, isSQLableTypedArray, isTypedArray,
|
||||
typedArrayToString,
|
||||
isUIThread: ()=>'undefined'===typeof WorkerGlobalScope,
|
||||
isUIThread: ()=>(self.window===self && !!self.document),
|
||||
// is this true for ESM?: 'undefined'===typeof WorkerGlobalScope
|
||||
isSharedTypedArray,
|
||||
toss: function(...args){throw new Error(args.join(' '))},
|
||||
toss3,
|
||||
typedArrayPart
|
||||
};
|
||||
|
||||
@@ -686,9 +631,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
|
||||
/**
|
||||
The WASM IR (Intermediate Representation) value for
|
||||
pointer-type values. It MUST refer to a value type of the
|
||||
size described by this.ptrSizeof _or_ it may be any value
|
||||
which ends in '*', which Emscripten's glue code internally
|
||||
translates to i32.
|
||||
size described by this.ptrSizeof.
|
||||
*/
|
||||
ptrIR: config.wasmPtrIR || "i32",
|
||||
/**
|
||||
@@ -970,9 +913,12 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
|
||||
["sqlite3_strlike", "int", "string","string","int"],
|
||||
["sqlite3_trace_v2", "int", "sqlite3*", "int", "*", "*"],
|
||||
["sqlite3_total_changes", "int", "sqlite3*"],
|
||||
["sqlite3_uri_boolean", "int", "string", "string", "int"],
|
||||
["sqlite3_uri_key", "string", "string", "int"],
|
||||
["sqlite3_uri_parameter", "string", "string", "string"],
|
||||
/* Note sqlite3_uri_...() has very specific requirements
|
||||
for their first C-string arguments, so we cannot perform
|
||||
any type conversion on those. */
|
||||
["sqlite3_uri_boolean", "int", "sqlite3_filename", "string", "int"],
|
||||
["sqlite3_uri_key", "string", "sqlite3_filename", "int"],
|
||||
["sqlite3_uri_parameter", "string", "sqlite3_filename", "string"],
|
||||
["sqlite3_user_data","void*", "sqlite3_context*"],
|
||||
["sqlite3_value_blob", "*", "sqlite3_value*"],
|
||||
["sqlite3_value_bytes","int", "sqlite3_value*"],
|
||||
@@ -1006,7 +952,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
|
||||
["sqlite3_realloc64", "*","*", "i64"],
|
||||
["sqlite3_result_int64",undefined, "*", "i64"],
|
||||
["sqlite3_total_changes64", "i64", ["sqlite3*"]],
|
||||
["sqlite3_uri_int64", "i64", ["string", "string", "i64"]],
|
||||
["sqlite3_uri_int64", "i64", ["sqlite3_filename", "string", "i64"]],
|
||||
["sqlite3_value_int64","i64", "sqlite3_value*"],
|
||||
];
|
||||
|
||||
@@ -1307,17 +1253,24 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
|
||||
};
|
||||
|
||||
/**
|
||||
Serializes the given `sqlite3*` pointer to a Uint8Array, as per
|
||||
sqlite3_serialize(). On success it returns a Uint8Array. On
|
||||
error it throws with a description of the problem.
|
||||
A convenience wrapper around sqlite3_serialize() which serializes
|
||||
the given `sqlite3*` pointer to a Uint8Array.
|
||||
|
||||
On success it returns a Uint8Array. If the schema is empty, an
|
||||
empty array is returned.
|
||||
|
||||
`schema` is the schema to serialize. It may be a WASM C-string
|
||||
pointer or a JS string. If it is falsy, it defaults to `"main"`.
|
||||
|
||||
On error it throws with a description of the problem.
|
||||
*/
|
||||
capi.sqlite3_js_db_export = function(pDb){
|
||||
capi.sqlite3_js_db_export = function(pDb, schema=0){
|
||||
if(!pDb) toss3('Invalid sqlite3* argument.');
|
||||
if(!wasm.bigIntEnabled) toss3('BigInt64 support is not enabled.');
|
||||
const stack = wasm.pstack.pointer;
|
||||
const scope = wasm.scopedAllocPush();
|
||||
let pOut;
|
||||
try{
|
||||
const pSize = wasm.pstack.alloc(8/*i64*/ + wasm.ptrSizeof);
|
||||
const pSize = wasm.scopedAlloc(8/*i64*/ + wasm.ptrSizeof);
|
||||
const ppOut = pSize + 8;
|
||||
/**
|
||||
Maintenance reminder, since this cost a full hour of grief
|
||||
@@ -1326,8 +1279,11 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
|
||||
export reads a garbage size because it's not on an 8-byte
|
||||
memory boundary!
|
||||
*/
|
||||
const zSchema = schema
|
||||
? (wasm.isPtr(schema) ? schema : wasm.scopedAllocCString(''+schema))
|
||||
: 0;
|
||||
let rc = wasm.exports.sqlite3_wasm_db_serialize(
|
||||
pDb, ppOut, pSize, 0
|
||||
pDb, zSchema, ppOut, pSize, 0
|
||||
);
|
||||
if(rc){
|
||||
toss3("Database serialization failed with code",
|
||||
@@ -1341,7 +1297,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
|
||||
return rc;
|
||||
}finally{
|
||||
if(pOut) wasm.exports.sqlite3_free(pOut);
|
||||
wasm.pstack.restore(stack);
|
||||
wasm.scopedAllocPop(scope);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1497,9 +1453,6 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
|
||||
let lip = sqlite3ApiBootstrap.initializersAsync;
|
||||
delete sqlite3ApiBootstrap.initializersAsync;
|
||||
if(!lip || !lip.length) return Promise.resolve(sqlite3);
|
||||
// Is it okay to resolve these in parallel or do we need them
|
||||
// to resolve in order? We currently only have 1, so it
|
||||
// makes no difference.
|
||||
lip = lip.map((f)=>{
|
||||
const p = (f instanceof Promise) ? f : f(sqlite3);
|
||||
return p.catch((e)=>{
|
||||
@@ -1507,10 +1460,29 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
|
||||
throw e;
|
||||
});
|
||||
});
|
||||
//let p = lip.shift();
|
||||
//while(lip.length) p = p.then(lip.shift());
|
||||
//return p.then(()=>sqlite3);
|
||||
return Promise.all(lip).then(()=>sqlite3);
|
||||
const postInit = ()=>{
|
||||
if(!sqlite3.__isUnderTest){
|
||||
/* Delete references to internal-only APIs which are used by
|
||||
some initializers. Retain them when running in test mode
|
||||
so that we can add tests for them. */
|
||||
delete sqlite3.util;
|
||||
delete sqlite3.VfsHelper;
|
||||
delete sqlite3.StructBinder;
|
||||
}
|
||||
return sqlite3;
|
||||
};
|
||||
if(1){
|
||||
/* Run all initializers in sequence. The advantage is that it
|
||||
allows us to have post-init cleanup defined outside of this
|
||||
routine at the end of the list and have it run at a
|
||||
well-defined time. */
|
||||
let p = lip.shift();
|
||||
while(lip.length) p = p.then(lip.shift());
|
||||
return p.then(postInit);
|
||||
}else{
|
||||
/* Run them in an arbitrary order. */
|
||||
return Promise.all(lip).then(postInit);
|
||||
}
|
||||
},
|
||||
/**
|
||||
scriptInfo ideally gets injected into this object by the
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
** 2022-11-30
|
||||
**
|
||||
** 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 installs sqlite.VfsHelper, an object which exists
|
||||
to assist in the creation of JavaScript implementations of
|
||||
sqlite3_vfs. It is NOT part of the public API, and is an
|
||||
internal implemenation detail for use in this project's
|
||||
own development of VFSes. It may be exposed to clients
|
||||
at some point, provided there is value in doing so.
|
||||
*/
|
||||
'use strict';
|
||||
self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss;
|
||||
const vh = Object.create(null);
|
||||
|
||||
/**
|
||||
Does nothing more than holds a permanent reference to each
|
||||
argument. This is useful in some cases to ensure that, e.g., a
|
||||
custom sqlite3_io_methods instance does not get
|
||||
garbage-collected.
|
||||
|
||||
Returns this object.
|
||||
*/
|
||||
vh.holdReference = function(...args){
|
||||
for(const v of args) this.refs.add(v);
|
||||
return vh;
|
||||
}.bind({refs: new Set});
|
||||
|
||||
/**
|
||||
Installs a StructBinder-bound function pointer member of the
|
||||
given name and function in the given StructType target object.
|
||||
It creates a WASM proxy for the given function and arranges for
|
||||
that proxy to be cleaned up when tgt.dispose() is called. Throws
|
||||
on the slightest hint of error, e.g. tgt is-not-a StructType,
|
||||
name does not map to a struct-bound member, etc.
|
||||
|
||||
If applyArgcCheck is true then each method gets wrapped in a
|
||||
proxy which asserts that it is passed the expected number of
|
||||
arguments, throwing if the argument count does not match
|
||||
expectations. That is only recommended for dev-time usage for
|
||||
sanity checking. Once a VFS implementation is known to be
|
||||
working, it is a given that the C API will never call it with the
|
||||
wrong argument count.
|
||||
|
||||
Returns a proxy for this function which is bound to tgt and takes
|
||||
2 args (name,func). That function returns the same thing,
|
||||
permitting calls to be chained.
|
||||
|
||||
If called with only 1 arg, it has no side effects but returns a
|
||||
func with the same signature as described above.
|
||||
|
||||
If tgt.ondispose is set before this is called then it _must_
|
||||
be an array, to which this function will append entries.
|
||||
*/
|
||||
vh.installMethod = function callee(tgt, name, func,
|
||||
applyArgcCheck=callee.installMethodArgcCheck){
|
||||
if(!(tgt instanceof sqlite3.StructBinder.StructType)){
|
||||
toss("Usage error: target object is-not-a StructType.");
|
||||
}
|
||||
if(1===arguments.length){
|
||||
return (n,f)=>callee(tgt, n, f, applyArgcCheck);
|
||||
}
|
||||
if(!callee.argcProxy){
|
||||
callee.argcProxy = function(func,sig){
|
||||
return function(...args){
|
||||
if(func.length!==arguments.length){
|
||||
toss("Argument mismatch. Native signature is:",sig);
|
||||
}
|
||||
return func.apply(this, args);
|
||||
}
|
||||
};
|
||||
/* An ondispose() callback for use with
|
||||
sqlite3.StructBinder-created types. */
|
||||
callee.removeFuncList = function(){
|
||||
if(this.ondispose.__removeFuncList){
|
||||
this.ondispose.__removeFuncList.forEach(
|
||||
(v,ndx)=>{
|
||||
if('number'===typeof v){
|
||||
try{wasm.uninstallFunction(v)}
|
||||
catch(e){/*ignore*/}
|
||||
}
|
||||
/* else it's a descriptive label for the next number in
|
||||
the list. */
|
||||
}
|
||||
);
|
||||
delete this.ondispose.__removeFuncList;
|
||||
}
|
||||
};
|
||||
}/*static init*/
|
||||
const sigN = tgt.memberSignature(name);
|
||||
if(sigN.length<2){
|
||||
toss("Member",name," is not a function pointer. Signature =",sigN);
|
||||
}
|
||||
const memKey = tgt.memberKey(name);
|
||||
const fProxy = applyArgcCheck
|
||||
/** This middle-man proxy is only for use during development, to
|
||||
confirm that we always pass the proper number of
|
||||
arguments. We know that the C-level code will always use the
|
||||
correct argument count. */
|
||||
? callee.argcProxy(func, sigN)
|
||||
: func;
|
||||
const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
|
||||
tgt[memKey] = pFunc;
|
||||
if(!tgt.ondispose) tgt.ondispose = [];
|
||||
if(!tgt.ondispose.__removeFuncList){
|
||||
tgt.ondispose.push('ondispose.__removeFuncList handler',
|
||||
callee.removeFuncList);
|
||||
tgt.ondispose.__removeFuncList = [];
|
||||
}
|
||||
tgt.ondispose.__removeFuncList.push(memKey, pFunc);
|
||||
return (n,f)=>callee(tgt, n, f, applyArgcCheck);
|
||||
}/*installMethod*/;
|
||||
vh.installMethod.installMethodArgcCheck = false;
|
||||
|
||||
/**
|
||||
Installs methods into the given StructType-type object. Each
|
||||
entry in the given methods object must map to a known member of
|
||||
the given StructType, else an exception will be triggered.
|
||||
See installMethod() for more details, including the semantics
|
||||
of the 3rd argument.
|
||||
|
||||
On success, passes its first argument to holdRefence() and
|
||||
returns this object. Throws on error.
|
||||
*/
|
||||
vh.installMethods = function(structType, methods,
|
||||
applyArgcCheck=vh.installMethod.installMethodArgcCheck){
|
||||
for(const k of Object.keys(methods)){
|
||||
vh.installMethod(structType, k, methods[k], applyArgcCheck);
|
||||
}
|
||||
return vh.holdReference(structType);
|
||||
};
|
||||
|
||||
/**
|
||||
Uses sqlite3_vfs_register() to register the
|
||||
sqlite3.capi.sqlite3_vfs-type vfs, which must have already been
|
||||
filled out properly. If the 2nd argument is truthy, the VFS is
|
||||
registered as the default VFS, else it is not.
|
||||
|
||||
On success, passes its first argument to this.holdReference() and
|
||||
returns this object. Throws on error.
|
||||
*/
|
||||
vh.registerVfs = function(vfs, asDefault=false){
|
||||
if(!(vfs instanceof sqlite3.capi.sqlite3_vfs)){
|
||||
toss("Expecting a sqlite3_vfs-type argument.");
|
||||
}
|
||||
const rc = capi.sqlite3_vfs_register(vfs.pointer, asDefault ? 1 : 0);
|
||||
if(rc){
|
||||
toss("sqlite3_vfs_register(",vfs,") failed with rc",rc);
|
||||
}
|
||||
if(vfs.pointer !== capi.sqlite3_vfs_find(vfs.$zName)){
|
||||
toss("BUG: sqlite3_vfs_find(vfs.$zName) failed for just-installed VFS",
|
||||
vfs);
|
||||
}
|
||||
return vh.holdReference(vfs);
|
||||
};
|
||||
|
||||
/**
|
||||
A wrapper for installMethods() or registerVfs() to reduce
|
||||
installation of a VFS and/or its I/O methods to a single
|
||||
call.
|
||||
|
||||
Accepts an object which contains the properties "io" and/or
|
||||
"vfs", each of which is itself an object with following properties:
|
||||
|
||||
- `struct`: an sqlite3.StructType-type struct. This must be a
|
||||
populated (except for the methods) object of type
|
||||
sqlite3_io_methods (for the "io" entry) or sqlite3_vfs (for the
|
||||
"vfs" entry).
|
||||
|
||||
- `methods`: an object mapping sqlite3_io_methods method names
|
||||
(e.g. 'xClose') to JS implementations of those methods.
|
||||
|
||||
For each of those object, this function passes its (`struct`,
|
||||
`methods`, (optional) `applyArgcCheck`) properties to
|
||||
this.installMethods().
|
||||
|
||||
If the `vfs` entry is set then:
|
||||
|
||||
- Its `struct` property is passed to this.registerVfs(). The
|
||||
`vfs` entry may optionally have an `asDefault` property, which
|
||||
gets passed as the 2nd argument to registerVfs().
|
||||
|
||||
- If `struct.$zName` is falsy and the entry has a string-type
|
||||
`name` property, `struct.$zName` is set to the C-string form of
|
||||
that `name` value before registerVfs() is called.
|
||||
|
||||
On success returns this object. Throws on error.
|
||||
*/
|
||||
vh.installVfs = function(opt){
|
||||
let count = 0;
|
||||
const propList = ['io','vfs'];
|
||||
for(const key of propList){
|
||||
const o = opt[key];
|
||||
if(o){
|
||||
++count;
|
||||
this.installMethods(o.struct, o.methods, !!o.applyArgcCheck);
|
||||
if('vfs'===key){
|
||||
if(!o.struct.$zName && 'string'===typeof o.name){
|
||||
o.struct.$zName = wasm.allocCString(o.name);
|
||||
/* Note that we leak that C-string. */
|
||||
}
|
||||
this.registerVfs(o.struct, !!o.asDefault);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!count) toss("Misuse: installVfs() options object requires at least",
|
||||
"one of:", propList);
|
||||
return this;
|
||||
};
|
||||
|
||||
sqlite3.VfsHelper = vh;
|
||||
}/*sqlite3ApiBootstrap.initializers.push()*/);
|
||||
@@ -76,15 +76,25 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
`opfs` property, containing several OPFS-specific utilities.
|
||||
*/
|
||||
const installOpfsVfs = function callee(options){
|
||||
if(!self.SharedArrayBuffer ||
|
||||
!self.Atomics ||
|
||||
!self.FileSystemHandle ||
|
||||
!self.FileSystemDirectoryHandle ||
|
||||
!self.FileSystemFileHandle ||
|
||||
!self.FileSystemFileHandle.prototype.createSyncAccessHandle ||
|
||||
!navigator.storage.getDirectory){
|
||||
if(!self.SharedArrayBuffer
|
||||
|| !self.Atomics){
|
||||
return Promise.reject(
|
||||
new Error("This environment does not have OPFS support.")
|
||||
new Error("Cannot install OPFS: Missing SharedArrayBuffer and/or Atomics. "+
|
||||
"The server must emit the COOP/COEP response headers to enable those. "+
|
||||
"See https://sqlite.org/wasm/doc/trunk/persistence.md#coop-coep")
|
||||
);
|
||||
}else if(self.window===self && self.document){
|
||||
return Promise.reject(
|
||||
new Error("The OPFS sqlite3_vfs cannot run in the main thread "+
|
||||
"because it requires Atomics.wait().")
|
||||
);
|
||||
}else if(!self.FileSystemHandle ||
|
||||
!self.FileSystemDirectoryHandle ||
|
||||
!self.FileSystemFileHandle ||
|
||||
!self.FileSystemFileHandle.prototype.createSyncAccessHandle ||
|
||||
!navigator.storage.getDirectory){
|
||||
return Promise.reject(
|
||||
new Error("Missing required OPFS APIs.")
|
||||
);
|
||||
}
|
||||
if(!options || 'object'!==typeof options){
|
||||
@@ -119,7 +129,7 @@ const installOpfsVfs = function callee(options){
|
||||
const log = (...args)=>logImpl(2, ...args);
|
||||
const warn = (...args)=>logImpl(1, ...args);
|
||||
const error = (...args)=>logImpl(0, ...args);
|
||||
const toss = function(...args){throw new Error(args.join(' '))};
|
||||
const toss = sqlite3.util.toss;
|
||||
const capi = sqlite3.capi;
|
||||
const wasm = sqlite3.wasm;
|
||||
const sqlite3_vfs = capi.sqlite3_vfs;
|
||||
@@ -128,8 +138,24 @@ const installOpfsVfs = function callee(options){
|
||||
/**
|
||||
Generic utilities for working with OPFS. This will get filled out
|
||||
by the Promise setup and, on success, installed as sqlite3.opfs.
|
||||
|
||||
ACHTUNG: do not rely on these APIs in client code. They are
|
||||
experimental and subject to change or removal as the
|
||||
OPFS-specific sqlite3_vfs evolves.
|
||||
*/
|
||||
const opfsUtil = Object.create(null);
|
||||
|
||||
/**
|
||||
Returns true if _this_ thread has access to the OPFS APIs.
|
||||
*/
|
||||
const thisThreadHasOPFS = ()=>{
|
||||
return self.FileSystemHandle &&
|
||||
self.FileSystemDirectoryHandle &&
|
||||
self.FileSystemFileHandle &&
|
||||
self.FileSystemFileHandle.prototype.createSyncAccessHandle &&
|
||||
navigator.storage.getDirectory;
|
||||
};
|
||||
|
||||
/**
|
||||
Not part of the public API. Solely for internal/development
|
||||
use.
|
||||
@@ -165,6 +191,8 @@ const installOpfsVfs = function callee(options){
|
||||
s.count = s.time = 0;
|
||||
}
|
||||
}/*metrics*/;
|
||||
const opfsVfs = new sqlite3_vfs();
|
||||
const opfsIoMethods = new sqlite3_io_methods();
|
||||
const promiseReject = function(err){
|
||||
opfsVfs.dispose();
|
||||
return promiseReject_(err);
|
||||
@@ -186,10 +214,7 @@ const installOpfsVfs = function callee(options){
|
||||
const dVfs = pDVfs
|
||||
? new sqlite3_vfs(pDVfs)
|
||||
: null /* dVfs will be null when sqlite3 is built with
|
||||
SQLITE_OS_OTHER. Though we cannot currently handle
|
||||
that case, the hope is to eventually be able to. */;
|
||||
const opfsVfs = new sqlite3_vfs();
|
||||
const opfsIoMethods = new sqlite3_io_methods();
|
||||
SQLITE_OS_OTHER. */;
|
||||
opfsVfs.$iVersion = 2/*yes, two*/;
|
||||
opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof;
|
||||
opfsVfs.$mxPathname = 1024/*sure, why not?*/;
|
||||
@@ -243,6 +268,16 @@ const installOpfsVfs = function callee(options){
|
||||
// Int16Array uses the platform's endianness.
|
||||
return new Int16Array(buffer)[0] === 256;
|
||||
})();
|
||||
/**
|
||||
asyncIdleWaitTime is how long (ms) to wait, in the async proxy,
|
||||
for each Atomics.wait() when waiting on inbound VFS API calls.
|
||||
We need to wake up periodically to give the thread a chance to
|
||||
do other things. If this is too high (e.g. 500ms) then even two
|
||||
workers/tabs can easily run into locking errors. Some multiple
|
||||
of this value is also used for determining how long to wait on
|
||||
lock contention to free up.
|
||||
*/
|
||||
state.asyncIdleWaitTime = 100;
|
||||
/**
|
||||
Whether the async counterpart should log exceptions to
|
||||
the serialization channel. That produces a great deal of
|
||||
@@ -265,7 +300,9 @@ const installOpfsVfs = function callee(options){
|
||||
The size of the block in our SAB for serializing arguments and
|
||||
result values. Needs to be large enough to hold serialized
|
||||
values of any of the proxied APIs. Filenames are the largest
|
||||
part but are limited to opfsVfs.$mxPathname bytes.
|
||||
part but are limited to opfsVfs.$mxPathname bytes. We also
|
||||
store exceptions there, so it needs to be long enough to hold
|
||||
a reasonably long exception string.
|
||||
*/
|
||||
state.sabS11nSize = opfsVfs.$mxPathname * 2;
|
||||
/**
|
||||
@@ -348,12 +385,32 @@ const installOpfsVfs = function callee(options){
|
||||
'SQLITE_NOTFOUND',
|
||||
'SQLITE_OPEN_CREATE',
|
||||
'SQLITE_OPEN_DELETEONCLOSE',
|
||||
'SQLITE_OPEN_MAIN_DB',
|
||||
'SQLITE_OPEN_READONLY'
|
||||
].forEach((k)=>{
|
||||
if(undefined === (state.sq3Codes[k] = capi[k])){
|
||||
toss("Maintenance required: not found:",k);
|
||||
}
|
||||
});
|
||||
state.opfsFlags = Object.assign(Object.create(null),{
|
||||
/**
|
||||
Flag for use with xOpen(). "opfs-unlock-asap=1" enables
|
||||
this. See defaultUnlockAsap, below.
|
||||
*/
|
||||
OPFS_UNLOCK_ASAP: 0x01,
|
||||
/**
|
||||
If true, any async routine which implicitly acquires a sync
|
||||
access handle (i.e. an OPFS lock) will release that locks at
|
||||
the end of the call which acquires it. If false, such
|
||||
"autolocks" are not released until the VFS is idle for some
|
||||
brief amount of time.
|
||||
|
||||
The benefit of enabling this is much higher concurrency. The
|
||||
down-side is much-reduced performance (as much as a 4x decrease
|
||||
in speedtest1).
|
||||
*/
|
||||
defaultUnlockAsap: false
|
||||
});
|
||||
|
||||
/**
|
||||
Runs the given operation (by name) in the async worker
|
||||
@@ -586,77 +643,6 @@ const installOpfsVfs = function callee(options){
|
||||
*/
|
||||
const __openFiles = Object.create(null);
|
||||
|
||||
/**
|
||||
Installs a StructBinder-bound function pointer member of the
|
||||
given name and function in the given StructType target object.
|
||||
It creates a WASM proxy for the given function and arranges for
|
||||
that proxy to be cleaned up when tgt.dispose() is called. Throws
|
||||
on the slightest hint of error (e.g. tgt is-not-a StructType,
|
||||
name does not map to a struct-bound member, etc.).
|
||||
|
||||
Returns a proxy for this function which is bound to tgt and takes
|
||||
2 args (name,func). That function returns the same thing,
|
||||
permitting calls to be chained.
|
||||
|
||||
If called with only 1 arg, it has no side effects but returns a
|
||||
func with the same signature as described above.
|
||||
*/
|
||||
const installMethod = function callee(tgt, name, func){
|
||||
if(!(tgt instanceof sqlite3.StructBinder.StructType)){
|
||||
toss("Usage error: target object is-not-a StructType.");
|
||||
}
|
||||
if(1===arguments.length){
|
||||
return (n,f)=>callee(tgt,n,f);
|
||||
}
|
||||
if(!callee.argcProxy){
|
||||
callee.argcProxy = function(func,sig){
|
||||
return function(...args){
|
||||
if(func.length!==arguments.length){
|
||||
toss("Argument mismatch. Native signature is:",sig);
|
||||
}
|
||||
return func.apply(this, args);
|
||||
}
|
||||
};
|
||||
callee.removeFuncList = function(){
|
||||
if(this.ondispose.__removeFuncList){
|
||||
this.ondispose.__removeFuncList.forEach(
|
||||
(v,ndx)=>{
|
||||
if('number'===typeof v){
|
||||
try{wasm.uninstallFunction(v)}
|
||||
catch(e){/*ignore*/}
|
||||
}
|
||||
/* else it's a descriptive label for the next number in
|
||||
the list. */
|
||||
}
|
||||
);
|
||||
delete this.ondispose.__removeFuncList;
|
||||
}
|
||||
};
|
||||
}/*static init*/
|
||||
const sigN = tgt.memberSignature(name);
|
||||
if(sigN.length<2){
|
||||
toss("Member",name," is not a function pointer. Signature =",sigN);
|
||||
}
|
||||
const memKey = tgt.memberKey(name);
|
||||
const fProxy = 0
|
||||
/** This middle-man proxy is only for use during development, to
|
||||
confirm that we always pass the proper number of
|
||||
arguments. We know that the C-level code will always use the
|
||||
correct argument count. */
|
||||
? callee.argcProxy(func, sigN)
|
||||
: func;
|
||||
const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
|
||||
tgt[memKey] = pFunc;
|
||||
if(!tgt.ondispose) tgt.ondispose = [];
|
||||
if(!tgt.ondispose.__removeFuncList){
|
||||
tgt.ondispose.push('ondispose.__removeFuncList handler',
|
||||
callee.removeFuncList);
|
||||
tgt.ondispose.__removeFuncList = [];
|
||||
}
|
||||
tgt.ondispose.__removeFuncList.push(memKey, pFunc);
|
||||
return (n,f)=>callee(tgt, n, f);
|
||||
}/*installMethod*/;
|
||||
|
||||
const opTimer = Object.create(null);
|
||||
opTimer.op = undefined;
|
||||
opTimer.start = undefined;
|
||||
@@ -682,10 +668,10 @@ const installOpfsVfs = function callee(options){
|
||||
success) release a sync-handle for it, but doing so would
|
||||
involve an inherent race condition. For the time being,
|
||||
pending a better solution, we simply report whether the
|
||||
given pFile instance has a lock.
|
||||
given pFile is open.
|
||||
*/
|
||||
const f = __openFiles[pFile];
|
||||
wasm.setMemValue(pOut, f.lockMode ? 1 : 0, 'i32');
|
||||
wasm.setMemValue(pOut, f.lockType ? 1 : 0, 'i32');
|
||||
return 0;
|
||||
},
|
||||
xClose: function(pFile){
|
||||
@@ -726,7 +712,11 @@ const installOpfsVfs = function callee(options){
|
||||
mTimeStart('xLock');
|
||||
const f = __openFiles[pFile];
|
||||
let rc = 0;
|
||||
if( capi.SQLITE_LOCK_NONE === f.lockType ) {
|
||||
/* All OPFS locks are exclusive locks. If xLock() has
|
||||
previously succeeded, do nothing except record the lock
|
||||
type. If no lock is active, have the async counterpart
|
||||
lock the file. */
|
||||
if( !f.lockType ) {
|
||||
rc = opRun('xLock', pFile, lockType);
|
||||
if( 0===rc ) f.lockType = lockType;
|
||||
}else{
|
||||
@@ -844,9 +834,15 @@ const installOpfsVfs = function callee(options){
|
||||
//xSleep is optionally defined below
|
||||
xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){
|
||||
mTimeStart('xOpen');
|
||||
let opfsFlags = 0;
|
||||
if(0===zName){
|
||||
zName = randomFilename();
|
||||
}else if('number'===typeof zName){
|
||||
if(capi.sqlite3_uri_boolean(zName, "opfs-unlock-asap", 0)){
|
||||
/* -----------------------^^^^^ MUST pass the untranslated
|
||||
C-string here. */
|
||||
opfsFlags |= state.opfsFlags.OPFS_UNLOCK_ASAP;
|
||||
}
|
||||
zName = wasm.cstringToJs(zName);
|
||||
}
|
||||
const fh = Object.create(null);
|
||||
@@ -854,7 +850,7 @@ const installOpfsVfs = function callee(options){
|
||||
fh.filename = zName;
|
||||
fh.sab = new SharedArrayBuffer(state.fileBufferSize);
|
||||
fh.flags = flags;
|
||||
const rc = opRun('xOpen', pFile, zName, flags);
|
||||
const rc = opRun('xOpen', pFile, zName, flags, opfsFlags);
|
||||
if(!rc){
|
||||
/* Recall that sqlite3_vfs::xClose() will be called, even on
|
||||
error, unless pFile->pMethods is NULL. */
|
||||
@@ -895,14 +891,6 @@ const installOpfsVfs = function callee(options){
|
||||
};
|
||||
}
|
||||
|
||||
/* Install the vfs/io_methods into their C-level shared instances... */
|
||||
for(let k of Object.keys(ioSyncWrappers)){
|
||||
installMethod(opfsIoMethods, k, ioSyncWrappers[k]);
|
||||
}
|
||||
for(let k of Object.keys(vfsSyncWrappers)){
|
||||
installMethod(opfsVfs, k, vfsSyncWrappers[k]);
|
||||
}
|
||||
|
||||
/**
|
||||
Expects an OPFS file path. It gets resolved, such that ".."
|
||||
components are properly expanded, and returned. If the 2nd arg
|
||||
@@ -1042,8 +1030,9 @@ const installOpfsVfs = function callee(options){
|
||||
Irrevocably deletes _all_ files in the current origin's OPFS.
|
||||
Obviously, this must be used with great caution. It may throw
|
||||
an exception if removal of anything fails (e.g. a file is
|
||||
locked), but the precise conditions under which it will throw
|
||||
are not documented (so we cannot tell you what they are).
|
||||
locked), but the precise conditions under which the underlying
|
||||
APIs will throw are not documented (so we cannot tell you what
|
||||
they are).
|
||||
*/
|
||||
opfsUtil.rmfr = async function(){
|
||||
const dir = opfsUtil.rootDirectory, opt = {recurse: true};
|
||||
@@ -1145,23 +1134,32 @@ const installOpfsVfs = function callee(options){
|
||||
|
||||
//TODO to support fiddle and worker1 db upload:
|
||||
//opfsUtil.createFile = function(absName, content=undefined){...}
|
||||
//We have sqlite3.wasm.sqlite3_wasm_vfs_create_file() for this
|
||||
//purpose but its interface and name are still under
|
||||
//consideration.
|
||||
|
||||
if(sqlite3.oo1){
|
||||
opfsUtil.OpfsDb = function(...args){
|
||||
const OpfsDb = function(...args){
|
||||
const opt = sqlite3.oo1.DB.dbCtorHelper.normalizeArgs(...args);
|
||||
opt.vfs = opfsVfs.$zName;
|
||||
sqlite3.oo1.DB.dbCtorHelper.call(this, opt);
|
||||
};
|
||||
opfsUtil.OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype);
|
||||
sqlite3.oo1.OpfsDb =
|
||||
opfsUtil.OpfsDb /* sqlite3.opfs.OpfsDb => deprecated name -
|
||||
will be phased out Real Soon */ =
|
||||
OpfsDb;
|
||||
OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype);
|
||||
sqlite3.oo1.DB.dbCtorHelper.setVfsPostOpenSql(
|
||||
opfsVfs.pointer,
|
||||
[
|
||||
/* Truncate journal mode is faster than delete or wal for
|
||||
this vfs, per speedtest1. */
|
||||
"pragma journal_mode=truncate;",
|
||||
/* Truncate journal mode is faster than delete for
|
||||
this vfs, per speedtest1. That gap seems to have closed with
|
||||
Chome version 108 or 109, but "persist" is very roughly 5-6%
|
||||
faster than truncate in initial tests. */
|
||||
"pragma journal_mode=persist;",
|
||||
/* Set a default busy-timeout handler to help OPFS dbs
|
||||
deal with multi-tab/multi-worker contention. */
|
||||
"pragma busy_timeout=2000;",
|
||||
"pragma busy_timeout=3000;",
|
||||
/*
|
||||
This vfs benefits hugely from cache on moderate/large
|
||||
speedtest1 --size 50 and --size 100 workloads. We currently
|
||||
@@ -1174,13 +1172,6 @@ const installOpfsVfs = function callee(options){
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
Potential TODOs:
|
||||
|
||||
- Expose one or both of the Worker objects via opfsUtil and
|
||||
publish an interface for proxying the higher-level OPFS
|
||||
features like getting a directory listing.
|
||||
*/
|
||||
const sanityCheck = function(){
|
||||
const scope = wasm.scopedAllocPush();
|
||||
const sq3File = new sqlite3_file();
|
||||
@@ -1250,6 +1241,11 @@ const installOpfsVfs = function callee(options){
|
||||
W.onmessage = function({data}){
|
||||
//log("Worker.onmessage:",data);
|
||||
switch(data.type){
|
||||
case 'opfs-unavailable':
|
||||
/* Async proxy has determined that OPFS is unavailable. There's
|
||||
nothing more for us to do here. */
|
||||
promiseReject(new Error(data.payload.join(' ')));
|
||||
break;
|
||||
case 'opfs-async-loaded':
|
||||
/*Arrives as soon as the asyc proxy finishes loading.
|
||||
Pass our config and shared state on to the async worker.*/
|
||||
@@ -1260,14 +1256,10 @@ const installOpfsVfs = function callee(options){
|
||||
and has finished initializing, so the real work can
|
||||
begin...*/
|
||||
try {
|
||||
const rc = capi.sqlite3_vfs_register(opfsVfs.pointer, 0);
|
||||
if(rc){
|
||||
toss("sqlite3_vfs_register(OPFS) failed with rc",rc);
|
||||
}
|
||||
if(opfsVfs.pointer !== capi.sqlite3_vfs_find("opfs")){
|
||||
toss("BUG: sqlite3_vfs_find() failed for just-installed OPFS VFS");
|
||||
}
|
||||
capi.sqlite3_vfs_register.addReference(opfsVfs, opfsIoMethods);
|
||||
sqlite3.VfsHelper.installVfs({
|
||||
io: {struct: opfsIoMethods, methods: ioSyncWrappers},
|
||||
vfs: {struct: opfsVfs, methods: vfsSyncWrappers}
|
||||
});
|
||||
state.sabOPView = new Int32Array(state.sabOP);
|
||||
state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize);
|
||||
state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
|
||||
@@ -1276,14 +1268,18 @@ const installOpfsVfs = function callee(options){
|
||||
warn("Running sanity checks because of opfs-sanity-check URL arg...");
|
||||
sanityCheck();
|
||||
}
|
||||
navigator.storage.getDirectory().then((d)=>{
|
||||
W.onerror = W._originalOnError;
|
||||
delete W._originalOnError;
|
||||
sqlite3.opfs = opfsUtil;
|
||||
opfsUtil.rootDirectory = d;
|
||||
log("End of OPFS sqlite3_vfs setup.", opfsVfs);
|
||||
if(thisThreadHasOPFS()){
|
||||
navigator.storage.getDirectory().then((d)=>{
|
||||
W.onerror = W._originalOnError;
|
||||
delete W._originalOnError;
|
||||
sqlite3.opfs = opfsUtil;
|
||||
opfsUtil.rootDirectory = d;
|
||||
log("End of OPFS sqlite3_vfs setup.", opfsVfs);
|
||||
promiseResolve(sqlite3);
|
||||
}).catch(promiseReject);
|
||||
}else{
|
||||
promiseResolve(sqlite3);
|
||||
});
|
||||
}
|
||||
}catch(e){
|
||||
error(e);
|
||||
promiseReject(e);
|
||||
@@ -1302,9 +1298,6 @@ const installOpfsVfs = function callee(options){
|
||||
installOpfsVfs.defaultProxyUri =
|
||||
"sqlite3-opfs-async-proxy.js";
|
||||
self.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{
|
||||
if(sqlite3.scriptInfo && !sqlite3.scriptInfo.isWorker){
|
||||
return;
|
||||
}
|
||||
try{
|
||||
let proxyJs = installOpfsVfs.defaultProxyUri;
|
||||
if(sqlite3.scriptInfo.sqlite3Dir){
|
||||
@@ -916,25 +916,27 @@ int sqlite3_wasm_db_export_chunked( sqlite3* pDb,
|
||||
}
|
||||
|
||||
/*
|
||||
** A proxy for sqlite3_serialize() which serializes the "main" schema
|
||||
** A proxy for sqlite3_serialize() which serializes the schema zSchema
|
||||
** of pDb, placing the serialized output in pOut and nOut. nOut may be
|
||||
** NULL. If pDb or pOut are NULL then SQLITE_MISUSE is returned. If
|
||||
** allocation of the serialized copy fails, SQLITE_NOMEM is returned.
|
||||
** On success, 0 is returned and `*pOut` will contain a pointer to the
|
||||
** memory unless mFlags includes SQLITE_SERIALIZE_NOCOPY and the
|
||||
** database has no contiguous memory representation, in which case
|
||||
** `*pOut` will be NULL but 0 will be returned.
|
||||
** NULL. If zSchema is NULL then "main" is assumed. If pDb or pOut are
|
||||
** NULL then SQLITE_MISUSE is returned. If allocation of the
|
||||
** serialized copy fails, SQLITE_NOMEM is returned. On success, 0 is
|
||||
** returned and `*pOut` will contain a pointer to the memory unless
|
||||
** mFlags includes SQLITE_SERIALIZE_NOCOPY and the database has no
|
||||
** contiguous memory representation, in which case `*pOut` will be
|
||||
** NULL but 0 will be returned.
|
||||
**
|
||||
** If `*pOut` is not NULL, the caller is responsible for passing it to
|
||||
** sqlite3_free() to free it.
|
||||
*/
|
||||
SQLITE_WASM_KEEP
|
||||
int sqlite3_wasm_db_serialize( sqlite3 *pDb, unsigned char **pOut,
|
||||
int sqlite3_wasm_db_serialize( sqlite3 *pDb, const char *zSchema,
|
||||
unsigned char **pOut,
|
||||
sqlite3_int64 *nOut, unsigned int mFlags ){
|
||||
unsigned char * z;
|
||||
if( !pDb || !pOut ) return SQLITE_MISUSE;
|
||||
if(nOut) *nOut = 0;
|
||||
z = sqlite3_serialize(pDb, "main", nOut, mFlags);
|
||||
z = sqlite3_serialize(pDb, zSchema ? zSchema : "main", nOut, mFlags);
|
||||
if( z || (SQLITE_SERIALIZE_NOCOPY & mFlags) ){
|
||||
*pOut = z;
|
||||
return 0;
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<span>→ <a id='link-main-thread' href='#' target='speedtest-main'
|
||||
title='Start speedtest1.html with the selected flags'>speedtest1</a>
|
||||
</span>
|
||||
<span>→ <a id='link-wasmfs' href='#' target='speedtest-wasmfs'
|
||||
<span class='hidden'>→ <a id='link-wasmfs' href='#' target='speedtest-wasmfs'
|
||||
title='Start speedtest1-wasmfs.html with the selected flags'>speedtest1-wasmfs</a>
|
||||
</span>
|
||||
<span>→ <a id='link-kvvfs' href='#' target='speedtest-kvvfs'
|
||||
|
||||
+30
-17
@@ -1163,6 +1163,16 @@ self.sqlite3InitModule = sqlite3InitModule;
|
||||
.assert(0 === capi.sqlite3_errmsg(db.pointer).indexOf("Invalid SQL"))
|
||||
.assert(dbFile === db.dbFilename())
|
||||
.assert(!db.dbFilename('nope'));
|
||||
//Sanity check DB.checkRc()...
|
||||
let ex;
|
||||
try{db.checkRc(rc)}
|
||||
catch(e){ex = e}
|
||||
T.assert(ex instanceof sqlite3.SQLite3Error)
|
||||
.assert(0===ex.message.indexOf("sqlite3 result code"))
|
||||
.assert(ex.message.indexOf("Invalid SQL")>0);
|
||||
T.assert(db === db.checkRc(0))
|
||||
.assert(db === sqlite3.oo1.DB.checkRc(db,0))
|
||||
.assert(null === sqlite3.oo1.DB.checkRc(null,0))
|
||||
})
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
@@ -1774,13 +1784,12 @@ self.sqlite3InitModule = sqlite3InitModule;
|
||||
.t({
|
||||
name: 'OPFS sanity checks',
|
||||
test: async function(sqlite3){
|
||||
const opfs = sqlite3.opfs;
|
||||
const filename = 'sqlite3-tester1.db';
|
||||
const pVfs = capi.sqlite3_vfs_find('opfs');
|
||||
T.assert(pVfs);
|
||||
const unlink = (fn=filename)=>wasm.sqlite3_wasm_vfs_unlink(pVfs,fn);
|
||||
unlink();
|
||||
let db = new opfs.OpfsDb(filename);
|
||||
let db = new sqlite3.oo1.OpfsDb(filename);
|
||||
try {
|
||||
db.exec([
|
||||
'create table p(a);',
|
||||
@@ -1788,7 +1797,7 @@ self.sqlite3InitModule = sqlite3InitModule;
|
||||
]);
|
||||
T.assert(3 === db.selectValue('select count(*) from p'));
|
||||
db.close();
|
||||
db = new opfs.OpfsDb(filename);
|
||||
db = new sqlite3.oo1.OpfsDb(filename);
|
||||
db.exec('insert into p(a) values(4),(5),(6)');
|
||||
T.assert(6 === db.selectValue('select count(*) from p'));
|
||||
}finally{
|
||||
@@ -1796,8 +1805,9 @@ self.sqlite3InitModule = sqlite3InitModule;
|
||||
unlink();
|
||||
}
|
||||
|
||||
if(1){
|
||||
if(sqlite3.opfs){
|
||||
// Sanity-test sqlite3_wasm_vfs_create_file()...
|
||||
const opfs = sqlite3.opfs;
|
||||
const fSize = 1379;
|
||||
let sh;
|
||||
try{
|
||||
@@ -1814,20 +1824,20 @@ self.sqlite3InitModule = sqlite3InitModule;
|
||||
if(sh) await sh.close();
|
||||
unlink();
|
||||
}
|
||||
}
|
||||
|
||||
// Some sanity checks of the opfs utility functions...
|
||||
const testDir = '/sqlite3-opfs-'+opfs.randomFilename(12);
|
||||
const aDir = testDir+'/test/dir';
|
||||
T.assert(await opfs.mkdir(aDir), "mkdir failed")
|
||||
.assert(await opfs.mkdir(aDir), "mkdir must pass if the dir exists")
|
||||
.assert(!(await opfs.unlink(testDir+'/test')), "delete 1 should have failed (dir not empty)")
|
||||
.assert((await opfs.unlink(testDir+'/test/dir')), "delete 2 failed")
|
||||
.assert(!(await opfs.unlink(testDir+'/test/dir')),
|
||||
"delete 2b should have failed (dir already deleted)")
|
||||
.assert((await opfs.unlink(testDir, true)), "delete 3 failed")
|
||||
.assert(!(await opfs.entryExists(testDir)),
|
||||
"entryExists(",testDir,") should have failed");
|
||||
// Some sanity checks of the opfs utility functions...
|
||||
const testDir = '/sqlite3-opfs-'+opfs.randomFilename(12);
|
||||
const aDir = testDir+'/test/dir';
|
||||
T.assert(await opfs.mkdir(aDir), "mkdir failed")
|
||||
.assert(await opfs.mkdir(aDir), "mkdir must pass if the dir exists")
|
||||
.assert(!(await opfs.unlink(testDir+'/test')), "delete 1 should have failed (dir not empty)")
|
||||
.assert((await opfs.unlink(testDir+'/test/dir')), "delete 2 failed")
|
||||
.assert(!(await opfs.unlink(testDir+'/test/dir')),
|
||||
"delete 2b should have failed (dir already deleted)")
|
||||
.assert((await opfs.unlink(testDir, true)), "delete 3 failed")
|
||||
.assert(!(await opfs.entryExists(testDir)),
|
||||
"entryExists(",testDir,") should have failed");
|
||||
}
|
||||
}
|
||||
}/*OPFS sanity checks*/)
|
||||
;/* end OPFS tests */
|
||||
@@ -1857,6 +1867,9 @@ self.sqlite3InitModule = sqlite3InitModule;
|
||||
}
|
||||
importScripts(sqlite3Js);
|
||||
}
|
||||
self.sqlite3InitModule.__isUnderTest =
|
||||
true /* disables certain API-internal cleanup so that we can
|
||||
test internal APIs from here */;
|
||||
self.sqlite3InitModule({
|
||||
print: log,
|
||||
printErr: error
|
||||
|
||||
@@ -18,21 +18,23 @@
|
||||
<h1></h1>
|
||||
<p>
|
||||
OPFS concurrency tester using multiple independent Workers.
|
||||
Disclaimer: concurrency in OPFS is currently a pain point
|
||||
and timing/concurrency mitigation in this environment is
|
||||
highly unpredictable!
|
||||
Disclaimer: concurrency in OPFS is currently a pain point!
|
||||
</p>
|
||||
<p>
|
||||
URL flags: pass a number of workers using
|
||||
the <code>workers=N</code> URL flag and the worker work interval
|
||||
as <code>interval=N</code> (milliseconds). Enable OPFS VFS
|
||||
verbosity with <code>verbose=1-3</code> (output goes to the
|
||||
dev console).
|
||||
the <code>workers=N</code> URL flag. Set the time between each
|
||||
workload with <code>interval=N</code> (milliseconds). Set the
|
||||
number of worker iterations with <code>iterations=N</code>.
|
||||
Enable OPFS VFS verbosity with <code>verbose=1-3</code> (output
|
||||
goes to the dev console). Enable/disable "unlock ASAP" mode
|
||||
(higher concurrency, lower speed)
|
||||
with <code>unlock-asap=0-1</code>.
|
||||
</p>
|
||||
<p>Achtung: if it does not start to do anything within a couple of
|
||||
seconds, check the dev console: Chrome often fails with "cannot allocate
|
||||
WasmMemory" at startup. Closing and re-opening the tab usually resolves
|
||||
it.
|
||||
seconds, check the dev console: Chrome sometimes fails to load
|
||||
the wasm module due to "cannot allocate WasmMemory." Closing and
|
||||
re-opening the tab usually resolves it, but sometimes restarting
|
||||
the browser is required.
|
||||
</p>
|
||||
<div class='input-wrapper'>
|
||||
<input type='checkbox' id='cb-log-reverse'>
|
||||
|
||||
@@ -56,26 +56,42 @@
|
||||
options.sqlite3Dir = urlArgsJs.get('sqlite3.dir');
|
||||
options.workerCount = (
|
||||
urlArgsHtml.has('workers') ? +urlArgsHtml.get('workers') : 3
|
||||
) || 3;
|
||||
) || 4;
|
||||
options.opfsVerbose = (
|
||||
urlArgsHtml.has('verbose') ? +urlArgsHtml.get('verbose') : 1
|
||||
) || 1;
|
||||
options.interval = (
|
||||
urlArgsHtml.has('interval') ? +urlArgsHtml.get('interval') : 750
|
||||
) || 750;
|
||||
) || 1000;
|
||||
options.iterations = (
|
||||
urlArgsHtml.has('iterations') ? +urlArgsHtml.get('iterations') : 10
|
||||
) || 10;
|
||||
options.unlockAsap = (
|
||||
urlArgsHtml.has('unlock-asap') ? +urlArgsHtml.get('unlock-asap') : 0
|
||||
) || 0;
|
||||
const workers = [];
|
||||
workers.post = (type,...args)=>{
|
||||
for(const w of workers) w.postMessage({type, payload:args});
|
||||
};
|
||||
workers.loadedCount = 0;
|
||||
workers.counts = {loaded: 0, passed: 0, failed: 0};
|
||||
const checkFinished = function(){
|
||||
if(workers.counts.passed + workers.counts.failed !== workers.length){
|
||||
return;
|
||||
}
|
||||
if(workers.counts.failed>0){
|
||||
logCss('tests-fail',"Finished with",workers.counts.failed,"failure(s).");
|
||||
}else{
|
||||
logCss('tests-pass',"All",workers.length,"workers finished.");
|
||||
}
|
||||
};
|
||||
workers.onmessage = function(msg){
|
||||
msg = msg.data;
|
||||
const prefix = 'Worker #'+msg.worker+':';
|
||||
switch(msg.type){
|
||||
case 'loaded':
|
||||
stdout(prefix,"loaded");
|
||||
if(++workers.loadedCount === workers.length){
|
||||
stdout("All workers loaded. Telling them to run...");
|
||||
if(++workers.counts.loaded === workers.length){
|
||||
stdout("All",workers.length,"workers loaded. Telling them to run...");
|
||||
workers.post('run');
|
||||
}
|
||||
break;
|
||||
@@ -83,21 +99,27 @@
|
||||
case 'stderr': stderr(prefix,...msg.payload); break;
|
||||
case 'error': stderr(prefix,"ERROR:",...msg.payload); break;
|
||||
case 'finished':
|
||||
++workers.counts.passed;
|
||||
logCss('tests-pass',prefix,...msg.payload);
|
||||
checkFinished();
|
||||
break;
|
||||
case 'failed':
|
||||
++workers.counts.failed;
|
||||
logCss('tests-fail',prefix,"FAILED:",...msg.payload);
|
||||
checkFinished();
|
||||
break;
|
||||
default: logCss('error',"Unhandled message type:",msg); break;
|
||||
}
|
||||
};
|
||||
|
||||
stdout("Launching",options.workerCount,"workers...");
|
||||
stdout("Launching",options.workerCount,"workers. Options:",options);
|
||||
workers.uri = (
|
||||
'worker.js?'
|
||||
+ 'sqlite3.dir='+options.sqlite3Dir
|
||||
+ '&interval='+options.interval
|
||||
+ '&iterations='+options.iterations
|
||||
+ '&opfs-verbose='+options.opfsVerbose
|
||||
+ '&opfs-unlock-asap='+options.unlockAsap
|
||||
);
|
||||
for(let i = 0; i < options.workerCount; ++i){
|
||||
stdout("Launching worker...");
|
||||
|
||||
@@ -3,9 +3,12 @@ importScripts(
|
||||
);
|
||||
self.sqlite3InitModule().then(async function(sqlite3){
|
||||
const urlArgs = new URL(self.location.href).searchParams;
|
||||
const wName = urlArgs.get('workerId') || Math.round(Math.random()*10000);
|
||||
const options = {
|
||||
workerName: urlArgs.get('workerId') || Math.round(Math.random()*10000),
|
||||
unlockAsap: urlArgs.get('opfs-unlock-asap') || 0 /*EXPERIMENTAL*/
|
||||
};
|
||||
const wPost = (type,...payload)=>{
|
||||
postMessage({type, worker: wName, payload});
|
||||
postMessage({type, worker: options.workerName, payload});
|
||||
};
|
||||
const stdout = (...args)=>wPost('stdout',...args);
|
||||
const stderr = (...args)=>wPost('stderr',...args);
|
||||
@@ -43,7 +46,10 @@ self.sqlite3InitModule().then(async function(sqlite3){
|
||||
}
|
||||
};
|
||||
const run = async function(){
|
||||
db = new sqlite3.opfs.OpfsDb(dbName,'c');
|
||||
db = new sqlite3.oo1.OpfsDb({
|
||||
filename: 'file:'+dbName+'?opfs-unlock-asap='+options.unlockAsap,
|
||||
flags: 'c'
|
||||
});
|
||||
sqlite3.capi.sqlite3_busy_timeout(db.pointer, 5000);
|
||||
db.transaction((db)=>{
|
||||
db.exec([
|
||||
@@ -52,7 +58,8 @@ self.sqlite3InitModule().then(async function(sqlite3){
|
||||
]);
|
||||
});
|
||||
|
||||
const maxIterations = 10;
|
||||
const maxIterations =
|
||||
urlArgs.has('iterations') ? (+urlArgs.get('iterations') || 10) : 10;
|
||||
stdout("Starting interval-based db updates with delay of",interval.delay,"ms.");
|
||||
const doWork = async ()=>{
|
||||
const tm = new Date().getTime();
|
||||
@@ -62,7 +69,7 @@ self.sqlite3InitModule().then(async function(sqlite3){
|
||||
try{
|
||||
db.exec({
|
||||
sql:"INSERT OR REPLACE INTO t1(w,v) VALUES(?,?)",
|
||||
bind: [wName, new Date().getTime()]
|
||||
bind: [options.workerName, new Date().getTime()]
|
||||
});
|
||||
//stdout("Set",prefix);
|
||||
}catch(e){
|
||||
@@ -70,11 +77,12 @@ self.sqlite3InitModule().then(async function(sqlite3){
|
||||
}
|
||||
};
|
||||
if(1){/*use setInterval()*/
|
||||
interval.handle = setInterval(async ()=>{
|
||||
setTimeout(async function timer(){
|
||||
await doWork();
|
||||
if(interval.error || maxIterations === interval.count){
|
||||
clearInterval(interval.handle);
|
||||
finish();
|
||||
}else{
|
||||
setTimeout(timer, interval.delay);
|
||||
}
|
||||
}, interval.delay);
|
||||
}else{
|
||||
|
||||
@@ -363,6 +363,7 @@ TESTSRC = \
|
||||
TESTSRC += \
|
||||
$(TOP)/ext/misc/amatch.c \
|
||||
$(TOP)/ext/misc/appendvfs.c \
|
||||
$(TOP)/ext/misc/basexx.c \
|
||||
$(TOP)/ext/misc/carray.c \
|
||||
$(TOP)/ext/misc/cksumvfs.c \
|
||||
$(TOP)/ext/misc/closure.c \
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
C Merge\strunk\sinto\swasi-patches\sbranch\sto\sclean\sup\sthe\sdiff\sview.
|
||||
D 2022-11-21T16:03:19.238
|
||||
C Merge\strunk\sinto\swasi-patches\sbranch.
|
||||
D 2022-11-30T10:05:02.920
|
||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
|
||||
F Makefile.in 78e4c4916f2c3993a8a454018745ff02094a8029d449d0c22db98f1cf8260420
|
||||
F Makefile.in c223963d7b0828f26cb62ea3e0f583d26839b7d3ef0d1cca87f35c4b222ff01b
|
||||
F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241
|
||||
F Makefile.msc e7a564ceec71f0d9666031d5638cf0d4f88b050b44e8df5d32125137cd259ac0
|
||||
F Makefile.msc e86ec21328921721dd6f777da435c62a5469cda1a654f5ecefacf87ddb3dfeb3
|
||||
F README.md 8b8df9ca852aeac4864eb1e400002633ee6db84065bd01b78c33817f97d31f5e
|
||||
F VERSION 413ec94920a487ae32c9a2a8819544d690662d6f7c7ce025c0d0b8a1e74fa9db
|
||||
F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
|
||||
@@ -289,6 +289,9 @@ F ext/misc/README.md d6dd0fe1d8af77040216798a6a2b0c46c73054d2f0ea544fbbcdccf6f23
|
||||
F ext/misc/amatch.c e3ad5532799cee9a97647f483f67f43b38796b84b5a8c60594fe782a4338f358
|
||||
F ext/misc/anycollseq.c 5ffdfde9829eeac52219136ad6aa7cd9a4edb3b15f4f2532de52f4a22525eddb
|
||||
F ext/misc/appendvfs.c 9642c7a194a2a25dca7ad3e36af24a0a46d7702168c4ad7e59c9f9b0e16a3824
|
||||
F ext/misc/base64.c fb140039d4e15710298e28cafe12d2bdff1b927fc57540cbcd07996f82bf9800 x
|
||||
F ext/misc/base85.c ace591246855806a70e63eb6705ce8736f4b782da2137bdbb5d39cc337346e0d
|
||||
F ext/misc/basexx.c b37ea21f00deea1029d2ddc670f5701d9370ae06f7d173c5bed79d0f26a67ec1
|
||||
F ext/misc/blobio.c a867c4c4617f6ec223a307ebfe0eabb45e0992f74dd47722b96f3e631c0edb2a
|
||||
F ext/misc/btreeinfo.c d28ce349b40054eaa9473e835837bad7a71deec33ba13e39f963d50933bfa0f9
|
||||
F ext/misc/carray.c b752f46411e4e47e34dce6f0c88bc8e51bb821ba9e49bfcd882506451c928f69
|
||||
@@ -322,7 +325,7 @@ F ext/misc/rot13.c 51ac5f51e9d5fd811db58a9c23c628ad5f333c173f1fc53c8491a3603d385
|
||||
F ext/misc/scrub.c 2a44b0d44c69584c0580ad2553f6290a307a49df4668941d2812135bfb96a946
|
||||
F ext/misc/series.c 8d79354f2c3d46b95ee21272a07cf0bcabb58d1f2b06d9e7b8a31dca1dacb3e5
|
||||
F ext/misc/sha1.c 4011aef176616872b2a0d5bccf0ecfb1f7ce3fe5c3d107f3a8e949d8e1e3f08d
|
||||
F ext/misc/shathree.c 7b17615869a495659f1569ada1d8d3d21b4a24614f2746d93cc87ef7c0b6b36d
|
||||
F ext/misc/shathree.c 9b7af7b7d55b27c5bbd16548b67fe6aa47a819e71bc6a80a456144f86f4e834d
|
||||
F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52
|
||||
F ext/misc/spellfix.c 94df9bbfa514a563c1484f684a2df3d128a2f7209a84ca3ca100c68a0163e29f
|
||||
F ext/misc/sqlar.c 0ace5d3c10fe736dc584bf1159a36b8e2e60fab309d310cd8a0eecd9036621b6
|
||||
@@ -387,7 +390,7 @@ F ext/rbu/sqlite3rbu.c c4ba7901b2d3e0c7845f30840e3ffb35c6f999d6da0d80f121866491f
|
||||
F ext/rbu/sqlite3rbu.h 02d981e2d39c151391759e1a400e29c7388730812957ac3db8dad7f6c9f9cfc8
|
||||
F ext/rbu/test_rbu.c ee6ede75147bc081fe9bc3931e6b206277418d14d3fbceea6fdc6216d9b47055
|
||||
F ext/recover/dbdata.c 8f1f75d636431de69d7977ec50fc41bfdd0c48c510d5ee7eae0cbd4164e1429a
|
||||
F ext/recover/recover1.test 02004eb8f9ec2825ba77e24742c18e45162cb21d27e76a3a435b83a759a1131a
|
||||
F ext/recover/recover1.test 2a2df2943d6696f9487e75868feae4b1511c4a511b102854ba0d2af0326d9dfb
|
||||
F ext/recover/recover_common.tcl a61306c1eb45c0c3fc45652c35b2d4ec19729e340bdf65a272ce4c229cefd85a
|
||||
F ext/recover/recoverclobber.test 3ba6c0c373c5c63d17e82eced64c05c57ccaf26c1abe1ca7141334022a79f32e
|
||||
F ext/recover/recovercorrupt.test 64c081ad1200ae77b447da99eb724785d6bf71715f394543dc7689642e92bf49
|
||||
@@ -399,7 +402,7 @@ F ext/recover/recoverpgsz.test 3658ab8e68475b1bb87d6af88baa04551c84b73280a566a1b
|
||||
F ext/recover/recoverrowid.test f948bf4024a5f41b0e21b8af80c60564c5b5d78c05a8d64fc00787715ff9f45f
|
||||
F ext/recover/recoverslowidx.test 5205a9742dd9490ee99950dabb622307355ef1662dea6a3a21030057bfd81411
|
||||
F ext/recover/recoversql.test e66d01f95302a223bcd3fd42b5ee58dc2b53d70afa90b0d00e41e4b8eab20486
|
||||
F ext/recover/sqlite3recover.c 1e4de9cd30daa67e5c1cd0cafb764ddf183087ce037ab47b7c1a7e2921c90d37
|
||||
F ext/recover/sqlite3recover.c 6d37a422c8917b793548a7b2c6e7ff8e1af15ba5453eef37bb69331828b73186
|
||||
F ext/recover/sqlite3recover.h 011c799f02deb70ab685916f6f538e6bb32c4e0025e79bfd0e24ff9c74820959
|
||||
F ext/recover/test_recover.c 1a34e2d04533d919a30ae4d5caeb1643f6684e9ccd7597ca27721d8af81f4ade
|
||||
F ext/repair/README.md 92f5e8aae749a4dae14f02eea8e1bb42d4db2b6ce5e83dbcdd6b1446997e0c15
|
||||
@@ -488,27 +491,28 @@ F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3
|
||||
F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04
|
||||
F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb
|
||||
F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c
|
||||
F ext/wasm/GNUmakefile bc1696a1189f4c571b3d878b8f3a67c1f4b52c222f52d356027a5a0c707337c7
|
||||
F ext/wasm/GNUmakefile c6b88ce5f735e29bef2b5da2e095848a7919100521a45e645cbf2456d759d5dd
|
||||
F ext/wasm/README-dist.txt 2d670b426fc7c613b90a7d2f2b05b433088fe65181abead970980f0a4a75ea20
|
||||
F ext/wasm/README.md ef39861aa21632fdbca0bdd469f78f0096f6449a720f3f39642594af503030e9
|
||||
F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api b4d68c97d14944b48d55e06aa44f544a6f56a7fa2bcb6f9e030936a5b2a9479a
|
||||
F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287
|
||||
F ext/wasm/api/README.md 29276a845e57004e82efba61fa5866fd05f9137380a1dc26dc4c6d65264cd81c
|
||||
F ext/wasm/api/extern-post-js.js 31400dd1c0ae3458a0e6510229e59318e45eac402a75dd703c2950b9b5758b46
|
||||
F ext/wasm/api/README.md 44a05899d607a792370260a7c9193c9c111a7df06bc3ad1823c92a159526dbf3
|
||||
F ext/wasm/api/extern-post-js.js 8923f76c3d2213159e12d641dc750523ead5c848185dc4996fae5cc12397f88d
|
||||
F ext/wasm/api/extern-pre-js.js cc61c09c7a24a07dbecb4c352453c3985170cec12b4e7e7e7a4d11d43c5c8f41
|
||||
F ext/wasm/api/post-js-footer.js cd0a8ec768501d9bd45d325ab0442037fb0e33d1f3b4f08902f15c34720ee4a1
|
||||
F ext/wasm/api/post-js-header.js d6ab3dfef4a06960d28a7eaa338d4e2a1a5981e9b38718168bbde8fdb2a439b8
|
||||
F ext/wasm/api/pre-js.js b88499dc303c21fc3f55f2c364a0f814f587b60a95784303881169f9e91c1d5f
|
||||
F ext/wasm/api/sqlite3-api-cleanup.js ecdc69dbfccfe26146f04799fcfd4a6f5790d46e7e3b9b6e9b0491f92ed8ae34
|
||||
F ext/wasm/api/sqlite3-api-glue.js 056f44b82c126358a0175e08a892d56fadfce177b0d7a0012502a6acf67ea6d5
|
||||
F ext/wasm/api/sqlite3-api-oo1.js e9a83489bbb4838ce0aee46eaaa9350e0e25a5b926b565e4f5ae8e840e4fbaed
|
||||
F ext/wasm/api/sqlite3-api-opfs.js 38d368e33f470f9ba196f1a2b0c9ce076c930c70df233c345a246f1ad4c26d3b
|
||||
F ext/wasm/api/sqlite3-api-prologue.js 08e96d26d329e8c1e08813fe0b84ee93e0e78b087efdd6eb2809ae2672902437
|
||||
F ext/wasm/api/sqlite3-api-cleanup.js 6a22a3287b34a2b4b0f8127614242e8307cce31ac060eb705a89a6d04dcb1028
|
||||
F ext/wasm/api/sqlite3-api-glue.js 03c40b65530d67bb2748b7380aea5fd1534500f812b76a6b401066dcd7fc4116
|
||||
F ext/wasm/api/sqlite3-api-oo1.js 06ad2079368e16cb9f182c18cd37bdc3932536856dff4f60582d0ca5f6c491a8
|
||||
F ext/wasm/api/sqlite3-api-prologue.js e1db3935e1deb1340c1dc0c0e4730b2b88254d616841ebd5bc6bb1b90b32657f
|
||||
F ext/wasm/api/sqlite3-api-worker1.js e94ba98e44afccfa482874cd9acb325883ade50ed1f9f9526beb9de1711f182f
|
||||
F ext/wasm/api/sqlite3-license-version-header.js a661182fc93fc2cf212dfd0b987f8e138a3ac98f850b1112e29b5fbdaecc87c3
|
||||
F ext/wasm/api/sqlite3-opfs-async-proxy.js 1ec10873f1d59d305f6f3b435c50a1b75d693d5fb739b226f3da46fcbb11261a
|
||||
F ext/wasm/api/sqlite3-opfs-async-proxy.js 9963c78bf6e5ccb5ba28e8597851bd9d980e86803b6d341cc985e586aef10c82
|
||||
F ext/wasm/api/sqlite3-vfs-helper.js 4ad4faf02e1524bf0296be8452c00b5708dce6faf649468d0377e26a0b299263
|
||||
F ext/wasm/api/sqlite3-vfs-opfs.js 654f37fd6312d3bb0d067b21ad42f9dcfd629fd34ace892e67e06143a65dc6d0 w ext/wasm/api/sqlite3-api-opfs.js
|
||||
F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
|
||||
F ext/wasm/api/sqlite3-wasm.c 8fc8f47680df0e9a6c0f2f03cb004148645ecc983aa216daba09cb21f7e092a2
|
||||
F ext/wasm/api/sqlite3-wasm.c 8b32787a3b6bb2990cbaba2304bd5b75a9652acbc8d29909b3279019b6cbaef5
|
||||
F ext/wasm/api/sqlite3-worker1-promiser.js 0c7a9826dbf82a5ed4e4f7bf7816e825a52aff253afbf3350431f5773faf0e4b
|
||||
F ext/wasm/api/sqlite3-worker1.js 1e54ea3d540161bcfb2100368a2fc0cad871a207b8336afee1c445715851ec54
|
||||
F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8
|
||||
@@ -541,7 +545,7 @@ F ext/wasm/module-symbols.html b8eebafef8e536624bbe5f7a3da40c07a9062b843dfd3161a
|
||||
F ext/wasm/scratchpad-wasmfs-main.html 20cf6f1a8f368e70d01e8c17200e3eaa90f1c8e1029186d836d14b83845fbe06
|
||||
F ext/wasm/scratchpad-wasmfs-main.js 4c140457f4d6da9d646a49addd91edb6e9ad1643c6c48e3258b5bce24725dc18
|
||||
F ext/wasm/speedtest1-wasmfs.html bc28eb29b69a73864b8d7aae428448f8b7e1de81d8bfb9bba99541322054dbd0
|
||||
F ext/wasm/speedtest1-worker.html d75d9de9347c6f2f3278b73ccb6bceb559080644aef385fe496fa389f58bcd66
|
||||
F ext/wasm/speedtest1-worker.html e94cecebcbb9d187647025edd04d37af9789dfba98c2cc439b549b5ae8a8bc93
|
||||
F ext/wasm/speedtest1-worker.js 13b57c4a41729678a1194014afec2bd5b94435dcfc8d1039dfa9a533ac819ee1
|
||||
F ext/wasm/speedtest1.html e4c4e5c1c8ec1ad13c995e346e4216a1df152fd2c5cd17e0793b865b2f3c5000
|
||||
F ext/wasm/split-speedtest1-script.sh a3e271938d4d14ee49105eb05567c6a69ba4c1f1293583ad5af0cd3a3779e205 x
|
||||
@@ -550,17 +554,17 @@ F ext/wasm/sql/001-sudoku.sql 35b7cb7239ba5d5f193bc05ec379bcf66891bce6f2a5b3879f
|
||||
F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555e685bce3da8c3f
|
||||
F ext/wasm/test-opfs-vfs.js 44363db07b2a20e73b0eb1808de4400ca71b703af718d0fa6d962f15e73bf2ac
|
||||
F ext/wasm/tester1-worker.html 5ef353348c37cf2e4fd0b23da562d3275523e036260b510734e9a3239ba8c987
|
||||
F ext/wasm/tester1.c-pp.html 74aa9b31c75f12490653f814b53c3dd39f40cd3f70d6a53a716f4e8587107399 w ext/wasm/tester1.html
|
||||
F ext/wasm/tester1.c-pp.js 0c129495d057c77788b59715152d51f9bf9002ebbcce759ef8b028272ce3519d w ext/wasm/tester1.js
|
||||
F ext/wasm/tests/opfs/concurrency/index.html bb9b0f6da86df34c67fa506db9c45b7c4cf0045a211611cc6b8d2b53fa983481
|
||||
F ext/wasm/tests/opfs/concurrency/test.js 5993c08657d547d3a26b78ff3480122aed2b7361823bc127e96e558931093aff
|
||||
F ext/wasm/tests/opfs/concurrency/worker.js df065bb386ff994951f7fbdd76e12f16e58fbef0e929b2caf74553359da40afc
|
||||
F ext/wasm/tester1.c-pp.html 74aa9b31c75f12490653f814b53c3dd39f40cd3f70d6a53a716f4e8587107399
|
||||
F ext/wasm/tester1.c-pp.js 5820d578b67835aaf651230663dd207237071ee1f16f11a9c5168500aae04a50
|
||||
F ext/wasm/tests/opfs/concurrency/index.html 86d8ac435074d1e7007b91105f4897f368c165e8cecb6a9aa3d81f5cf5dcbe70
|
||||
F ext/wasm/tests/opfs/concurrency/test.js bfc3d7e27b207f0827f12568986b8d516a744529550b449314f5c21c9e9faf4a
|
||||
F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2
|
||||
F ext/wasm/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd72273503ae7d5
|
||||
F ext/wasm/wasmfs.make 8fea9b4f3cde06141de1fc4c586ab405bd32c3f401554f4ebb18c797401a678d
|
||||
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
|
||||
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
|
||||
F magic.txt 5ade0bc977aa135e79e3faaea894d5671b26107cc91e70783aa7dc83f22f3ba0
|
||||
F main.mk fd90f1bd90bd4070c9af7e9c8396bd0cf4208a0186c8d18fa6e2609dff447d5d
|
||||
F main.mk 5ce166d63fb99df9821f262fb1777fd5eb7b6c9396e867a6b0b2761f35f8238a
|
||||
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
|
||||
F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
|
||||
F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
|
||||
@@ -580,8 +584,8 @@ F src/auth.c f4fa91b6a90bbc8e0d0f738aa284551739c9543a367071f55574681e0f24f8cf
|
||||
F src/backup.c a2891172438e385fdbe97c11c9745676bec54f518d4447090af97189fd8e52d7
|
||||
F src/bitvec.c 7c849aac407230278445cb069bebc5f89bf2ddd87c5ed9459b070a9175707b3d
|
||||
F src/btmutex.c 6ffb0a22c19e2f9110be0964d0731d2ef1c67b5f7fabfbaeb7b9dabc4b7740ca
|
||||
F src/btree.c 522df0f1173495e06c5589f0b17a61f53a212017b82be53171133fcfc0e1e90a
|
||||
F src/btree.h 4fcbb0b041013071dd5e9f53c538d49916c092e6ad8842185985e5270a0792de
|
||||
F src/btree.c 2f794c217e52fdf4322bf37ee7778331b4d93aed2c00b5d67f914c0239a9edcc
|
||||
F src/btree.h 49da925329574798be3cbb745a49d069a9e67c99900d8a0d04b1e934d60394ea
|
||||
F src/btreeInt.h 88ad499c92b489afedbfefc3f067c4d15023ec021afe622db240dc9d2277cfa5
|
||||
F src/build.c d3e43e950e4e377c1d451a4862556792acdef1faba14a03f899d30d09731c48b
|
||||
F src/callback.c 4cd7225b26a97f7de5fee5ae10464bed5a78f2adefe19534cc2095b3a8ca484a
|
||||
@@ -591,7 +595,7 @@ F src/date.c 94ce83b4cd848a387680a5f920c9018c16655db778c4d36525af0a0f34679ac5
|
||||
F src/dbpage.c f1a87f4ebcf22284e0aaf0697862f4ccfc120dcd6db3d8dfa3b049b2580c01d8
|
||||
F src/dbstat.c 861e08690fcb0f2ee1165eff0060ea8d4f3e2ea10f80dab7d32ad70443a6ff2d
|
||||
F src/delete.c 86573edae75e3d3e9a8b590d87db8e47222103029df4f3e11fa56044459b514e
|
||||
F src/expr.c 847f87d9df3ede2b2b0a8db088af0b9c1923b21009f8ea1b9b7b28cb0a383170
|
||||
F src/expr.c 9e7fadc664b938c18f006be0d4f6669888f9302756ee204420c7eccaed5435a6
|
||||
F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007
|
||||
F src/fkey.c 722f20779f5342a787922deded3628d8c74b5249cab04098cf17ee2f2aaff002
|
||||
F src/func.c 7e86074afc4dc702691a29b7801f6dcc191db092b52e8bbe69dcd2f7be52194d
|
||||
@@ -603,9 +607,9 @@ F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71
|
||||
F src/insert.c 90a32bc7faa755cd5292ade21d2b3c6edba8fd1d70754a364caccabfde2c3bb2
|
||||
F src/json.c 7749b98c62f691697c7ee536b570c744c0583cab4a89200fdd0fc2aa8cc8cbd6
|
||||
F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa
|
||||
F src/loadext.c 8086232d10e51e183a7f64199815bad1c579896354db69435347665f62f481e9
|
||||
F src/main.c fa53bb2ae09549dab5629271c3cfd681f89059f5192afaaaf5c0d396bb3957fe
|
||||
F src/malloc.c dfddca1e163496c0a10250cedeafaf56dff47673e0f15888fb0925340a8e3f90
|
||||
F src/loadext.c 25663175950c5c4404b9377840b7b4c6fe5c53b415caf43634c62f442c02a9a7
|
||||
F src/main.c 954490392b74fb215378af3c75a9e1f4f559f19cb1567e5d77f3fbbb63909b4d
|
||||
F src/malloc.c 3d4ec162214024ee071d85711b93bec25cd3371280aee3702b63bcf312ca8238
|
||||
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
|
||||
F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de
|
||||
F src/mem2.c c8bfc9446fd0798bddd495eb5d9dbafa7d4b7287d8c22d50a83ac9daa26d8a75
|
||||
@@ -622,7 +626,7 @@ F src/mutex_w32.c caa50e1c0258ac4443f52e00fe8aaea73b6d0728bd8856bedfff822cae4185
|
||||
F src/notify.c 89a97dc854c3aa62ad5f384ef50c5a4a11d70fcc69f86de3e991573421130ed6
|
||||
F src/os.c 81c9c1c52eab711e27e33fd51fe5788488d3a02bc1a71439857abbee5d0d2c97
|
||||
F src/os.h 1ff5ae51d339d0e30d8a9d814f4b8f8e448169304d83a7ed9db66a65732f3e63
|
||||
F src/os_common.h b2f4707a603e36811d9b1a13278bffd757857b85
|
||||
F src/os_common.h 6c0eb8dd40ef3e12fe585a13e709710267a258e2c8dd1c40b1948a1d14582e06
|
||||
F src/os_kv.c 0e59600d25b72034c7666b8b7dcc527f039b5d9c16f24a7eca4c08c66f63c364
|
||||
F src/os_setup.h 6011ad7af5db4e05155f385eb3a9b4470688de6f65d6166b8956e58a3d872107
|
||||
F src/os_unix.c 2d16b0e0186805306680cfad36f1efd2909683cdabd0812f0460833b24e0e66d
|
||||
@@ -641,17 +645,17 @@ F src/printf.c e99ee9741e79ae3873458146f59644276657340385ade4e76a5f5d1c25793764
|
||||
F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
|
||||
F src/resolve.c efea4e5fbecfd6d0a9071b0be0d952620991673391b6ffaaf4c277b0bb674633
|
||||
F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
|
||||
F src/select.c 9886d6669f5787471aab6ae52af76fad90b53edb1c218fc9ed9d953363bc5184
|
||||
F src/shell.c.in c04980beb7d8a6443bef463d3bac695d35c76e26a577076f2390efc871193cea
|
||||
F src/sqlite.h.in b14769848d4c1bcf51b3a968c656133302443aa4112de52fec8425a862fa99e9
|
||||
F src/select.c 6983de0e6b8b68c97f82f9fca27ffb8f17161cff4d0d48fdf9eb40b213da0cc6
|
||||
F src/shell.c.in 8bad4432364203b90ede197bb2dfe23fd00e23596a4808111d8601b1271381bb
|
||||
F src/sqlite.h.in d9b18849710545395a7daddd3e375005e7dcb248195e6ab33cae77c8c40a7ff8
|
||||
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
|
||||
F src/sqlite3ext.h c4b9fa7a7e2bcdf850cfeb4b8a91d5ec47b7a00033bc996fd2ee96cbf2741f5f
|
||||
F src/sqliteInt.h 1a2cec0f7682da69975eb203fef2058949051ff500445b997d8047cbd8813315
|
||||
F src/sqliteInt.h a8d507577ca5e6581abb65c30497f7b8cfc7c9feb0d768a081c1e4a6adfcd061
|
||||
F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657
|
||||
F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749
|
||||
F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
|
||||
F src/tclsqlite.c 4e64ba300a5a26e0f1170e09032429faeb65e45e8f3d1a7833e8edb69fc2979e
|
||||
F src/test1.c 40c9a40975512985762f87b83d0c63e4904833a9fe78cbcca664a37095301b1d
|
||||
F src/test1.c 09ad3f2bf5f0efe70eb16e460054954b36be8ed09dfcafa0642b37d25d379f6f
|
||||
F src/test2.c 827446e259a3b7ab949da1542953edda7b5117982576d3e6f1c24a0dd20a5cef
|
||||
F src/test3.c 61798bb0d38b915067a8c8e03f5a534b431181f802659a6616f9b4ff7d872644
|
||||
F src/test4.c 4533b76419e7feb41b40582554663ed3cd77aaa54e135cf76b3205098cd6e664
|
||||
@@ -706,14 +710,14 @@ F src/test_window.c cdae419fdcea5bad6dcd9368c685abdad6deb59e9fc8b84b153de513d394
|
||||
F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9
|
||||
F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c
|
||||
F src/tokenize.c 1305797eab3542a0896b552c6e7669c972c1468e11e92b370533c1f37a37082b
|
||||
F src/treeview.c 07787f67cd297a6d09d04b8d70c06769c60c9c1d9080378f93929c16f8fd3298
|
||||
F src/trigger.c 4163ada044af89d51caba1cb713a73165347b2ec05fe84a283737c134d61fcd5
|
||||
F src/treeview.c 29b1dc7e0f84ba090734febe27393d4719682af0cae1b902d5ebf0236ecebea4
|
||||
F src/trigger.c 5e68b790f022b8dafbfb0eb244786512a95c9575fc198719d2557d73e5795858
|
||||
F src/update.c 5b0302c47cf31b533d5dff04c497ca1d8b9d89c39727e633fbe7b882fd5ac5aa
|
||||
F src/upsert.c 5303dc6c518fa7d4b280ec65170f465c7a70b7ac2b22491598f6d0b4875b3145
|
||||
F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0
|
||||
F src/util.c 0be191521ff6d2805995f4910f0b6231b42843678b2efdc1abecaf39929a673f
|
||||
F src/util.c 4e42c338d982e3f139004467bf0f2e9d679d519c7c75718fcf4ff1811401b03c
|
||||
F src/vacuum.c 84ce7f01f8a7a08748e107a441db83bcec13970190ddcb0c9ff522adbc1c23fd
|
||||
F src/vdbe.c c2d6d0c0c343d8ebffef996c73cbb69e337225f757fea7fe5c0e3ea14662adec
|
||||
F src/vdbe.c 00648bd76fb2145c2d890312112bda446569da64ca70aaf1f2282cc6c2b22d14
|
||||
F src/vdbe.h 58675f47dcf3105bab182c3ad3726efd60ffd003e954386904ac9107d0d2b743
|
||||
F src/vdbeInt.h 17b7461ffcf9ee760d1341731715a419f6b8c763089a7ece25c2e8098d702b3f
|
||||
F src/vdbeapi.c 1e8713d0b653acb43cd1bdf579c40e005c4844ea90f414f065946a83db3c27fb
|
||||
@@ -728,11 +732,11 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
|
||||
F src/wal.c b9df133a705093da8977da5eb202eaadb844839f1c7297c08d33471f5491843d
|
||||
F src/wal.h c3aa7825bfa2fe0d85bef2db94655f99870a285778baa36307c0a16da32b226a
|
||||
F src/walker.c f890a3298418d7cba3b69b8803594fdc484ea241206a8dfa99db6dd36f8cbb3b
|
||||
F src/where.c 1ef5aae7fac877057b9f360f06b26d4275888460d8fb6e92bbb9e70e07afe946
|
||||
F src/whereInt.h df0c79388c0b71b4a91f480d02791679fe0345d40410435c541c8893e95a4d3f
|
||||
F src/where.c 81422870d17fdf3880fdb9d3b3965e1aa5efb7a72c6933cec3f1bc8081c463db
|
||||
F src/whereInt.h e25203e5bfee149f5f1225ae0166cfb4f1e65490c998a024249e98bb0647377c
|
||||
F src/wherecode.c 133a94f82858787217d073143617df19e4a6a7d0b771a1519f957608109ad5a5
|
||||
F src/whereexpr.c 05295b44b54eea76d1ba766f0908928d0e20e990c249344c9521454d3d09c7ae
|
||||
F src/window.c 928e215840e2f2d9a2746e018c9643ef42c66c4ab6630ef0df7fa388fa145e86
|
||||
F src/window.c 14836767adb26573b50f528eb37f8b1336f2c430ab38de7cead1e5c546bb4d8c
|
||||
F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
|
||||
F test/affinity2.test ce1aafc86e110685b324e9a763eab4f2a73f737842ec3b687bd965867de90627
|
||||
F test/affinity3.test b5c19d504dec222c0dc66642673d23dce915d35737b68e74d9f237b80493eb53
|
||||
@@ -813,6 +817,7 @@ F test/backup_ioerr.test 4c3c7147cee85b024ecf6e150e090c32fdbb5135
|
||||
F test/backup_malloc.test 0c9abdf74c51e7bedb66d504cd684f28d4bd4027
|
||||
F test/badutf.test d5360fc31f643d37a973ab0d8b4fb85799c3169f
|
||||
F test/badutf2.test f310fd3b24a491b6b77bccdf14923b85d6ebcce751068c180d93a6b8ff854399
|
||||
F test/basexx1.test d8a50f0744b93dca656625597bcd3499ff4b9a4ea2a82432b119b7d46e3e0c08
|
||||
F test/bc_common.tcl b5e42d80305be95697e6370e015af571e5333a1c
|
||||
F test/bestindex1.test 856a453dff8c68b4568601eed5a8b5e20b4763af9229f3947c215729ed878db0
|
||||
F test/bestindex2.test 394ff8fbf34703391247116d6a44e1c50ee7282236ee77909044573cefc37bc0
|
||||
@@ -1094,7 +1099,7 @@ F test/fts3e.test 1f6c6ac9cc8b772ca256e6b22aaeed50c9350851
|
||||
F test/fts3expr.test ebae205a7a89446c32583bcd492dcb817b9f6b31819bb4dde2583bb99c77e526
|
||||
F test/fts3expr2.test 18da930352e5693eaa163a3eacf96233b7290d1a
|
||||
F test/fts3expr3.test c4d4a7d6327418428c96e0a3a1137c251b8dfbf8
|
||||
F test/fts3expr4.test f5b2832549f01b1f7f73389fa21d4b875499bc95bf7c8b36271844888c6a0938
|
||||
F test/fts3expr4.test 6c7675bbdbffe6ffc95e9e861500b8ac3f739c4d004ffda812f138eeb1b45529
|
||||
F test/fts3expr5.test a5b9a053becbdb8e973fbf4d6d3abaabeb42d511d1848bd57931f3e0a1cf983e
|
||||
F test/fts3f.test 8c438d5e1cab526b0021988fb1dc70cf3597b006a33ffd6c955ee89929077fe3
|
||||
F test/fts3fault.test f4e1342acfe6d216a001490e8cd52afac1f9ffe4a11bbcdcb296129a45c5df45
|
||||
@@ -1209,7 +1214,7 @@ F test/index7.test b238344318e0b4e42126717f6554f0e7dfd0b39cecad4b736039b43e1e3b6
|
||||
F test/index8.test caa097735c91dbc23d8a402f5e63a2a03c83840ba3928733ed7f9a03f8a912a3
|
||||
F test/index9.test 2ac891806a4136ef3e91280477e23114e67575207dc331e6797fa0ed9379f997
|
||||
F test/indexedby.test f21eca4f7a6ffe14c8500a7ad6cd53166666c99e5ccd311842a28bc94a195fe0
|
||||
F test/indexexpr1.test 3360c2a29a8844e7c4b13293567025281257f9e13a31854cfff6959cede11502
|
||||
F test/indexexpr1.test 1cd460113e3999a1477754fd0e3ac0d7bcbbbc742b427b418945f28af708c695
|
||||
F test/indexexpr2.test 2c7abe3c48f8aaa5a448615ab4d13df3662185d28419c00999670834a3f0b484
|
||||
F test/indexfault.test 98d78a8ff1f5335628b62f886a1cb7c7dac1ef6d48fa39c51ec871c87dce9811
|
||||
F test/init.test 15c823093fdabbf7b531fe22cf037134d09587a7
|
||||
@@ -1340,7 +1345,7 @@ F test/mmapfault.test d4c9eff9cd8c2dc14bc43e71e042f175b0a26fe3
|
||||
F test/mmapwarm.test 2272005969cd17a910077bd5082f70bc1fefad9a875afec7fc9af483898ecaf3
|
||||
F test/multiplex.test d74c034e52805f6de8cc5432cef8c9eb774bb64ec29b83a22effc8ca4dac1f08
|
||||
F test/multiplex2.test 580ca5817c7edbe4cc68fa150609c9473393003a
|
||||
F test/multiplex3.test d228f59eac91839a977eac19f21d053f03e4d101
|
||||
F test/multiplex3.test fac575e0b1b852025575a6a8357701d80933e98b5d2fe6d35ddaa68f92f6a1f7
|
||||
F test/multiplex4.test e8ae4c4bd70606a5727743241f13b5701990abe4
|
||||
F test/mutex1.test 177db2e4edb530f2ff21edc52ac79a412dbe63e4c47c3ae9504d3fb4f1ce81fa
|
||||
F test/mutex2.test bfeaeac2e73095b2ac32285d2756e3a65e681660
|
||||
@@ -1403,7 +1408,7 @@ F test/printf.test 390d0d7fcffc3c4ea3c1bb4cbb267444e32b33b048ae21895f23a291844fe
|
||||
F test/printf2.test 3f55c1871a5a65507416076f6eb97e738d5210aeda7595a74ee895f2224cce60
|
||||
F test/progress.test ebab27f670bd0d4eb9d20d49cef96e68141d92fb
|
||||
F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc
|
||||
F test/pushdown.test 5e72c51c5e33253ed639ccee1e01ce62d62b6eee5ca893cd82334e4ee7b1d7fc
|
||||
F test/pushdown.test c69f0970ea17e0afc674b89741f60c172cb6f761d81665fc71015f674f0f66ba
|
||||
F test/queryonly.test 5f653159e0f552f0552d43259890c1089391dcca
|
||||
F test/quick.test 1681febc928d686362d50057c642f77a02c62e57
|
||||
F test/quota-glob.test 32901e9eed6705d68ca3faee2a06b73b57cb3c26
|
||||
@@ -1421,7 +1426,7 @@ F test/reindex.test cd9d6021729910ece82267b4f5e1b5ac2911a7566c43b43c176a6a4732e2
|
||||
F test/releasetest_data.tcl 0db8aee0c348090fd06da47020ab4ed8ec692e0723427b2f3947d4dfb806f3b0
|
||||
F test/resetdb.test 8062cf10a09d8c048f8de7711e94571c38b38168db0e5877ba7561789e5eeb2b
|
||||
F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb
|
||||
F test/returning1.test c43b8370a351f77aec6d71f4a2cde59b849369ed1933261a2c2c69e23e34ff5e
|
||||
F test/returning1.test 1366e04566cfe1a082d17b1e0f195ec64473c79374b3a5d4ae00c43d885dea31
|
||||
F test/returningfault.test ae4c4b5e8745813287a359d9ccdb9d5c883c2e68afb18fb0767937d5de5692a4
|
||||
F test/rollback.test 06680159bc6746d0f26276e339e3ae2f951c64812468308838e0a3362d911eaa
|
||||
F test/rollback2.test 3f3a4e20401825017df7e7671e9f31b6de5fae5620c2b9b49917f52f8c160a8f
|
||||
@@ -1497,7 +1502,7 @@ F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304
|
||||
F test/shell1.test e4b4de56f454708e0747b52915135baa2cbfec4965406d6eaf02a4a5c22a9880
|
||||
F test/shell2.test c536c2aab4852608f8a606262330797abc4d964a4c2c782a7760f54ea1f17a6a
|
||||
F test/shell3.test 91febeac0412812bf6370abb8ed72700e32bf8f9878849414518f662dfd55e8a
|
||||
F test/shell4.test 7dc8a515705bc093d8ffe381670e8fa7a969661e8ed177c35c847e3c6dfc35e2
|
||||
F test/shell4.test 9abd0c12a7e20a4c49e84d5be208d2124fa6c09e728f56f1f4bee0f02853935f
|
||||
F test/shell5.test c8b6c54f26ec537f8558273d7ed293ca3725ef42e6b12b8f151718628bd1473b
|
||||
F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3
|
||||
F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f
|
||||
@@ -1628,6 +1633,7 @@ F test/tkt-868145d012.test a5f941107ece6a64410ca4755c6329b7eb57a356
|
||||
F test/tkt-8c63ff0ec.test 258b7fc8d7e4e1cb5362c7d65c143528b9c4cbed
|
||||
F test/tkt-91e2e8ba6f.test 08c4f94ae07696b05c9b822da0b4e5337a2f54c5
|
||||
F test/tkt-94c04eaadb.test f738c57c7f68ab8be1c054415af7774617cb6223
|
||||
F test/tkt-99378177930f87bd.test 0f932e85fa1d41f30532cb7be9718d82e491e953123b8c4c85cf025f36ffe34b
|
||||
F test/tkt-9a8b09f8e6.test b2ef151d0984b2ebf237760dbeaa50724e5a0667
|
||||
F test/tkt-9d68c883.test 16f7cb96781ba579bc2e19bb14b4ad609d9774b6
|
||||
F test/tkt-9f2eb3abac.test cb6123ac695a08b4454c3792fbe85108f67fabf8
|
||||
@@ -1995,8 +2001,8 @@ F tool/mkshellc.tcl 02d0de8349ef830c0fb20d29680320bde2466e2ec422e5bd94c4317a7a7e
|
||||
F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9
|
||||
F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
|
||||
F tool/mksqlite3c-noext.tcl 4f7cfef5152b0c91920355cbfc1d608a4ad242cb819f1aea07f6d0274f584a7f
|
||||
F tool/mksqlite3c.tcl 4fc26a9bfa0c4505b203d7ca0cf086e75ebcd4d63eb719c82f37e3fecdf23d37
|
||||
F tool/mksqlite3h.tcl 1f5e4a1dbbbc43c83cc6e74fe32c6c620502240b66c7c0f33a51378e78fc4edf
|
||||
F tool/mksqlite3c.tcl eb47021591b1ad4a6862e2cb5625f1ac67ec1e0c6db5ba3953c069c635284cf5
|
||||
F tool/mksqlite3h.tcl d391cff7cad0a372ee1406faee9ccc7dad9cb80a0c95cae0f73d10dd26e06762
|
||||
F tool/mksqlite3internalh.tcl eb994013e833359137eb53a55acdad0b5ae1049b
|
||||
F tool/mkvsix.tcl b9e0777a213c23156b6542842c238479e496ebf5
|
||||
F tool/offsets.c 8ed2b344d33f06e71366a9b93ccedaa38c096cc1dbd4c3c26ad08c6115285845
|
||||
@@ -2059,8 +2065,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 d469ac0c448eced26a697751ce9be316e8bc3cd029fda2b50966523c7850854f 5f564bf7de7ce3ad7bedb5f06b3086ceaec55da768a60d74059fa4fba4328567
|
||||
R c6e7dc02521e7ecf758d690fc58cd968
|
||||
P 95de6742d3d96d2b21eec57195dc7a2236d3f61640633ae1baa36bf142a3485b eddafafffa634a42ceeed70aa3fc58be130527612157a4bf4ff9e65c7f6dc26c
|
||||
R 697b52b98f11229ba2b335e2d06000d1
|
||||
U stephan
|
||||
Z ef5aed11fc17ce6d5e08251a18764500
|
||||
Z 9b84cfa6c56dbe2c1cd3d1878651d694
|
||||
# Remove this line to create a well-formed Fossil manifest.
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
95de6742d3d96d2b21eec57195dc7a2236d3f61640633ae1baa36bf142a3485b
|
||||
64cf34a85505814b4c93e82855c25559c8096bb3ce38cdc5e0fb9218c6bcce32
|
||||
+14
-3
@@ -7127,12 +7127,12 @@ static int insertCell(
|
||||
assert( get2byte(&data[pPage->hdrOffset+3])==pPage->nCell || CORRUPT_DB );
|
||||
#ifndef SQLITE_OMIT_AUTOVACUUM
|
||||
if( pPage->pBt->autoVacuum ){
|
||||
int rc = SQLITE_OK;
|
||||
int rc2 = SQLITE_OK;
|
||||
/* The cell may contain a pointer to an overflow page. If so, write
|
||||
** the entry for the overflow page into the pointer map.
|
||||
*/
|
||||
ptrmapPutOvflPtr(pPage, pPage, pCell, &rc);
|
||||
if( rc ) return rc;
|
||||
ptrmapPutOvflPtr(pPage, pPage, pCell, &rc2);
|
||||
if( rc2 ) return rc2;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -11083,6 +11083,17 @@ int sqlite3BtreeIsReadonly(Btree *p){
|
||||
*/
|
||||
int sqlite3HeaderSizeBtree(void){ return ROUND8(sizeof(MemPage)); }
|
||||
|
||||
/*
|
||||
** If no transaction is active and the database is not a temp-db, clear
|
||||
** the in-memory pager cache.
|
||||
*/
|
||||
void sqlite3BtreeClearCache(Btree *p){
|
||||
BtShared *pBt = p->pBt;
|
||||
if( pBt->inTransaction==TRANS_NONE ){
|
||||
sqlite3PagerClearCache(pBt->pPager);
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(SQLITE_OMIT_SHARED_CACHE)
|
||||
/*
|
||||
** Return true if the Btree passed as the only argument is sharable.
|
||||
|
||||
@@ -368,6 +368,8 @@ void sqlite3BtreeCursorList(Btree*);
|
||||
|
||||
int sqlite3BtreeTransferRow(BtCursor*, BtCursor*, i64);
|
||||
|
||||
void sqlite3BtreeClearCache(Btree*);
|
||||
|
||||
/*
|
||||
** If we are not using shared cache, then there is no need to
|
||||
** use mutexes to access the BtShared structures. So make the
|
||||
|
||||
+118
-74
@@ -53,7 +53,7 @@ char sqlite3ExprAffinity(const Expr *pExpr){
|
||||
}
|
||||
op = pExpr->op;
|
||||
if( op==TK_REGISTER ) op = pExpr->op2;
|
||||
if( op==TK_COLUMN || op==TK_AGG_COLUMN ){
|
||||
if( op==TK_COLUMN || (op==TK_AGG_COLUMN && pExpr->y.pTab!=0) ){
|
||||
assert( ExprUseYTab(pExpr) );
|
||||
assert( pExpr->y.pTab!=0 );
|
||||
return sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn);
|
||||
@@ -173,7 +173,9 @@ CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr){
|
||||
while( p ){
|
||||
int op = p->op;
|
||||
if( op==TK_REGISTER ) op = p->op2;
|
||||
if( op==TK_AGG_COLUMN || op==TK_COLUMN || op==TK_TRIGGER ){
|
||||
if( (op==TK_AGG_COLUMN && p->y.pTab!=0)
|
||||
|| op==TK_COLUMN || op==TK_TRIGGER
|
||||
){
|
||||
int j;
|
||||
assert( ExprUseYTab(p) );
|
||||
assert( p->y.pTab!=0 );
|
||||
@@ -4037,7 +4039,7 @@ static int exprCodeInlineFunction(
|
||||
}
|
||||
|
||||
/*
|
||||
** Check to see if pExpr is one of the indexed expressions on pParse->pIdxExpr.
|
||||
** Check to see if pExpr is one of the indexed expressions on pParse->pIdxEpr.
|
||||
** If it is, then resolve the expression by reading from the index and
|
||||
** return the register into which the value has been read. If pExpr is
|
||||
** not an indexed expression, then return negative.
|
||||
@@ -4049,7 +4051,7 @@ static SQLITE_NOINLINE int sqlite3IndexedExprLookup(
|
||||
){
|
||||
IndexedExpr *p;
|
||||
Vdbe *v;
|
||||
for(p=pParse->pIdxExpr; p; p=p->pIENext){
|
||||
for(p=pParse->pIdxEpr; p; p=p->pIENext){
|
||||
int iDataCur = p->iDataCur;
|
||||
if( iDataCur<0 ) continue;
|
||||
if( pParse->iSelfTab ){
|
||||
@@ -4069,10 +4071,10 @@ static SQLITE_NOINLINE int sqlite3IndexedExprLookup(
|
||||
sqlite3VdbeAddOp3(v, OP_Column, p->iIdxCur, p->iIdxCol, target);
|
||||
VdbeComment((v, "%s expr-column %d", p->zIdxName, p->iIdxCol));
|
||||
sqlite3VdbeGoto(v, 0);
|
||||
p = pParse->pIdxExpr;
|
||||
pParse->pIdxExpr = 0;
|
||||
p = pParse->pIdxEpr;
|
||||
pParse->pIdxEpr = 0;
|
||||
sqlite3ExprCode(pParse, pExpr, target);
|
||||
pParse->pIdxExpr = p;
|
||||
pParse->pIdxEpr = p;
|
||||
sqlite3VdbeJumpHere(v, addr+2);
|
||||
}else{
|
||||
sqlite3VdbeAddOp3(v, OP_Column, p->iIdxCur, p->iIdxCol, target);
|
||||
@@ -4111,7 +4113,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){
|
||||
expr_code_doover:
|
||||
if( pExpr==0 ){
|
||||
op = TK_NULL;
|
||||
}else if( pParse->pIdxExpr!=0
|
||||
}else if( pParse->pIdxEpr!=0
|
||||
&& !ExprHasProperty(pExpr, EP_Leaf)
|
||||
&& (r1 = sqlite3IndexedExprLookup(pParse, pExpr, target))>=0
|
||||
){
|
||||
@@ -4128,15 +4130,16 @@ expr_code_doover:
|
||||
assert( pExpr->iAgg>=0 && pExpr->iAgg<pAggInfo->nColumn );
|
||||
pCol = &pAggInfo->aCol[pExpr->iAgg];
|
||||
if( !pAggInfo->directMode ){
|
||||
assert( pCol->iMem>0 );
|
||||
return pCol->iMem;
|
||||
return AggInfoColumnReg(pAggInfo, pExpr->iAgg);
|
||||
}else if( pAggInfo->useSortingIdx ){
|
||||
Table *pTab = pCol->pTab;
|
||||
sqlite3VdbeAddOp3(v, OP_Column, pAggInfo->sortingIdxPTab,
|
||||
pCol->iSorterColumn, target);
|
||||
if( pCol->iColumn<0 ){
|
||||
if( pTab==0 ){
|
||||
/* No comment added */
|
||||
}else if( pCol->iColumn<0 ){
|
||||
VdbeComment((v,"%s.rowid",pTab->zName));
|
||||
}else if( ALWAYS(pTab!=0) ){
|
||||
}else{
|
||||
VdbeComment((v,"%s.%s",
|
||||
pTab->zName, pTab->aCol[pCol->iColumn].zCnName));
|
||||
if( pTab->aCol[pCol->iColumn].affinity==SQLITE_AFF_REAL ){
|
||||
@@ -4144,6 +4147,11 @@ expr_code_doover:
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}else if( pExpr->y.pTab==0 ){
|
||||
/* This case happens when the argument to an aggregate function
|
||||
** is rewritten by aggregateConvertIndexedExprRefToColumn() */
|
||||
sqlite3VdbeAddOp3(v, OP_Column, pExpr->iTable, pExpr->iColumn, target);
|
||||
return target;
|
||||
}
|
||||
/* Otherwise, fall thru into the TK_COLUMN case */
|
||||
/* no break */ deliberate_fall_through
|
||||
@@ -4441,7 +4449,7 @@ expr_code_doover:
|
||||
assert( !ExprHasProperty(pExpr, EP_IntValue) );
|
||||
sqlite3ErrorMsg(pParse, "misuse of aggregate: %#T()", pExpr);
|
||||
}else{
|
||||
return pInfo->aFunc[pExpr->iAgg].iMem;
|
||||
return AggInfoFuncReg(pInfo, pExpr->iAgg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -4730,7 +4738,7 @@ expr_code_doover:
|
||||
if( pAggInfo ){
|
||||
assert( pExpr->iAgg>=0 && pExpr->iAgg<pAggInfo->nColumn );
|
||||
if( !pAggInfo->directMode ){
|
||||
inReg = pAggInfo->aCol[pExpr->iAgg].iMem;
|
||||
inReg = AggInfoColumnReg(pAggInfo, pExpr->iAgg);
|
||||
break;
|
||||
}
|
||||
if( pExpr->pAggInfo->useSortingIdx ){
|
||||
@@ -6242,6 +6250,72 @@ static int addAggInfoFunc(sqlite3 *db, AggInfo *pInfo){
|
||||
return i;
|
||||
}
|
||||
|
||||
/*
|
||||
** Search the AggInfo object for an aCol[] entry that has iTable and iColumn.
|
||||
** Return the index in aCol[] of the entry that describes that column.
|
||||
**
|
||||
** If no prior entry is found, create a new one and return -1. The
|
||||
** new column will have an idex of pAggInfo->nColumn-1.
|
||||
*/
|
||||
static void findOrCreateAggInfoColumn(
|
||||
Parse *pParse, /* Parsing context */
|
||||
AggInfo *pAggInfo, /* The AggInfo object to search and/or modify */
|
||||
Expr *pExpr /* Expr describing the column to find or insert */
|
||||
){
|
||||
struct AggInfo_col *pCol;
|
||||
int k;
|
||||
|
||||
assert( pAggInfo->iFirstReg==0 );
|
||||
pCol = pAggInfo->aCol;
|
||||
for(k=0; k<pAggInfo->nColumn; k++, pCol++){
|
||||
if( pCol->iTable==pExpr->iTable
|
||||
&& pCol->iColumn==pExpr->iColumn
|
||||
&& pExpr->op!=TK_IF_NULL_ROW
|
||||
){
|
||||
goto fix_up_expr;
|
||||
}
|
||||
}
|
||||
k = addAggInfoColumn(pParse->db, pAggInfo);
|
||||
if( k<0 ){
|
||||
/* OOM on resize */
|
||||
assert( pParse->db->mallocFailed );
|
||||
return;
|
||||
}
|
||||
pCol = &pAggInfo->aCol[k];
|
||||
assert( ExprUseYTab(pExpr) );
|
||||
pCol->pTab = pExpr->y.pTab;
|
||||
pCol->iTable = pExpr->iTable;
|
||||
pCol->iColumn = pExpr->iColumn;
|
||||
pCol->iSorterColumn = -1;
|
||||
pCol->pCExpr = pExpr;
|
||||
if( pAggInfo->pGroupBy && pExpr->op!=TK_IF_NULL_ROW ){
|
||||
int j, n;
|
||||
ExprList *pGB = pAggInfo->pGroupBy;
|
||||
struct ExprList_item *pTerm = pGB->a;
|
||||
n = pGB->nExpr;
|
||||
for(j=0; j<n; j++, pTerm++){
|
||||
Expr *pE = pTerm->pExpr;
|
||||
if( pE->op==TK_COLUMN
|
||||
&& pE->iTable==pExpr->iTable
|
||||
&& pE->iColumn==pExpr->iColumn
|
||||
){
|
||||
pCol->iSorterColumn = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( pCol->iSorterColumn<0 ){
|
||||
pCol->iSorterColumn = pAggInfo->nSortingColumn++;
|
||||
}
|
||||
fix_up_expr:
|
||||
ExprSetVVAProperty(pExpr, EP_NoReduce);
|
||||
pExpr->pAggInfo = pAggInfo;
|
||||
if( pExpr->op==TK_COLUMN ){
|
||||
pExpr->op = TK_AGG_COLUMN;
|
||||
}
|
||||
pExpr->iAgg = (i16)k;
|
||||
}
|
||||
|
||||
/*
|
||||
** This is the xExprCallback for a tree walker. It is used to
|
||||
** implement sqlite3ExprAnalyzeAggregates(). See sqlite3ExprAnalyzeAggregates
|
||||
@@ -6255,7 +6329,36 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){
|
||||
AggInfo *pAggInfo = pNC->uNC.pAggInfo;
|
||||
|
||||
assert( pNC->ncFlags & NC_UAggInfo );
|
||||
assert( pAggInfo->iFirstReg==0 );
|
||||
switch( pExpr->op ){
|
||||
default: {
|
||||
IndexedExpr *pIEpr;
|
||||
Expr tmp;
|
||||
assert( pParse->iSelfTab==0 );
|
||||
if( (pNC->ncFlags & NC_InAggFunc)==0 ) break;
|
||||
if( pParse->pIdxEpr==0 ) break;
|
||||
for(pIEpr=pParse->pIdxEpr; pIEpr; pIEpr=pIEpr->pIENext){
|
||||
int iDataCur = pIEpr->iDataCur;
|
||||
if( iDataCur<0 ) continue;
|
||||
if( sqlite3ExprCompare(0, pExpr, pIEpr->pExpr, iDataCur)==0 ) break;
|
||||
}
|
||||
if( pIEpr==0 ) break;
|
||||
if( NEVER(!ExprUseYTab(pExpr)) ) break;
|
||||
|
||||
/* If we reach this point, it means that expression pExpr can be
|
||||
** translated into a reference to an index column as described by
|
||||
** pIEpr.
|
||||
*/
|
||||
memset(&tmp, 0, sizeof(tmp));
|
||||
tmp.op = TK_AGG_COLUMN;
|
||||
tmp.iTable = pIEpr->iIdxCur;
|
||||
tmp.iColumn = pIEpr->iIdxCol;
|
||||
findOrCreateAggInfoColumn(pParse, pAggInfo, &tmp);
|
||||
pAggInfo->aCol[tmp.iAgg].pCExpr = pExpr;
|
||||
pExpr->pAggInfo = pAggInfo;
|
||||
pExpr->iAgg = tmp.iAgg;
|
||||
return WRC_Prune;
|
||||
}
|
||||
case TK_IF_NULL_ROW:
|
||||
case TK_AGG_COLUMN:
|
||||
case TK_COLUMN: {
|
||||
@@ -6267,67 +6370,9 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){
|
||||
if( ALWAYS(pSrcList!=0) ){
|
||||
SrcItem *pItem = pSrcList->a;
|
||||
for(i=0; i<pSrcList->nSrc; i++, pItem++){
|
||||
struct AggInfo_col *pCol;
|
||||
assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) );
|
||||
if( pExpr->iTable==pItem->iCursor ){
|
||||
/* If we reach this point, it means that pExpr refers to a table
|
||||
** that is in the FROM clause of the aggregate query.
|
||||
**
|
||||
** Make an entry for the column in pAggInfo->aCol[] if there
|
||||
** is not an entry there already.
|
||||
*/
|
||||
int k;
|
||||
pCol = pAggInfo->aCol;
|
||||
for(k=0; k<pAggInfo->nColumn; k++, pCol++){
|
||||
if( pCol->iTable==pExpr->iTable
|
||||
&& pCol->iColumn==pExpr->iColumn
|
||||
&& pExpr->op!=TK_IF_NULL_ROW
|
||||
){
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( (k>=pAggInfo->nColumn)
|
||||
&& (k = addAggInfoColumn(pParse->db, pAggInfo))>=0
|
||||
){
|
||||
pCol = &pAggInfo->aCol[k];
|
||||
assert( ExprUseYTab(pExpr) );
|
||||
pCol->pTab = pExpr->y.pTab;
|
||||
pCol->iTable = pExpr->iTable;
|
||||
pCol->iColumn = pExpr->iColumn;
|
||||
pCol->iMem = ++pParse->nMem;
|
||||
pCol->iSorterColumn = -1;
|
||||
pCol->pCExpr = pExpr;
|
||||
if( pAggInfo->pGroupBy && pExpr->op!=TK_IF_NULL_ROW ){
|
||||
int j, n;
|
||||
ExprList *pGB = pAggInfo->pGroupBy;
|
||||
struct ExprList_item *pTerm = pGB->a;
|
||||
n = pGB->nExpr;
|
||||
for(j=0; j<n; j++, pTerm++){
|
||||
Expr *pE = pTerm->pExpr;
|
||||
if( pE->op==TK_COLUMN
|
||||
&& pE->iTable==pExpr->iTable
|
||||
&& pE->iColumn==pExpr->iColumn
|
||||
){
|
||||
pCol->iSorterColumn = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( pCol->iSorterColumn<0 ){
|
||||
pCol->iSorterColumn = pAggInfo->nSortingColumn++;
|
||||
}
|
||||
}
|
||||
/* There is now an entry for pExpr in pAggInfo->aCol[] (either
|
||||
** because it was there before or because we just created it).
|
||||
** Convert the pExpr to be a TK_AGG_COLUMN referring to that
|
||||
** pAggInfo->aCol[] entry.
|
||||
*/
|
||||
ExprSetVVAProperty(pExpr, EP_NoReduce);
|
||||
pExpr->pAggInfo = pAggInfo;
|
||||
if( pExpr->op==TK_COLUMN ){
|
||||
pExpr->op = TK_AGG_COLUMN;
|
||||
}
|
||||
pExpr->iAgg = (i16)k;
|
||||
findOrCreateAggInfoColumn(pParse, pAggInfo, pExpr);
|
||||
break;
|
||||
} /* endif pExpr->iTable==pItem->iCursor */
|
||||
} /* end loop over pSrcList */
|
||||
@@ -6357,7 +6402,6 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){
|
||||
assert( !ExprHasProperty(pExpr, EP_xIsSelect) );
|
||||
pItem = &pAggInfo->aFunc[i];
|
||||
pItem->pFExpr = pExpr;
|
||||
pItem->iMem = ++pParse->nMem;
|
||||
assert( ExprUseUToken(pExpr) );
|
||||
pItem->pFunc = sqlite3FindFunction(pParse->db,
|
||||
pExpr->u.zToken,
|
||||
|
||||
+1
-1
@@ -510,7 +510,7 @@ static const sqlite3_api_routines sqlite3Apis = {
|
||||
#endif
|
||||
sqlite3_db_name,
|
||||
/* Version 3.40.0 and later */
|
||||
sqlite3_value_type
|
||||
sqlite3_value_encoding
|
||||
};
|
||||
|
||||
/* True if x is the directory separator character
|
||||
|
||||
@@ -3950,6 +3950,9 @@ int sqlite3_file_control(sqlite3 *db, const char *zDbName, int op, void *pArg){
|
||||
sqlite3BtreeSetPageSize(pBtree, 0, iNew, 0);
|
||||
}
|
||||
rc = SQLITE_OK;
|
||||
}else if( op==SQLITE_FCNTL_RESET_CACHE ){
|
||||
sqlite3BtreeClearCache(pBtree);
|
||||
rc = SQLITE_OK;
|
||||
}else{
|
||||
int nSave = db->busyHandler.nBusy;
|
||||
rc = sqlite3OsFileControl(fd, op, pArg);
|
||||
|
||||
+6
-1
@@ -793,9 +793,14 @@ char *sqlite3DbStrNDup(sqlite3 *db, const char *z, u64 n){
|
||||
*/
|
||||
char *sqlite3DbSpanDup(sqlite3 *db, const char *zStart, const char *zEnd){
|
||||
int n;
|
||||
#ifdef SQLITE_DEBUG
|
||||
/* Because of the way the parser works, the span is guaranteed to contain
|
||||
** at least one non-space character */
|
||||
for(n=0; sqlite3Isspace(zStart[n]); n++){ assert( &zStart[n]<zEnd ); }
|
||||
#endif
|
||||
while( sqlite3Isspace(zStart[0]) ) zStart++;
|
||||
n = (int)(zEnd - zStart);
|
||||
while( ALWAYS(n>0) && sqlite3Isspace(zStart[n-1]) ) n--;
|
||||
while( sqlite3Isspace(zStart[n-1]) ) n--;
|
||||
return sqlite3DbStrNDup(db, zStart, n);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,12 +35,6 @@
|
||||
*/
|
||||
#ifdef SQLITE_PERFORMANCE_TRACE
|
||||
|
||||
/*
|
||||
** hwtime.h contains inline assembler code for implementing
|
||||
** high-performance timing routines.
|
||||
*/
|
||||
#include "hwtime.h"
|
||||
|
||||
static sqlite_uint64 g_start;
|
||||
static sqlite_uint64 g_elapsed;
|
||||
#define TIMER_START g_start=sqlite3Hwtime()
|
||||
|
||||
+323
-127
@@ -2091,7 +2091,7 @@ void sqlite3GenerateColumnNames(
|
||||
if( pParse->colNamesSet ) return;
|
||||
/* Column names are determined by the left-most term of a compound select */
|
||||
while( pSelect->pPrior ) pSelect = pSelect->pPrior;
|
||||
SELECTTRACE(1,pParse,pSelect,("generating column names\n"));
|
||||
TREETRACE(0x80,pParse,pSelect,("generating column names\n"));
|
||||
pTabList = pSelect->pSrc;
|
||||
pEList = pSelect->pEList;
|
||||
assert( v!=0 );
|
||||
@@ -2877,7 +2877,7 @@ static int multiSelect(
|
||||
pPrior->iLimit = p->iLimit;
|
||||
pPrior->iOffset = p->iOffset;
|
||||
pPrior->pLimit = p->pLimit;
|
||||
SELECTTRACE(1, pParse, p, ("multiSelect UNION ALL left...\n"));
|
||||
TREETRACE(0x200, pParse, p, ("multiSelect UNION ALL left...\n"));
|
||||
rc = sqlite3Select(pParse, pPrior, &dest);
|
||||
pPrior->pLimit = 0;
|
||||
if( rc ){
|
||||
@@ -2895,7 +2895,7 @@ static int multiSelect(
|
||||
}
|
||||
}
|
||||
ExplainQueryPlan((pParse, 1, "UNION ALL"));
|
||||
SELECTTRACE(1, pParse, p, ("multiSelect UNION ALL right...\n"));
|
||||
TREETRACE(0x200, pParse, p, ("multiSelect UNION ALL right...\n"));
|
||||
rc = sqlite3Select(pParse, p, &dest);
|
||||
testcase( rc!=SQLITE_OK );
|
||||
pDelete = p->pPrior;
|
||||
@@ -2948,7 +2948,7 @@ static int multiSelect(
|
||||
*/
|
||||
assert( !pPrior->pOrderBy );
|
||||
sqlite3SelectDestInit(&uniondest, priorOp, unionTab);
|
||||
SELECTTRACE(1, pParse, p, ("multiSelect EXCEPT/UNION left...\n"));
|
||||
TREETRACE(0x200, pParse, p, ("multiSelect EXCEPT/UNION left...\n"));
|
||||
rc = sqlite3Select(pParse, pPrior, &uniondest);
|
||||
if( rc ){
|
||||
goto multi_select_end;
|
||||
@@ -2968,7 +2968,7 @@ static int multiSelect(
|
||||
uniondest.eDest = op;
|
||||
ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE",
|
||||
sqlite3SelectOpName(p->op)));
|
||||
SELECTTRACE(1, pParse, p, ("multiSelect EXCEPT/UNION right...\n"));
|
||||
TREETRACE(0x200, pParse, p, ("multiSelect EXCEPT/UNION right...\n"));
|
||||
rc = sqlite3Select(pParse, p, &uniondest);
|
||||
testcase( rc!=SQLITE_OK );
|
||||
assert( p->pOrderBy==0 );
|
||||
@@ -3029,7 +3029,7 @@ static int multiSelect(
|
||||
/* Code the SELECTs to our left into temporary table "tab1".
|
||||
*/
|
||||
sqlite3SelectDestInit(&intersectdest, SRT_Union, tab1);
|
||||
SELECTTRACE(1, pParse, p, ("multiSelect INTERSECT left...\n"));
|
||||
TREETRACE(0x400, pParse, p, ("multiSelect INTERSECT left...\n"));
|
||||
rc = sqlite3Select(pParse, pPrior, &intersectdest);
|
||||
if( rc ){
|
||||
goto multi_select_end;
|
||||
@@ -3046,7 +3046,7 @@ static int multiSelect(
|
||||
intersectdest.iSDParm = tab2;
|
||||
ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE",
|
||||
sqlite3SelectOpName(p->op)));
|
||||
SELECTTRACE(1, pParse, p, ("multiSelect INTERSECT right...\n"));
|
||||
TREETRACE(0x400, pParse, p, ("multiSelect INTERSECT right...\n"));
|
||||
rc = sqlite3Select(pParse, p, &intersectdest);
|
||||
testcase( rc!=SQLITE_OK );
|
||||
pDelete = p->pPrior;
|
||||
@@ -4050,6 +4050,34 @@ static ExprList *findLeftmostExprlist(Select *pSel){
|
||||
return pSel->pEList;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return true if any of the result-set columns in the compound query
|
||||
** have incompatible affinities on one or more arms of the compound.
|
||||
*/
|
||||
static int compoundHasDifferentAffinities(Select *p){
|
||||
int ii;
|
||||
ExprList *pList;
|
||||
assert( p!=0 );
|
||||
assert( p->pEList!=0 );
|
||||
assert( p->pPrior!=0 );
|
||||
pList = p->pEList;
|
||||
for(ii=0; ii<pList->nExpr; ii++){
|
||||
char aff;
|
||||
Select *pSub1;
|
||||
assert( pList->a[ii].pExpr!=0 );
|
||||
aff = sqlite3ExprAffinity(pList->a[ii].pExpr);
|
||||
for(pSub1=p->pPrior; pSub1; pSub1=pSub1->pPrior){
|
||||
assert( pSub1->pEList!=0 );
|
||||
assert( pSub1->pEList->nExpr>ii );
|
||||
assert( pSub1->pEList->a[ii].pExpr!=0 );
|
||||
if( sqlite3ExprAffinity(pSub1->pEList->a[ii].pExpr)!=aff ){
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
|
||||
/*
|
||||
** This routine attempts to flatten subqueries as a performance optimization.
|
||||
@@ -4153,7 +4181,8 @@ static ExprList *findLeftmostExprlist(Select *pSel){
|
||||
** query or there are no RIGHT or FULL JOINs in any arm
|
||||
** of the subquery. (This is a duplicate of condition (27b).)
|
||||
** (17h) The corresponding result set expressions in all arms of the
|
||||
** compound must have the same affinity.
|
||||
** compound must have the same affinity. (See restriction (9)
|
||||
** on the push-down optimization.)
|
||||
**
|
||||
** The parent and sub-query may contain WHERE clauses. Subject to
|
||||
** rules (11), (13) and (14), they may also contain ORDER BY,
|
||||
@@ -4372,19 +4401,7 @@ static int flattenSubquery(
|
||||
if( (p->selFlags & SF_Recursive) ) return 0;
|
||||
|
||||
/* Restriction (17h) */
|
||||
for(ii=0; ii<pSub->pEList->nExpr; ii++){
|
||||
char aff;
|
||||
assert( pSub->pEList->a[ii].pExpr!=0 );
|
||||
aff = sqlite3ExprAffinity(pSub->pEList->a[ii].pExpr);
|
||||
for(pSub1=pSub->pPrior; pSub1; pSub1=pSub1->pPrior){
|
||||
assert( pSub1->pEList!=0 );
|
||||
assert( pSub1->pEList->nExpr>ii );
|
||||
assert( pSub1->pEList->a[ii].pExpr!=0 );
|
||||
if( sqlite3ExprAffinity(pSub1->pEList->a[ii].pExpr)!=aff ){
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( compoundHasDifferentAffinities(pSub) ) return 0;
|
||||
|
||||
if( pSrc->nSrc>1 ){
|
||||
if( pParse->nSelect>500 ) return 0;
|
||||
@@ -4395,7 +4412,7 @@ static int flattenSubquery(
|
||||
}
|
||||
|
||||
/***** If we reach this point, flattening is permitted. *****/
|
||||
SELECTTRACE(1,pParse,p,("flatten %u.%p from term %d\n",
|
||||
TREETRACE(0x4,pParse,p,("flatten %u.%p from term %d\n",
|
||||
pSub->selId, pSub, iFrom));
|
||||
|
||||
/* Authorize the subquery */
|
||||
@@ -4474,7 +4491,7 @@ static int flattenSubquery(
|
||||
if( pPrior ) pPrior->pNext = pNew;
|
||||
pNew->pNext = p;
|
||||
p->pPrior = pNew;
|
||||
SELECTTRACE(2,pParse,p,("compound-subquery flattener"
|
||||
TREETRACE(0x4,pParse,p,("compound-subquery flattener"
|
||||
" creates %u as peer\n",pNew->selId));
|
||||
}
|
||||
assert( pSubitem->pSelect==0 );
|
||||
@@ -4654,8 +4671,8 @@ static int flattenSubquery(
|
||||
sqlite3SelectDelete(db, pSub1);
|
||||
|
||||
#if TREETRACE_ENABLED
|
||||
if( sqlite3TreeTrace & 0x100 ){
|
||||
SELECTTRACE(0x100,pParse,p,("After flattening:\n"));
|
||||
if( sqlite3TreeTrace & 0x4 ){
|
||||
TREETRACE(0x4,pParse,p,("After flattening:\n"));
|
||||
sqlite3TreeViewSelect(0, p, 0);
|
||||
}
|
||||
#endif
|
||||
@@ -5029,12 +5046,14 @@ static int pushDownWindowCheck(Parse *pParse, Select *pSubq, Expr *pExpr){
|
||||
** be materialized. (This restriction is implemented in the calling
|
||||
** routine.)
|
||||
**
|
||||
** (8) The subquery may not be a compound that uses UNION, INTERSECT,
|
||||
** or EXCEPT. (We could, perhaps, relax this restriction to allow
|
||||
** this case if none of the comparisons operators between left and
|
||||
** right arms of the compound use a collation other than BINARY.
|
||||
** But it is a lot of work to check that case for an obscure and
|
||||
** minor optimization, so we omit it for now.)
|
||||
** (8) If the subquery is a compound that uses UNION, INTERSECT,
|
||||
** or EXCEPT, then all of the result set columns for all arms of
|
||||
** the compound must use the BINARY collating sequence.
|
||||
**
|
||||
** (9) If the subquery is a compound, then all arms of the compound must
|
||||
** have the same affinity. (This is the same as restriction (17h)
|
||||
** for query flattening.)
|
||||
**
|
||||
**
|
||||
** Return 0 if no changes are made and non-zero if one or more WHERE clause
|
||||
** terms are duplicated into the subquery.
|
||||
@@ -5051,20 +5070,44 @@ static int pushDownWhereTerms(
|
||||
if( pSubq->selFlags & (SF_Recursive|SF_MultiPart) ) return 0;
|
||||
if( pSrc->fg.jointype & (JT_LTORJ|JT_RIGHT) ) return 0;
|
||||
|
||||
#ifndef SQLITE_OMIT_WINDOWFUNC
|
||||
if( pSubq->pPrior ){
|
||||
Select *pSel;
|
||||
int notUnionAll = 0;
|
||||
for(pSel=pSubq; pSel; pSel=pSel->pPrior){
|
||||
u8 op = pSel->op;
|
||||
assert( op==TK_ALL || op==TK_SELECT
|
||||
|| op==TK_UNION || op==TK_INTERSECT || op==TK_EXCEPT );
|
||||
if( op!=TK_ALL && op!=TK_SELECT ) return 0; /* restriction (8) */
|
||||
if( op!=TK_ALL && op!=TK_SELECT ){
|
||||
notUnionAll = 1;
|
||||
}
|
||||
#ifndef SQLITE_OMIT_WINDOWFUNC
|
||||
if( pSel->pWin ) return 0; /* restriction (6b) */
|
||||
#endif
|
||||
}
|
||||
if( compoundHasDifferentAffinities(pSubq) ){
|
||||
return 0; /* restriction (9) */
|
||||
}
|
||||
if( notUnionAll ){
|
||||
/* If any of the compound arms are connected using UNION, INTERSECT,
|
||||
** or EXCEPT, then we must ensure that none of the columns use a
|
||||
** non-BINARY collating sequence. */
|
||||
for(pSel=pSubq; pSel; pSel=pSel->pPrior){
|
||||
int ii;
|
||||
const ExprList *pList = pSel->pEList;
|
||||
assert( pList!=0 );
|
||||
for(ii=0; ii<pList->nExpr; ii++){
|
||||
CollSeq *pColl = sqlite3ExprCollSeq(pParse, pList->a[ii].pExpr);
|
||||
if( !sqlite3IsBinary(pColl) ){
|
||||
return 0; /* Restriction (8) */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
#ifndef SQLITE_OMIT_WINDOWFUNC
|
||||
if( pSubq->pWin && pSubq->pWin->pPartition==0 ) return 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef SQLITE_DEBUG
|
||||
/* Only the first term of a compound can have a WITH clause. But make
|
||||
@@ -6068,8 +6111,8 @@ static int selectExpander(Walker *pWalker, Select *p){
|
||||
}
|
||||
}
|
||||
#if TREETRACE_ENABLED
|
||||
if( sqlite3TreeTrace & 0x100 ){
|
||||
SELECTTRACE(0x100,pParse,p,("After result-set wildcard expansion:\n"));
|
||||
if( sqlite3TreeTrace & 0x8 ){
|
||||
TREETRACE(0x8,pParse,p,("After result-set wildcard expansion:\n"));
|
||||
sqlite3TreeViewSelect(0, p, 0);
|
||||
}
|
||||
#endif
|
||||
@@ -6204,6 +6247,173 @@ void sqlite3SelectPrep(
|
||||
sqlite3SelectAddTypeInfo(pParse, p);
|
||||
}
|
||||
|
||||
#if TREETRACE_ENABLED
|
||||
/*
|
||||
** Display all information about an AggInfo object
|
||||
*/
|
||||
static void printAggInfo(AggInfo *pAggInfo){
|
||||
int ii;
|
||||
for(ii=0; ii<pAggInfo->nColumn; ii++){
|
||||
struct AggInfo_col *pCol = &pAggInfo->aCol[ii];
|
||||
sqlite3DebugPrintf(
|
||||
"agg-column[%d] pTab=%s iTable=%d iColumn=%d iMem=%d"
|
||||
" iSorterColumn=%d %s\n",
|
||||
ii, pCol->pTab ? pCol->pTab->zName : "NULL",
|
||||
pCol->iTable, pCol->iColumn, pAggInfo->iFirstReg+ii,
|
||||
pCol->iSorterColumn,
|
||||
ii>=pAggInfo->nAccumulator ? "" : " Accumulator");
|
||||
sqlite3TreeViewExpr(0, pAggInfo->aCol[ii].pCExpr, 0);
|
||||
}
|
||||
for(ii=0; ii<pAggInfo->nFunc; ii++){
|
||||
sqlite3DebugPrintf("agg-func[%d]: iMem=%d\n",
|
||||
ii, AggInfoFuncReg(pAggInfo,ii));
|
||||
sqlite3TreeViewExpr(0, pAggInfo->aFunc[ii].pFExpr, 0);
|
||||
}
|
||||
}
|
||||
#endif /* TREETRACE_ENABLED */
|
||||
|
||||
/*
|
||||
** Analyze the arguments to aggregate functions. Create new pAggInfo->aCol[]
|
||||
** entries for columns that are arguments to aggregate functions but which
|
||||
** are not otherwise used.
|
||||
**
|
||||
** The aCol[] entries in AggInfo prior to nAccumulator are columns that
|
||||
** are referenced outside of aggregate functions. These might be columns
|
||||
** that are part of the GROUP by clause, for example. Other database engines
|
||||
** would throw an error if there is a column reference that is not in the
|
||||
** GROUP BY clause and that is not part of an aggregate function argument.
|
||||
** But SQLite allows this.
|
||||
**
|
||||
** The aCol[] entries beginning with the aCol[nAccumulator] and following
|
||||
** are column references that are used exclusively as arguments to
|
||||
** aggregate functions. This routine is responsible for computing
|
||||
** (or recomputing) those aCol[] entries.
|
||||
*/
|
||||
static void analyzeAggFuncArgs(
|
||||
AggInfo *pAggInfo,
|
||||
NameContext *pNC
|
||||
){
|
||||
int i;
|
||||
assert( pAggInfo!=0 );
|
||||
assert( pAggInfo->iFirstReg==0 );
|
||||
pNC->ncFlags |= NC_InAggFunc;
|
||||
for(i=0; i<pAggInfo->nFunc; i++){
|
||||
Expr *pExpr = pAggInfo->aFunc[i].pFExpr;
|
||||
assert( ExprUseXList(pExpr) );
|
||||
sqlite3ExprAnalyzeAggList(pNC, pExpr->x.pList);
|
||||
#ifndef SQLITE_OMIT_WINDOWFUNC
|
||||
assert( !IsWindowFunc(pExpr) );
|
||||
if( ExprHasProperty(pExpr, EP_WinFunc) ){
|
||||
sqlite3ExprAnalyzeAggregates(pNC, pExpr->y.pWin->pFilter);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
pNC->ncFlags &= ~NC_InAggFunc;
|
||||
}
|
||||
|
||||
/*
|
||||
** An index on expressions is being used in the inner loop of an
|
||||
** aggregate query with a GROUP BY clause. This routine attempts
|
||||
** to adjust the AggInfo object to take advantage of index and to
|
||||
** perhaps use the index as a covering index.
|
||||
**
|
||||
*/
|
||||
static void optimizeAggregateUseOfIndexedExpr(
|
||||
Parse *pParse, /* Parsing context */
|
||||
Select *pSelect, /* The SELECT statement being processed */
|
||||
AggInfo *pAggInfo, /* The aggregate info */
|
||||
NameContext *pNC /* Name context used to resolve agg-func args */
|
||||
){
|
||||
assert( pAggInfo->iFirstReg==0 );
|
||||
pAggInfo->nColumn = pAggInfo->nAccumulator;
|
||||
if( ALWAYS(pAggInfo->nSortingColumn>0) ){
|
||||
if( pAggInfo->nColumn==0 ){
|
||||
pAggInfo->nSortingColumn = 0;
|
||||
}else{
|
||||
pAggInfo->nSortingColumn =
|
||||
pAggInfo->aCol[pAggInfo->nColumn-1].iSorterColumn+1;
|
||||
}
|
||||
}
|
||||
analyzeAggFuncArgs(pAggInfo, pNC);
|
||||
#if TREETRACE_ENABLED
|
||||
if( sqlite3TreeTrace & 0x20 ){
|
||||
IndexedExpr *pIEpr;
|
||||
TREETRACE(0x20, pParse, pSelect,
|
||||
("AggInfo (possibly) adjusted for Indexed Exprs\n"));
|
||||
sqlite3TreeViewSelect(0, pSelect, 0);
|
||||
for(pIEpr=pParse->pIdxEpr; pIEpr; pIEpr=pIEpr->pIENext){
|
||||
printf("data-cursor=%d index={%d,%d}\n",
|
||||
pIEpr->iDataCur, pIEpr->iIdxCur, pIEpr->iIdxCol);
|
||||
sqlite3TreeViewExpr(0, pIEpr->pExpr, 0);
|
||||
}
|
||||
printAggInfo(pAggInfo);
|
||||
}
|
||||
#else
|
||||
UNUSED_PARAMETER(pSelect);
|
||||
UNUSED_PARAMETER(pParse);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
** Walker callback for aggregateConvertIndexedExprRefToColumn().
|
||||
*/
|
||||
static int aggregateIdxEprRefToColCallback(Walker *pWalker, Expr *pExpr){
|
||||
AggInfo *pAggInfo;
|
||||
struct AggInfo_col *pCol;
|
||||
UNUSED_PARAMETER(pWalker);
|
||||
if( pExpr->pAggInfo==0 ) return WRC_Continue;
|
||||
if( pExpr->op==TK_AGG_COLUMN ) return WRC_Continue;
|
||||
if( pExpr->op==TK_AGG_FUNCTION ) return WRC_Continue;
|
||||
if( pExpr->op==TK_IF_NULL_ROW ) return WRC_Continue;
|
||||
pAggInfo = pExpr->pAggInfo;
|
||||
assert( pExpr->iAgg>=0 && pExpr->iAgg<pAggInfo->nColumn );
|
||||
pCol = &pAggInfo->aCol[pExpr->iAgg];
|
||||
pExpr->op = TK_AGG_COLUMN;
|
||||
pExpr->iTable = pCol->iTable;
|
||||
pExpr->iColumn = pCol->iColumn;
|
||||
return WRC_Prune;
|
||||
}
|
||||
|
||||
/*
|
||||
** Convert every pAggInfo->aFunc[].pExpr such that any node within
|
||||
** those expressions that has pAppInfo set is changed into a TK_AGG_COLUMN
|
||||
** opcode.
|
||||
*/
|
||||
static void aggregateConvertIndexedExprRefToColumn(AggInfo *pAggInfo){
|
||||
int i;
|
||||
Walker w;
|
||||
memset(&w, 0, sizeof(w));
|
||||
w.xExprCallback = aggregateIdxEprRefToColCallback;
|
||||
for(i=0; i<pAggInfo->nFunc; i++){
|
||||
sqlite3WalkExpr(&w, pAggInfo->aFunc[i].pFExpr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Allocate a block of registers so that there is one register for each
|
||||
** pAggInfo->aCol[] and pAggInfo->aFunc[] entry in pAggInfo. The first
|
||||
** register in this block is stored in pAggInfo->iFirstReg.
|
||||
**
|
||||
** This routine may only be called once for each AggInfo object. Prior
|
||||
** to calling this routine:
|
||||
**
|
||||
** * The aCol[] and aFunc[] arrays may be modified
|
||||
** * The AggInfoColumnReg() and AggInfoFuncReg() macros may not be used
|
||||
**
|
||||
** After clling this routine:
|
||||
**
|
||||
** * The aCol[] and aFunc[] arrays are fixed
|
||||
** * The AggInfoColumnReg() and AggInfoFuncReg() macros may be used
|
||||
**
|
||||
*/
|
||||
static void assignAggregateRegisters(Parse *pParse, AggInfo *pAggInfo){
|
||||
assert( pAggInfo!=0 );
|
||||
assert( pAggInfo->iFirstReg==0 );
|
||||
pAggInfo->iFirstReg = pParse->nMem + 1;
|
||||
pParse->nMem += pAggInfo->nColumn + pAggInfo->nFunc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Reset the aggregate accumulator.
|
||||
**
|
||||
@@ -6217,24 +6427,13 @@ static void resetAccumulator(Parse *pParse, AggInfo *pAggInfo){
|
||||
int i;
|
||||
struct AggInfo_func *pFunc;
|
||||
int nReg = pAggInfo->nFunc + pAggInfo->nColumn;
|
||||
assert( pAggInfo->iFirstReg>0 );
|
||||
assert( pParse->db->pParse==pParse );
|
||||
assert( pParse->db->mallocFailed==0 || pParse->nErr!=0 );
|
||||
if( nReg==0 ) return;
|
||||
if( pParse->nErr ) return;
|
||||
#ifdef SQLITE_DEBUG
|
||||
/* Verify that all AggInfo registers are within the range specified by
|
||||
** AggInfo.mnReg..AggInfo.mxReg */
|
||||
assert( nReg==pAggInfo->mxReg-pAggInfo->mnReg+1 );
|
||||
for(i=0; i<pAggInfo->nColumn; i++){
|
||||
assert( pAggInfo->aCol[i].iMem>=pAggInfo->mnReg
|
||||
&& pAggInfo->aCol[i].iMem<=pAggInfo->mxReg );
|
||||
}
|
||||
for(i=0; i<pAggInfo->nFunc; i++){
|
||||
assert( pAggInfo->aFunc[i].iMem>=pAggInfo->mnReg
|
||||
&& pAggInfo->aFunc[i].iMem<=pAggInfo->mxReg );
|
||||
}
|
||||
#endif
|
||||
sqlite3VdbeAddOp3(v, OP_Null, 0, pAggInfo->mnReg, pAggInfo->mxReg);
|
||||
sqlite3VdbeAddOp3(v, OP_Null, 0, pAggInfo->iFirstReg,
|
||||
pAggInfo->iFirstReg+nReg-1);
|
||||
for(pFunc=pAggInfo->aFunc, i=0; i<pAggInfo->nFunc; i++, pFunc++){
|
||||
if( pFunc->iDistinct>=0 ){
|
||||
Expr *pE = pFunc->pFExpr;
|
||||
@@ -6266,15 +6465,16 @@ static void finalizeAggFunctions(Parse *pParse, AggInfo *pAggInfo){
|
||||
ExprList *pList;
|
||||
assert( ExprUseXList(pF->pFExpr) );
|
||||
pList = pF->pFExpr->x.pList;
|
||||
sqlite3VdbeAddOp2(v, OP_AggFinal, pF->iMem, pList ? pList->nExpr : 0);
|
||||
sqlite3VdbeAddOp2(v, OP_AggFinal, AggInfoFuncReg(pAggInfo,i),
|
||||
pList ? pList->nExpr : 0);
|
||||
sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Update the accumulator memory cells for an aggregate based on
|
||||
** the current cursor position.
|
||||
** Generate code that will update the accumulator memory cells for an
|
||||
** aggregate based on the current cursor position.
|
||||
**
|
||||
** If regAcc is non-zero and there are no min() or max() aggregates
|
||||
** in pAggInfo, then only populate the pAggInfo->nAccumulator accumulator
|
||||
@@ -6294,6 +6494,8 @@ static void updateAccumulator(
|
||||
struct AggInfo_func *pF;
|
||||
struct AggInfo_col *pC;
|
||||
|
||||
assert( pAggInfo->iFirstReg>0 );
|
||||
if( pParse->nErr ) return;
|
||||
pAggInfo->directMode = 1;
|
||||
for(i=0, pF=pAggInfo->aFunc; i<pAggInfo->nFunc; i++, pF++){
|
||||
int nArg;
|
||||
@@ -6354,7 +6556,7 @@ static void updateAccumulator(
|
||||
if( regHit==0 && pAggInfo->nAccumulator ) regHit = ++pParse->nMem;
|
||||
sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0, (char *)pColl, P4_COLLSEQ);
|
||||
}
|
||||
sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, pF->iMem);
|
||||
sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i));
|
||||
sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
|
||||
sqlite3VdbeChangeP5(v, (u8)nArg);
|
||||
sqlite3ReleaseTempRange(pParse, regAgg, nArg);
|
||||
@@ -6369,7 +6571,7 @@ static void updateAccumulator(
|
||||
addrHitTest = sqlite3VdbeAddOp1(v, OP_If, regHit); VdbeCoverage(v);
|
||||
}
|
||||
for(i=0, pC=pAggInfo->aCol; i<pAggInfo->nAccumulator; i++, pC++){
|
||||
sqlite3ExprCode(pParse, pC->pCExpr, pC->iMem);
|
||||
sqlite3ExprCode(pParse, pC->pCExpr, AggInfoColumnReg(pAggInfo,i));
|
||||
}
|
||||
|
||||
pAggInfo->directMode = 0;
|
||||
@@ -6465,7 +6667,7 @@ static void havingToWhere(Parse *pParse, Select *p){
|
||||
sqlite3WalkExpr(&sWalker, p->pHaving);
|
||||
#if TREETRACE_ENABLED
|
||||
if( sWalker.eCode && (sqlite3TreeTrace & 0x100)!=0 ){
|
||||
SELECTTRACE(0x100,pParse,p,("Move HAVING terms into WHERE:\n"));
|
||||
TREETRACE(0x100,pParse,p,("Move HAVING terms into WHERE:\n"));
|
||||
sqlite3TreeViewSelect(0, p, 0);
|
||||
}
|
||||
#endif
|
||||
@@ -6597,8 +6799,8 @@ static int countOfViewOptimization(Parse *pParse, Select *p){
|
||||
p->selFlags &= ~SF_Aggregate;
|
||||
|
||||
#if TREETRACE_ENABLED
|
||||
if( sqlite3TreeTrace & 0x400 ){
|
||||
SELECTTRACE(0x400,pParse,p,("After count-of-view optimization:\n"));
|
||||
if( sqlite3TreeTrace & 0x200 ){
|
||||
TREETRACE(0x200,pParse,p,("After count-of-view optimization:\n"));
|
||||
sqlite3TreeViewSelect(0, p, 0);
|
||||
}
|
||||
#endif
|
||||
@@ -6674,8 +6876,8 @@ int sqlite3Select(
|
||||
assert( db->mallocFailed==0 );
|
||||
if( sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0) ) return 1;
|
||||
#if TREETRACE_ENABLED
|
||||
SELECTTRACE(1,pParse,p, ("begin processing:\n", pParse->addrExplain));
|
||||
if( sqlite3TreeTrace & 0x10100 ){
|
||||
TREETRACE(0x1,pParse,p, ("begin processing:\n", pParse->addrExplain));
|
||||
if( sqlite3TreeTrace & 0x10000 ){
|
||||
if( (sqlite3TreeTrace & 0x10001)==0x10000 ){
|
||||
sqlite3TreeViewLine(0, "In sqlite3Select() at %s:%d",
|
||||
__FILE__, __LINE__);
|
||||
@@ -6695,8 +6897,8 @@ int sqlite3Select(
|
||||
/* All of these destinations are also able to ignore the ORDER BY clause */
|
||||
if( p->pOrderBy ){
|
||||
#if TREETRACE_ENABLED
|
||||
SELECTTRACE(1,pParse,p, ("dropping superfluous ORDER BY:\n"));
|
||||
if( sqlite3TreeTrace & 0x100 ){
|
||||
TREETRACE(0x800,pParse,p, ("dropping superfluous ORDER BY:\n"));
|
||||
if( sqlite3TreeTrace & 0x800 ){
|
||||
sqlite3TreeViewExprList(0, p->pOrderBy, 0, "ORDERBY");
|
||||
}
|
||||
#endif
|
||||
@@ -6716,8 +6918,8 @@ int sqlite3Select(
|
||||
assert( db->mallocFailed==0 );
|
||||
assert( p->pEList!=0 );
|
||||
#if TREETRACE_ENABLED
|
||||
if( sqlite3TreeTrace & 0x104 ){
|
||||
SELECTTRACE(0x104,pParse,p, ("after name resolution:\n"));
|
||||
if( sqlite3TreeTrace & 0x10 ){
|
||||
TREETRACE(0x10,pParse,p, ("after name resolution:\n"));
|
||||
sqlite3TreeViewSelect(0, p, 0);
|
||||
}
|
||||
#endif
|
||||
@@ -6758,8 +6960,8 @@ int sqlite3Select(
|
||||
goto select_end;
|
||||
}
|
||||
#if TREETRACE_ENABLED
|
||||
if( p->pWin && (sqlite3TreeTrace & 0x108)!=0 ){
|
||||
SELECTTRACE(0x104,pParse,p, ("after window rewrite:\n"));
|
||||
if( p->pWin && (sqlite3TreeTrace & 0x40)!=0 ){
|
||||
TREETRACE(0x40,pParse,p, ("after window rewrite:\n"));
|
||||
sqlite3TreeViewSelect(0, p, 0);
|
||||
}
|
||||
#endif
|
||||
@@ -6790,7 +6992,7 @@ int sqlite3Select(
|
||||
&& sqlite3ExprImpliesNonNullRow(p->pWhere, pItem->iCursor)
|
||||
&& OptimizationEnabled(db, SQLITE_SimplifyJoin)
|
||||
){
|
||||
SELECTTRACE(0x100,pParse,p,
|
||||
TREETRACE(0x1000,pParse,p,
|
||||
("LEFT-JOIN simplifies to JOIN on term %d\n",i));
|
||||
pItem->fg.jointype &= ~(JT_LEFT|JT_OUTER);
|
||||
assert( pItem->iCursor>=0 );
|
||||
@@ -6846,7 +7048,7 @@ int sqlite3Select(
|
||||
&& (p->selFlags & SF_OrderByReqd)==0 /* Condition (3) and (4) */
|
||||
&& OptimizationEnabled(db, SQLITE_OmitOrderBy)
|
||||
){
|
||||
SELECTTRACE(0x100,pParse,p,
|
||||
TREETRACE(0x800,pParse,p,
|
||||
("omit superfluous ORDER BY on %r FROM-clause subquery\n",i+1));
|
||||
sqlite3ParserAddCleanup(pParse,
|
||||
(void(*)(sqlite3*,void*))sqlite3ExprListDelete,
|
||||
@@ -6901,8 +7103,8 @@ int sqlite3Select(
|
||||
if( p->pPrior ){
|
||||
rc = multiSelect(pParse, p, pDest);
|
||||
#if TREETRACE_ENABLED
|
||||
SELECTTRACE(0x1,pParse,p,("end compound-select processing\n"));
|
||||
if( (sqlite3TreeTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){
|
||||
TREETRACE(0x400,pParse,p,("end compound-select processing\n"));
|
||||
if( (sqlite3TreeTrace & 0x400)!=0 && ExplainQueryPlanParent(pParse)==0 ){
|
||||
sqlite3TreeViewSelect(0, p, 0);
|
||||
}
|
||||
#endif
|
||||
@@ -6922,13 +7124,13 @@ int sqlite3Select(
|
||||
&& propagateConstants(pParse, p)
|
||||
){
|
||||
#if TREETRACE_ENABLED
|
||||
if( sqlite3TreeTrace & 0x100 ){
|
||||
SELECTTRACE(0x100,pParse,p,("After constant propagation:\n"));
|
||||
if( sqlite3TreeTrace & 0x2000 ){
|
||||
TREETRACE(0x2000,pParse,p,("After constant propagation:\n"));
|
||||
sqlite3TreeViewSelect(0, p, 0);
|
||||
}
|
||||
#endif
|
||||
}else{
|
||||
SELECTTRACE(0x100,pParse,p,("Constant propagation not helpful\n"));
|
||||
TREETRACE(0x2000,pParse,p,("Constant propagation not helpful\n"));
|
||||
}
|
||||
|
||||
#ifdef SQLITE_COUNTOFVIEW_OPTIMIZATION
|
||||
@@ -7001,15 +7203,15 @@ int sqlite3Select(
|
||||
&& pushDownWhereTerms(pParse, pSub, p->pWhere, pItem)
|
||||
){
|
||||
#if TREETRACE_ENABLED
|
||||
if( sqlite3TreeTrace & 0x100 ){
|
||||
SELECTTRACE(0x100,pParse,p,
|
||||
if( sqlite3TreeTrace & 0x4000 ){
|
||||
TREETRACE(0x4000,pParse,p,
|
||||
("After WHERE-clause push-down into subquery %d:\n", pSub->selId));
|
||||
sqlite3TreeViewSelect(0, p, 0);
|
||||
}
|
||||
#endif
|
||||
assert( pItem->pSelect && (pItem->pSelect->selFlags & SF_PushDown)!=0 );
|
||||
}else{
|
||||
SELECTTRACE(0x100,pParse,p,("Push-down not possible\n"));
|
||||
TREETRACE(0x4000,pParse,p,("Push-down not possible\n"));
|
||||
}
|
||||
|
||||
zSavedAuthContext = pParse->zAuthContext;
|
||||
@@ -7124,8 +7326,8 @@ int sqlite3Select(
|
||||
sDistinct.isTnct = (p->selFlags & SF_Distinct)!=0;
|
||||
|
||||
#if TREETRACE_ENABLED
|
||||
if( sqlite3TreeTrace & 0x400 ){
|
||||
SELECTTRACE(0x400,pParse,p,("After all FROM-clause analysis:\n"));
|
||||
if( sqlite3TreeTrace & 0x8000 ){
|
||||
TREETRACE(0x8000,pParse,p,("After all FROM-clause analysis:\n"));
|
||||
sqlite3TreeViewSelect(0, p, 0);
|
||||
}
|
||||
#endif
|
||||
@@ -7161,8 +7363,8 @@ int sqlite3Select(
|
||||
sDistinct.isTnct = 2;
|
||||
|
||||
#if TREETRACE_ENABLED
|
||||
if( sqlite3TreeTrace & 0x400 ){
|
||||
SELECTTRACE(0x400,pParse,p,("Transform DISTINCT into GROUP BY:\n"));
|
||||
if( sqlite3TreeTrace & 0x20000 ){
|
||||
TREETRACE(0x20000,pParse,p,("Transform DISTINCT into GROUP BY:\n"));
|
||||
sqlite3TreeViewSelect(0, p, 0);
|
||||
}
|
||||
#endif
|
||||
@@ -7248,7 +7450,7 @@ int sqlite3Select(
|
||||
|
||||
|
||||
/* Begin the database scan. */
|
||||
SELECTTRACE(1,pParse,p,("WhereBegin\n"));
|
||||
TREETRACE(0x2,pParse,p,("WhereBegin\n"));
|
||||
pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, sSort.pOrderBy,
|
||||
p->pEList, p, wctrlFlags, p->nSelectRow);
|
||||
if( pWInfo==0 ) goto select_end;
|
||||
@@ -7265,7 +7467,7 @@ int sqlite3Select(
|
||||
sSort.pOrderBy = 0;
|
||||
}
|
||||
}
|
||||
SELECTTRACE(1,pParse,p,("WhereBegin returns\n"));
|
||||
TREETRACE(0x2,pParse,p,("WhereBegin returns\n"));
|
||||
|
||||
/* If sorting index that was created by a prior OP_OpenEphemeral
|
||||
** instruction ended up not being needed, then change the OP_OpenEphemeral
|
||||
@@ -7304,7 +7506,7 @@ int sqlite3Select(
|
||||
|
||||
/* End the database scan loop.
|
||||
*/
|
||||
SELECTTRACE(1,pParse,p,("WhereEnd\n"));
|
||||
TREETRACE(0x2,pParse,p,("WhereEnd\n"));
|
||||
sqlite3WhereEnd(pWInfo);
|
||||
}
|
||||
}else{
|
||||
@@ -7390,7 +7592,6 @@ int sqlite3Select(
|
||||
sNC.pSrcList = pTabList;
|
||||
sNC.uNC.pAggInfo = pAggInfo;
|
||||
VVA_ONLY( sNC.ncFlags = NC_UAggInfo; )
|
||||
pAggInfo->mnReg = pParse->nMem+1;
|
||||
pAggInfo->nSortingColumn = pGroupBy ? pGroupBy->nExpr : 0;
|
||||
pAggInfo->pGroupBy = pGroupBy;
|
||||
sqlite3ExprAnalyzeAggList(&sNC, pEList);
|
||||
@@ -7411,45 +7612,17 @@ int sqlite3Select(
|
||||
}else{
|
||||
minMaxFlag = WHERE_ORDERBY_NORMAL;
|
||||
}
|
||||
for(i=0; i<pAggInfo->nFunc; i++){
|
||||
Expr *pExpr = pAggInfo->aFunc[i].pFExpr;
|
||||
assert( ExprUseXList(pExpr) );
|
||||
sNC.ncFlags |= NC_InAggFunc;
|
||||
sqlite3ExprAnalyzeAggList(&sNC, pExpr->x.pList);
|
||||
#ifndef SQLITE_OMIT_WINDOWFUNC
|
||||
assert( !IsWindowFunc(pExpr) );
|
||||
if( ExprHasProperty(pExpr, EP_WinFunc) ){
|
||||
sqlite3ExprAnalyzeAggregates(&sNC, pExpr->y.pWin->pFilter);
|
||||
}
|
||||
#endif
|
||||
sNC.ncFlags &= ~NC_InAggFunc;
|
||||
}
|
||||
pAggInfo->mxReg = pParse->nMem;
|
||||
analyzeAggFuncArgs(pAggInfo, &sNC);
|
||||
if( db->mallocFailed ) goto select_end;
|
||||
#if TREETRACE_ENABLED
|
||||
if( sqlite3TreeTrace & 0x400 ){
|
||||
int ii;
|
||||
SELECTTRACE(0x400,pParse,p,("After aggregate analysis %p:\n", pAggInfo));
|
||||
if( sqlite3TreeTrace & 0x20 ){
|
||||
TREETRACE(0x20,pParse,p,("After aggregate analysis %p:\n", pAggInfo));
|
||||
sqlite3TreeViewSelect(0, p, 0);
|
||||
if( minMaxFlag ){
|
||||
sqlite3DebugPrintf("MIN/MAX Optimization (0x%02x) adds:\n", minMaxFlag);
|
||||
sqlite3TreeViewExprList(0, pMinMaxOrderBy, 0, "ORDERBY");
|
||||
}
|
||||
for(ii=0; ii<pAggInfo->nColumn; ii++){
|
||||
struct AggInfo_col *pCol = &pAggInfo->aCol[ii];
|
||||
sqlite3DebugPrintf(
|
||||
"agg-column[%d] pTab=%s iTable=%d iColumn=%d iMem=%d"
|
||||
" iSorterColumn=%d\n",
|
||||
ii, pCol->pTab ? pCol->pTab->zName : "NULL",
|
||||
pCol->iTable, pCol->iColumn, pCol->iMem,
|
||||
pCol->iSorterColumn);
|
||||
sqlite3TreeViewExpr(0, pAggInfo->aCol[ii].pCExpr, 0);
|
||||
}
|
||||
for(ii=0; ii<pAggInfo->nFunc; ii++){
|
||||
sqlite3DebugPrintf("agg-func[%d]: iMem=%d\n",
|
||||
ii, pAggInfo->aFunc[ii].iMem);
|
||||
sqlite3TreeViewExpr(0, pAggInfo->aFunc[ii].pFExpr, 0);
|
||||
}
|
||||
printAggInfo(pAggInfo);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -7518,7 +7691,7 @@ int sqlite3Select(
|
||||
** in the right order to begin with.
|
||||
*/
|
||||
sqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset);
|
||||
SELECTTRACE(1,pParse,p,("WhereBegin\n"));
|
||||
TREETRACE(0x2,pParse,p,("WhereBegin\n"));
|
||||
pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pGroupBy, pDistinct,
|
||||
p, (sDistinct.isTnct==2 ? WHERE_DISTINCTBY : WHERE_GROUPBY)
|
||||
| (orderByGrp ? WHERE_SORTBYGROUP : 0) | distFlag, 0
|
||||
@@ -7527,8 +7700,12 @@ int sqlite3Select(
|
||||
sqlite3ExprListDelete(db, pDistinct);
|
||||
goto select_end;
|
||||
}
|
||||
if( pParse->pIdxEpr ){
|
||||
optimizeAggregateUseOfIndexedExpr(pParse, p, pAggInfo, &sNC);
|
||||
}
|
||||
assignAggregateRegisters(pParse, pAggInfo);
|
||||
eDist = sqlite3WhereIsDistinct(pWInfo);
|
||||
SELECTTRACE(1,pParse,p,("WhereBegin returns\n"));
|
||||
TREETRACE(0x2,pParse,p,("WhereBegin returns\n"));
|
||||
if( sqlite3WhereIsOrdered(pWInfo)==pGroupBy->nExpr ){
|
||||
/* The optimizer is able to deliver rows in group by order so
|
||||
** we do not have to sort. The OP_OpenEphemeral table will be
|
||||
@@ -7577,7 +7754,7 @@ int sqlite3Select(
|
||||
sqlite3VdbeAddOp2(v, OP_SorterInsert, pAggInfo->sortingIdx, regRecord);
|
||||
sqlite3ReleaseTempReg(pParse, regRecord);
|
||||
sqlite3ReleaseTempRange(pParse, regBase, nCol);
|
||||
SELECTTRACE(1,pParse,p,("WhereEnd\n"));
|
||||
TREETRACE(0x2,pParse,p,("WhereEnd\n"));
|
||||
sqlite3WhereEnd(pWInfo);
|
||||
pAggInfo->sortingIdxPTab = sortPTab = pParse->nTab++;
|
||||
sortOut = sqlite3GetTempReg(pParse);
|
||||
@@ -7587,6 +7764,23 @@ int sqlite3Select(
|
||||
pAggInfo->useSortingIdx = 1;
|
||||
}
|
||||
|
||||
/* If there entries in pAgggInfo->aFunc[] that contain subexpressions
|
||||
** that are indexed (and that were previously identified and tagged
|
||||
** in optimizeAggregateUseOfIndexedExpr()) then those subexpressions
|
||||
** must now be converted into a TK_AGG_COLUMN node so that the value
|
||||
** is correctly pulled from the index rather than being recomputed. */
|
||||
if( pParse->pIdxEpr ){
|
||||
aggregateConvertIndexedExprRefToColumn(pAggInfo);
|
||||
#if TREETRACE_ENABLED
|
||||
if( sqlite3TreeTrace & 0x20 ){
|
||||
TREETRACE(0x20, pParse, p,
|
||||
("AggInfo function expressions converted to reference index\n"));
|
||||
sqlite3TreeViewSelect(0, p, 0);
|
||||
printAggInfo(pAggInfo);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* If the index or temporary table used by the GROUP BY sort
|
||||
** will naturally deliver rows in the order required by the ORDER BY
|
||||
** clause, cancel the ephemeral table open coded earlier.
|
||||
@@ -7655,7 +7849,7 @@ int sqlite3Select(
|
||||
sqlite3VdbeAddOp2(v, OP_SorterNext, pAggInfo->sortingIdx,addrTopOfLoop);
|
||||
VdbeCoverage(v);
|
||||
}else{
|
||||
SELECTTRACE(1,pParse,p,("WhereEnd\n"));
|
||||
TREETRACE(0x2,pParse,p,("WhereEnd\n"));
|
||||
sqlite3WhereEnd(pWInfo);
|
||||
sqlite3VdbeChangeToNoop(v, addrSortingIdx);
|
||||
}
|
||||
@@ -7765,7 +7959,8 @@ int sqlite3Select(
|
||||
if( pKeyInfo ){
|
||||
sqlite3VdbeChangeP4(v, -1, (char *)pKeyInfo, P4_KEYINFO);
|
||||
}
|
||||
sqlite3VdbeAddOp2(v, OP_Count, iCsr, pAggInfo->aFunc[0].iMem);
|
||||
assignAggregateRegisters(pParse, pAggInfo);
|
||||
sqlite3VdbeAddOp2(v, OP_Count, iCsr, AggInfoFuncReg(pAggInfo,0));
|
||||
sqlite3VdbeAddOp1(v, OP_Close, iCsr);
|
||||
explainSimpleCount(pParse, pTab, pBest);
|
||||
}else{
|
||||
@@ -7801,6 +7996,7 @@ int sqlite3Select(
|
||||
pDistinct = pAggInfo->aFunc[0].pFExpr->x.pList;
|
||||
distFlag = pDistinct ? (WHERE_WANT_DISTINCT|WHERE_AGG_DISTINCT) : 0;
|
||||
}
|
||||
assignAggregateRegisters(pParse, pAggInfo);
|
||||
|
||||
/* This case runs if the aggregate has no GROUP BY clause. The
|
||||
** processing is much simpler since there is only a single row
|
||||
@@ -7817,13 +8013,13 @@ int sqlite3Select(
|
||||
assert( minMaxFlag==WHERE_ORDERBY_NORMAL || pMinMaxOrderBy!=0 );
|
||||
assert( pMinMaxOrderBy==0 || pMinMaxOrderBy->nExpr==1 );
|
||||
|
||||
SELECTTRACE(1,pParse,p,("WhereBegin\n"));
|
||||
TREETRACE(0x2,pParse,p,("WhereBegin\n"));
|
||||
pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pMinMaxOrderBy,
|
||||
pDistinct, p, minMaxFlag|distFlag, 0);
|
||||
if( pWInfo==0 ){
|
||||
goto select_end;
|
||||
}
|
||||
SELECTTRACE(1,pParse,p,("WhereBegin returns\n"));
|
||||
TREETRACE(0x2,pParse,p,("WhereBegin returns\n"));
|
||||
eDist = sqlite3WhereIsDistinct(pWInfo);
|
||||
updateAccumulator(pParse, regAcc, pAggInfo, eDist);
|
||||
if( eDist!=WHERE_DISTINCT_NOOP ){
|
||||
@@ -7837,7 +8033,7 @@ int sqlite3Select(
|
||||
if( minMaxFlag ){
|
||||
sqlite3WhereMinMaxOptEarlyOut(v, pWInfo);
|
||||
}
|
||||
SELECTTRACE(1,pParse,p,("WhereEnd\n"));
|
||||
TREETRACE(0x2,pParse,p,("WhereEnd\n"));
|
||||
sqlite3WhereEnd(pWInfo);
|
||||
finalizeAggFunctions(pParse, pAggInfo);
|
||||
}
|
||||
@@ -7884,7 +8080,7 @@ select_end:
|
||||
if( pAggInfo && !db->mallocFailed ){
|
||||
for(i=0; i<pAggInfo->nColumn; i++){
|
||||
Expr *pExpr = pAggInfo->aCol[i].pCExpr;
|
||||
assert( pExpr!=0 );
|
||||
if( pExpr==0 ) continue;
|
||||
assert( pExpr->pAggInfo==pAggInfo );
|
||||
assert( pExpr->iAgg==i );
|
||||
}
|
||||
@@ -7898,8 +8094,8 @@ select_end:
|
||||
#endif
|
||||
|
||||
#if TREETRACE_ENABLED
|
||||
SELECTTRACE(0x1,pParse,p,("end processing\n"));
|
||||
if( (sqlite3TreeTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){
|
||||
TREETRACE(0x1,pParse,p,("end processing\n"));
|
||||
if( (sqlite3TreeTrace & 0x40000)!=0 && ExplainQueryPlanParent(pParse)==0 ){
|
||||
sqlite3TreeViewSelect(0, p, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
+38
-3
@@ -1066,9 +1066,14 @@ INCLUDE ../ext/expert/sqlite3expert.c
|
||||
#define SQLITE_SHELL_HAVE_RECOVER 0
|
||||
#endif
|
||||
#if SQLITE_SHELL_HAVE_RECOVER
|
||||
INCLUDE ../ext/recover/dbdata.c
|
||||
INCLUDE ../ext/recover/sqlite3recover.h
|
||||
# ifndef SQLITE_HAVE_SQLITE3R
|
||||
INCLUDE ../ext/recover/dbdata.c
|
||||
INCLUDE ../ext/recover/sqlite3recover.c
|
||||
# endif
|
||||
#endif
|
||||
#ifdef SQLITE_SHELL_EXTSRC
|
||||
# include SHELL_STRINGIFY(SQLITE_SHELL_EXTSRC)
|
||||
#endif
|
||||
|
||||
#if defined(SQLITE_ENABLE_SESSION)
|
||||
@@ -5115,6 +5120,7 @@ static void open_db(ShellState *p, int openFlags){
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_LOAD_EXTENSION
|
||||
sqlite3_enable_load_extension(p->db, 1);
|
||||
#endif
|
||||
@@ -5137,6 +5143,34 @@ static void open_db(ShellState *p, int openFlags){
|
||||
sqlite3_sqlar_init(p->db, 0, 0);
|
||||
}
|
||||
#endif
|
||||
#ifdef SQLITE_SHELL_EXTFUNCS
|
||||
/* Create a preprocessing mechanism for extensions to make
|
||||
* their own provisions for being built into the shell.
|
||||
* This is a short-span macro. See further below for usage.
|
||||
*/
|
||||
#define SHELL_SUB_MACRO(base, variant) base ## _ ## variant
|
||||
#define SHELL_SUBMACRO(base, variant) SHELL_SUB_MACRO(base, variant)
|
||||
/* Let custom-included extensions get their ..._init() called.
|
||||
* The WHATEVER_INIT( db, pzErrorMsg, pApi ) macro should cause
|
||||
* the extension's sqlite3_*_init( db, pzErrorMsg, pApi )
|
||||
* inititialization routine to be called.
|
||||
*/
|
||||
{
|
||||
int irc = SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, INIT)(p->db);
|
||||
/* Let custom-included extensions expose their functionality.
|
||||
* The WHATEVER_EXPOSE( db, pzErrorMsg ) macro should cause
|
||||
* the SQL functions, virtual tables, collating sequences or
|
||||
* VFS's implemented by the extension to be registered.
|
||||
*/
|
||||
if( irc==SQLITE_OK
|
||||
|| irc==SQLITE_OK_LOAD_PERMANENTLY ){
|
||||
SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, EXPOSE)(p->db, 0);
|
||||
}
|
||||
#undef SHELL_SUB_MACRO
|
||||
#undef SHELL_SUBMACRO
|
||||
}
|
||||
#endif
|
||||
|
||||
sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0,
|
||||
shellAddSchemaName, 0, 0);
|
||||
sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0,
|
||||
@@ -5157,6 +5191,7 @@ static void open_db(ShellState *p, int openFlags){
|
||||
sqlite3_create_function(p->db, "edit", 2, SQLITE_UTF8, 0,
|
||||
editFunc, 0, 0);
|
||||
#endif
|
||||
|
||||
if( p->openMode==SHELL_OPEN_ZIPFILE ){
|
||||
char *zSql = sqlite3_mprintf(
|
||||
"CREATE VIRTUAL TABLE zip USING zipfile(%Q);", zDbFilename);
|
||||
@@ -10656,7 +10691,7 @@ static int do_meta_command(char *zLine, ShellState *p){
|
||||
}
|
||||
}else{
|
||||
output_file_close(p->traceOut);
|
||||
p->traceOut = output_file_open(azArg[1], 0);
|
||||
p->traceOut = output_file_open(z, 0);
|
||||
}
|
||||
}
|
||||
if( p->traceOut==0 ){
|
||||
@@ -11767,7 +11802,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", argv[i]);
|
||||
utf8_printf(stderr, "no such VFS: \"%s\"\n", zVfs);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1192,6 +1192,12 @@ struct sqlite3_io_methods {
|
||||
**
|
||||
** <li>[[SQLITE_FCNTL_CKSM_FILE]]
|
||||
** Used by the cksmvfs VFS module only.
|
||||
**
|
||||
** <li>[[SQLITE_FCNTL_RESET_CACHE]]
|
||||
** If there is currently no transaction open on the database, and the
|
||||
** database is not a temp db, then this file-control purges the contents
|
||||
** of the in-memory page cache. If there is an open transaction, or if
|
||||
** the db is a temp-db, it is a no-op, not an error.
|
||||
** </ul>
|
||||
*/
|
||||
#define SQLITE_FCNTL_LOCKSTATE 1
|
||||
@@ -1234,6 +1240,7 @@ struct sqlite3_io_methods {
|
||||
#define SQLITE_FCNTL_CKPT_START 39
|
||||
#define SQLITE_FCNTL_EXTERNAL_READER 40
|
||||
#define SQLITE_FCNTL_CKSM_FILE 41
|
||||
#define SQLITE_FCNTL_RESET_CACHE 42
|
||||
|
||||
/* deprecated names */
|
||||
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
|
||||
|
||||
+43
-7
@@ -1029,15 +1029,38 @@ extern u32 sqlite3TreeTrace;
|
||||
&& (defined(SQLITE_TEST) || defined(SQLITE_ENABLE_SELECTTRACE) \
|
||||
|| defined(SQLITE_ENABLE_TREETRACE))
|
||||
# define TREETRACE_ENABLED 1
|
||||
# define SELECTTRACE(K,P,S,X) \
|
||||
# define TREETRACE(K,P,S,X) \
|
||||
if(sqlite3TreeTrace&(K)) \
|
||||
sqlite3DebugPrintf("%u/%d/%p: ",(S)->selId,(P)->addrExplain,(S)),\
|
||||
sqlite3DebugPrintf X
|
||||
#else
|
||||
# define SELECTTRACE(K,P,S,X)
|
||||
# define TREETRACE(K,P,S,X)
|
||||
# define TREETRACE_ENABLED 0
|
||||
#endif
|
||||
|
||||
/* TREETRACE flag meanings:
|
||||
**
|
||||
** 0x00000001 Beginning and end of SELECT processing
|
||||
** 0x00000002 WHERE clause processing
|
||||
** 0x00000004 Query flattener
|
||||
** 0x00000008 Result-set wildcard expansion
|
||||
** 0x00000010 Query name resolution
|
||||
** 0x00000020 Aggregate analysis
|
||||
** 0x00000040 Window functions
|
||||
** 0x00000080 Generated column names
|
||||
** 0x00000100 Move HAVING terms into WHERE
|
||||
** 0x00000200 Count-of-view optimization
|
||||
** 0x00000400 Compound SELECT processing
|
||||
** 0x00000800 Drop superfluous ORDER BY
|
||||
** 0x00001000 LEFT JOIN simplifies to JOIN
|
||||
** 0x00002000 Constant propagation
|
||||
** 0x00004000 Push-down optimization
|
||||
** 0x00008000 After all FROM-clause analysis
|
||||
** 0x00010000 Beginning of DELETE/INSERT/UPDATE processing
|
||||
** 0x00020000 Transform DISTINCT into GROUP BY
|
||||
** 0x00040000 SELECT tree dump after all code has been generated
|
||||
*/
|
||||
|
||||
/*
|
||||
** Macros for "wheretrace"
|
||||
*/
|
||||
@@ -2716,16 +2739,15 @@ struct AggInfo {
|
||||
** from source tables rather than from accumulators */
|
||||
u8 useSortingIdx; /* In direct mode, reference the sorting index rather
|
||||
** than the source table */
|
||||
u16 nSortingColumn; /* Number of columns in the sorting index */
|
||||
int sortingIdx; /* Cursor number of the sorting index */
|
||||
int sortingIdxPTab; /* Cursor number of pseudo-table */
|
||||
int nSortingColumn; /* Number of columns in the sorting index */
|
||||
int mnReg, mxReg; /* Range of registers allocated for aCol and aFunc */
|
||||
int iFirstReg; /* First register in range for aCol[] and aFunc[] */
|
||||
ExprList *pGroupBy; /* The group by clause */
|
||||
struct AggInfo_col { /* For each column used in source tables */
|
||||
Table *pTab; /* Source table */
|
||||
Expr *pCExpr; /* The original expression */
|
||||
int iTable; /* Cursor number of the source table */
|
||||
int iMem; /* Memory location that acts as accumulator */
|
||||
i16 iColumn; /* Column number within the source table */
|
||||
i16 iSorterColumn; /* Column number in the sorting index */
|
||||
} *aCol;
|
||||
@@ -2736,7 +2758,6 @@ struct AggInfo {
|
||||
struct AggInfo_func { /* For each aggregate function */
|
||||
Expr *pFExpr; /* Expression encoding the function */
|
||||
FuncDef *pFunc; /* The aggregate function implementation */
|
||||
int iMem; /* Memory location that acts as accumulator */
|
||||
int iDistinct; /* Ephemeral table used to enforce DISTINCT */
|
||||
int iDistAddr; /* Address of OP_OpenEphemeral */
|
||||
} *aFunc;
|
||||
@@ -2744,6 +2765,17 @@ struct AggInfo {
|
||||
u32 selId; /* Select to which this AggInfo belongs */
|
||||
};
|
||||
|
||||
/*
|
||||
** Macros to compute aCol[] and aFunc[] register numbers.
|
||||
**
|
||||
** These macros should not be used prior to the call to
|
||||
** assignAggregateRegisters() that computes the value of pAggInfo->iFirstReg.
|
||||
** The assert()s that are part of this macro verify that constraint.
|
||||
*/
|
||||
#define AggInfoColumnReg(A,I) (assert((A)->iFirstReg),(A)->iFirstReg+(I))
|
||||
#define AggInfoFuncReg(A,I) \
|
||||
(assert((A)->iFirstReg),(A)->iFirstReg+(A)->nColumn+(I))
|
||||
|
||||
/*
|
||||
** The datatype ynVar is a signed integer, either 16-bit or 32-bit.
|
||||
** Usually it is 16-bits. But if SQLITE_MAX_VARIABLE_NUMBER is greater
|
||||
@@ -3656,7 +3688,7 @@ struct Parse {
|
||||
int nLabelAlloc; /* Number of slots in aLabel */
|
||||
int *aLabel; /* Space to hold the labels */
|
||||
ExprList *pConstExpr;/* Constant expressions */
|
||||
IndexedExpr *pIdxExpr;/* List of expressions used by active indexes */
|
||||
IndexedExpr *pIdxEpr;/* List of expressions used by active indexes */
|
||||
Token constraintName;/* Name of the constraint currently being parsed */
|
||||
yDbMask writeMask; /* Start a write transaction on these databases */
|
||||
yDbMask cookieMask; /* Bitmask of schema verified databases */
|
||||
@@ -5503,4 +5535,8 @@ const char **sqlite3CompileOptions(int *pnOpt);
|
||||
int sqlite3KvvfsInit(void);
|
||||
#endif
|
||||
|
||||
#if defined(VDBE_PROFILE) || defined(SQLITE_PERFORMANCE_TRACE)
|
||||
sqlite3_uint64 sqlite3Hwtime(void);
|
||||
#endif
|
||||
|
||||
#endif /* SQLITEINT_H */
|
||||
|
||||
@@ -7709,6 +7709,7 @@ static int SQLITE_TCLAPI tclLoadStaticExtensionCmd(
|
||||
){
|
||||
extern int sqlite3_amatch_init(sqlite3*,char**,const sqlite3_api_routines*);
|
||||
extern int sqlite3_appendvfs_init(sqlite3*,char**,const sqlite3_api_routines*);
|
||||
extern int sqlite3_basexx_init(sqlite3*,char**,const sqlite3_api_routines*);
|
||||
extern int sqlite3_carray_init(sqlite3*,char**,const sqlite3_api_routines*);
|
||||
extern int sqlite3_closure_init(sqlite3*,char**,const sqlite3_api_routines*);
|
||||
extern int sqlite3_csv_init(sqlite3*,char**,const sqlite3_api_routines*);
|
||||
@@ -7740,6 +7741,7 @@ static int SQLITE_TCLAPI tclLoadStaticExtensionCmd(
|
||||
} aExtension[] = {
|
||||
{ "amatch", sqlite3_amatch_init },
|
||||
{ "appendvfs", sqlite3_appendvfs_init },
|
||||
{ "basexx", sqlite3_basexx_init },
|
||||
{ "carray", sqlite3_carray_init },
|
||||
{ "closure", sqlite3_closure_init },
|
||||
{ "csv", sqlite3_csv_init },
|
||||
|
||||
+4
-1
@@ -487,7 +487,7 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){
|
||||
sqlite3TreeViewPop(&pView);
|
||||
return;
|
||||
}
|
||||
if( pExpr->flags || pExpr->affExpr || pExpr->vvaFlags ){
|
||||
if( pExpr->flags || pExpr->affExpr || pExpr->vvaFlags || pExpr->pAggInfo ){
|
||||
StrAccum x;
|
||||
sqlite3StrAccumInit(&x, 0, zFlgs, sizeof(zFlgs), 0);
|
||||
sqlite3_str_appendf(&x, " fg.af=%x.%c",
|
||||
@@ -504,6 +504,9 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){
|
||||
if( ExprHasVVAProperty(pExpr, EP_Immutable) ){
|
||||
sqlite3_str_appendf(&x, " IMMUTABLE");
|
||||
}
|
||||
if( pExpr->pAggInfo!=0 ){
|
||||
sqlite3_str_appendf(&x, " agg-column[%d]", pExpr->iAgg);
|
||||
}
|
||||
sqlite3StrAccumFinish(&x);
|
||||
}else{
|
||||
zFlgs[0] = 0;
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ Trigger *sqlite3TriggerList(Parse *pParse, Table *pTab){
|
||||
if( pTrig->pTabSchema==pTab->pSchema
|
||||
&& pTrig->table
|
||||
&& 0==sqlite3StrICmp(pTrig->table, pTab->zName)
|
||||
&& pTrig->pTabSchema!=pTmpSchema
|
||||
&& (pTrig->pTabSchema!=pTmpSchema || pTrig->bReturning)
|
||||
){
|
||||
pTrig->pNext = pList;
|
||||
pList = pTrig;
|
||||
|
||||
@@ -1713,3 +1713,10 @@ int sqlite3VListNameToNum(VList *pIn, const char *zName, int nName){
|
||||
}while( i<mx );
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** High-resolution hardware timer used for debugging and testing only.
|
||||
*/
|
||||
#if defined(VDBE_PROFILE) || defined(SQLITE_PERFORMANCE_TRACE)
|
||||
# include "hwtime.h"
|
||||
#endif
|
||||
|
||||
-10
@@ -618,16 +618,6 @@ void sqlite3VdbeRegisterDump(Vdbe *v){
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef VDBE_PROFILE
|
||||
|
||||
/*
|
||||
** hwtime.h contains inline assembler code for implementing
|
||||
** high-performance timing routines.
|
||||
*/
|
||||
#include "hwtime.h"
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef NDEBUG
|
||||
/*
|
||||
** This function is only called from within an assert() expression. It
|
||||
|
||||
+131
-52
@@ -3094,7 +3094,7 @@ static int whereLoopAddBtreeIndex(
|
||||
assert( pSrc->pTab->szTabRow>0 );
|
||||
rCostIdx = pNew->nOut + 1 + (15*pProbe->szIdxRow)/pSrc->pTab->szTabRow;
|
||||
pNew->rRun = sqlite3LogEstAdd(rLogSize, rCostIdx);
|
||||
if( (pNew->wsFlags & (WHERE_IDX_ONLY|WHERE_IPK))==0 ){
|
||||
if( (pNew->wsFlags & (WHERE_IDX_ONLY|WHERE_IPK|WHERE_EXPRIDX))==0 ){
|
||||
pNew->rRun = sqlite3LogEstAdd(pNew->rRun, pNew->nOut + 16);
|
||||
}
|
||||
ApplyCostMultiplier(pNew->rRun, pProbe->pTable->costMult);
|
||||
@@ -3247,16 +3247,39 @@ static int whereUsablePartialIndex(
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** pIdx is an index containing expressions. Check it see if any of the
|
||||
** expressions in the index match the pExpr expression.
|
||||
*/
|
||||
static int exprIsCoveredByIndex(
|
||||
const Expr *pExpr,
|
||||
const Index *pIdx,
|
||||
int iTabCur
|
||||
){
|
||||
int i;
|
||||
for(i=0; i<pIdx->nColumn; i++){
|
||||
if( pIdx->aiColumn[i]==XN_EXPR
|
||||
&& sqlite3ExprCompare(0, pExpr, pIdx->aColExpr->a[i].pExpr, iTabCur)==0
|
||||
){
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Structure passed to the whereIsCoveringIndex Walker callback.
|
||||
*/
|
||||
typedef struct CoveringIndexCheck CoveringIndexCheck;
|
||||
struct CoveringIndexCheck {
|
||||
Index *pIdx; /* The index */
|
||||
int iTabCur; /* Cursor number for the corresponding table */
|
||||
u8 bExpr; /* Uses an indexed expression */
|
||||
u8 bUnidx; /* Uses an unindexed column not within an indexed expr */
|
||||
};
|
||||
|
||||
/*
|
||||
** Information passed in is pWalk->u.pCovIdxCk. Call is pCk.
|
||||
** Information passed in is pWalk->u.pCovIdxCk. Call it pCk.
|
||||
**
|
||||
** If the Expr node references the table with cursor pCk->iTabCur, then
|
||||
** make sure that column is covered by the index pCk->pIdx. We know that
|
||||
@@ -3268,71 +3291,103 @@ struct CoveringIndexCheck {
|
||||
**
|
||||
** If this node does not disprove that the index can be a covering index,
|
||||
** then just return WRC_Continue, to continue the search.
|
||||
**
|
||||
** If pCk->pIdx contains indexed expressions and one of those expressions
|
||||
** matches pExpr, then prune the search.
|
||||
*/
|
||||
static int whereIsCoveringIndexWalkCallback(Walker *pWalk, Expr *pExpr){
|
||||
int i; /* Loop counter */
|
||||
const Index *pIdx; /* The index of interest */
|
||||
const i16 *aiColumn; /* Columns contained in the index */
|
||||
u16 nColumn; /* Number of columns in the index */
|
||||
if( pExpr->op!=TK_COLUMN && pExpr->op!=TK_AGG_COLUMN ) return WRC_Continue;
|
||||
if( pExpr->iColumn<(BMS-1) ) return WRC_Continue;
|
||||
if( pExpr->iTable!=pWalk->u.pCovIdxCk->iTabCur ) return WRC_Continue;
|
||||
pIdx = pWalk->u.pCovIdxCk->pIdx;
|
||||
aiColumn = pIdx->aiColumn;
|
||||
nColumn = pIdx->nColumn;
|
||||
for(i=0; i<nColumn; i++){
|
||||
if( aiColumn[i]==pExpr->iColumn ) return WRC_Continue;
|
||||
int i; /* Loop counter */
|
||||
const Index *pIdx; /* The index of interest */
|
||||
const i16 *aiColumn; /* Columns contained in the index */
|
||||
u16 nColumn; /* Number of columns in the index */
|
||||
CoveringIndexCheck *pCk; /* Info about this search */
|
||||
|
||||
pCk = pWalk->u.pCovIdxCk;
|
||||
pIdx = pCk->pIdx;
|
||||
if( (pExpr->op==TK_COLUMN || pExpr->op==TK_AGG_COLUMN) ){
|
||||
/* if( pExpr->iColumn<(BMS-1) && pIdx->bHasExpr==0 ) return WRC_Continue;*/
|
||||
if( pExpr->iTable!=pCk->iTabCur ) return WRC_Continue;
|
||||
pIdx = pWalk->u.pCovIdxCk->pIdx;
|
||||
aiColumn = pIdx->aiColumn;
|
||||
nColumn = pIdx->nColumn;
|
||||
for(i=0; i<nColumn; i++){
|
||||
if( aiColumn[i]==pExpr->iColumn ) return WRC_Continue;
|
||||
}
|
||||
pCk->bUnidx = 1;
|
||||
return WRC_Abort;
|
||||
}else if( pIdx->bHasExpr
|
||||
&& exprIsCoveredByIndex(pExpr, pIdx, pWalk->u.pCovIdxCk->iTabCur) ){
|
||||
pCk->bExpr = 1;
|
||||
return WRC_Prune;
|
||||
}
|
||||
pWalk->eCode = 1;
|
||||
return WRC_Abort;
|
||||
return WRC_Continue;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** pIdx is an index that covers all of the low-number columns used by
|
||||
** pWInfo->pSelect (columns from 0 through 62). But there are columns
|
||||
** in pWInfo->pSelect beyond 62. This routine tries to answer the question
|
||||
** of whether pIdx covers *all* columns in the query.
|
||||
** pWInfo->pSelect (columns from 0 through 62) or an index that has
|
||||
** expressions terms. Hence, we cannot determine whether or not it is
|
||||
** a covering index by using the colUsed bitmasks. We have to do a search
|
||||
** to see if the index is covering. This routine does that search.
|
||||
**
|
||||
** Return 0 if pIdx is a covering index. Return non-zero if pIdx is
|
||||
** not a covering index or if we are unable to determine if pIdx is a
|
||||
** covering index.
|
||||
** The return value is one of these:
|
||||
**
|
||||
** This routine is an optimization. It is always safe to return non-zero.
|
||||
** But returning zero when non-zero should have been returned can lead to
|
||||
** incorrect bytecode and assertion faults.
|
||||
** 0 The index is definitely not a covering index
|
||||
**
|
||||
** WHERE_IDX_ONLY The index is definitely a covering index
|
||||
**
|
||||
** WHERE_EXPRIDX The index is likely a covering index, but it is
|
||||
** difficult to determine precisely because of the
|
||||
** expressions that are indexed. Score it as a
|
||||
** covering index, but still keep the main table open
|
||||
** just in case we need it.
|
||||
**
|
||||
** This routine is an optimization. It is always safe to return zero.
|
||||
** But returning one of the other two values when zero should have been
|
||||
** returned can lead to incorrect bytecode and assertion faults.
|
||||
*/
|
||||
static SQLITE_NOINLINE u32 whereIsCoveringIndex(
|
||||
WhereInfo *pWInfo, /* The WHERE clause context */
|
||||
Index *pIdx, /* Index that is being tested */
|
||||
int iTabCur /* Cursor for the table being indexed */
|
||||
){
|
||||
int i;
|
||||
int i, rc;
|
||||
struct CoveringIndexCheck ck;
|
||||
Walker w;
|
||||
if( pWInfo->pSelect==0 ){
|
||||
/* We don't have access to the full query, so we cannot check to see
|
||||
** if pIdx is covering. Assume it is not. */
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
for(i=0; i<pIdx->nColumn; i++){
|
||||
if( pIdx->aiColumn[i]>=BMS-1 ) break;
|
||||
}
|
||||
if( i>=pIdx->nColumn ){
|
||||
/* pIdx does not index any columns greater than 62, but we know from
|
||||
** colMask that columns greater than 62 are used, so this is not a
|
||||
** covering index */
|
||||
return 1;
|
||||
if( pIdx->bHasExpr==0 ){
|
||||
for(i=0; i<pIdx->nColumn; i++){
|
||||
if( pIdx->aiColumn[i]>=BMS-1 ) break;
|
||||
}
|
||||
if( i>=pIdx->nColumn ){
|
||||
/* pIdx does not index any columns greater than 62, but we know from
|
||||
** colMask that columns greater than 62 are used, so this is not a
|
||||
** covering index */
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
ck.pIdx = pIdx;
|
||||
ck.iTabCur = iTabCur;
|
||||
ck.bExpr = 0;
|
||||
ck.bUnidx = 0;
|
||||
memset(&w, 0, sizeof(w));
|
||||
w.xExprCallback = whereIsCoveringIndexWalkCallback;
|
||||
w.xSelectCallback = sqlite3SelectWalkNoop;
|
||||
w.u.pCovIdxCk = &ck;
|
||||
w.eCode = 0;
|
||||
sqlite3WalkSelect(&w, pWInfo->pSelect);
|
||||
return w.eCode;
|
||||
if( ck.bUnidx ){
|
||||
rc = 0;
|
||||
}else if( ck.bExpr ){
|
||||
rc = WHERE_EXPRIDX;
|
||||
}else{
|
||||
rc = WHERE_IDX_ONLY;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -3548,14 +3603,38 @@ static int whereLoopAddBtree(
|
||||
}else{
|
||||
Bitmask m;
|
||||
if( pProbe->isCovering ){
|
||||
pNew->wsFlags = WHERE_IDX_ONLY | WHERE_INDEXED;
|
||||
m = 0;
|
||||
pNew->wsFlags = WHERE_IDX_ONLY | WHERE_INDEXED;
|
||||
}else{
|
||||
m = pSrc->colUsed & pProbe->colNotIdxed;
|
||||
if( m==TOPBIT ){
|
||||
m = whereIsCoveringIndex(pWInfo, pProbe, pSrc->iCursor);
|
||||
pNew->wsFlags = WHERE_INDEXED;
|
||||
if( m==TOPBIT || (pProbe->bHasExpr && !pProbe->bHasVCol && m!=0) ){
|
||||
u32 isCov = whereIsCoveringIndex(pWInfo, pProbe, pSrc->iCursor);
|
||||
if( isCov==0 ){
|
||||
WHERETRACE(0xff,
|
||||
("-> %s is not a covering index"
|
||||
" according to whereIsCoveringIndex()\n", pProbe->zName));
|
||||
assert( m!=0 );
|
||||
}else{
|
||||
m = 0;
|
||||
pNew->wsFlags |= isCov;
|
||||
if( isCov & WHERE_IDX_ONLY ){
|
||||
WHERETRACE(0xff,
|
||||
("-> %s is a covering expression index"
|
||||
" according to whereIsCoveringIndex()\n", pProbe->zName));
|
||||
}else{
|
||||
assert( isCov==WHERE_EXPRIDX );
|
||||
WHERETRACE(0xff,
|
||||
("-> %s might be a covering expression index"
|
||||
" according to whereIsCoveringIndex()\n", pProbe->zName));
|
||||
}
|
||||
}
|
||||
}else if( m==0 ){
|
||||
WHERETRACE(0xff,
|
||||
("-> %s a covering index according to bitmasks\n",
|
||||
pProbe->zName, m==0 ? "is" : "is not"));
|
||||
pNew->wsFlags = WHERE_IDX_ONLY | WHERE_INDEXED;
|
||||
}
|
||||
pNew->wsFlags = (m==0) ? (WHERE_IDX_ONLY|WHERE_INDEXED) : WHERE_INDEXED;
|
||||
}
|
||||
|
||||
/* Full scan via index */
|
||||
@@ -4591,8 +4670,8 @@ static i8 wherePathSatisfiesOrderBy(
|
||||
if( pOBExpr->iTable!=iCur ) continue;
|
||||
if( pOBExpr->iColumn!=iColumn ) continue;
|
||||
}else{
|
||||
Expr *pIdxExpr = pIndex->aColExpr->a[j].pExpr;
|
||||
if( sqlite3ExprCompareSkip(pOBExpr, pIdxExpr, iCur) ){
|
||||
Expr *pIxExpr = pIndex->aColExpr->a[j].pExpr;
|
||||
if( sqlite3ExprCompareSkip(pOBExpr, pIxExpr, iCur) ){
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -5461,13 +5540,13 @@ static SQLITE_NOINLINE void whereCheckIfBloomFilterIsUseful(
|
||||
|
||||
/*
|
||||
** This is an sqlite3ParserAddCleanup() callback that is invoked to
|
||||
** free the Parse->pIdxExpr list when the Parse object is destroyed.
|
||||
** free the Parse->pIdxEpr list when the Parse object is destroyed.
|
||||
*/
|
||||
static void whereIndexedExprCleanup(sqlite3 *db, void *pObject){
|
||||
Parse *pParse = (Parse*)pObject;
|
||||
while( pParse->pIdxExpr!=0 ){
|
||||
IndexedExpr *p = pParse->pIdxExpr;
|
||||
pParse->pIdxExpr = p->pIENext;
|
||||
while( pParse->pIdxEpr!=0 ){
|
||||
IndexedExpr *p = pParse->pIdxEpr;
|
||||
pParse->pIdxEpr = p->pIENext;
|
||||
sqlite3ExprDelete(db, p->pExpr);
|
||||
sqlite3DbFreeNN(db, p);
|
||||
}
|
||||
@@ -5479,13 +5558,13 @@ static void whereIndexedExprCleanup(sqlite3 *db, void *pObject){
|
||||
** number for the index and iDataCur is the cursor number for the corresponding
|
||||
** table.
|
||||
**
|
||||
** This routine adds IndexedExpr entries to the Parse->pIdxExpr field for
|
||||
** This routine adds IndexedExpr entries to the Parse->pIdxEpr field for
|
||||
** each of the expressions in the index so that the expression code generator
|
||||
** will know to replace occurrences of the indexed expression with
|
||||
** references to the corresponding column of the index.
|
||||
*/
|
||||
static SQLITE_NOINLINE void whereAddIndexedExpr(
|
||||
Parse *pParse, /* Add IndexedExpr entries to pParse->pIdxExpr */
|
||||
Parse *pParse, /* Add IndexedExpr entries to pParse->pIdxEpr */
|
||||
Index *pIdx, /* The index-on-expression that contains the expressions */
|
||||
int iIdxCur, /* Cursor number for pIdx */
|
||||
SrcItem *pTabItem /* The FROM clause entry for the table */
|
||||
@@ -5514,7 +5593,7 @@ static SQLITE_NOINLINE void whereAddIndexedExpr(
|
||||
if( sqlite3ExprIsConstant(pExpr) ) continue;
|
||||
p = sqlite3DbMallocRaw(pParse->db, sizeof(IndexedExpr));
|
||||
if( p==0 ) break;
|
||||
p->pIENext = pParse->pIdxExpr;
|
||||
p->pIENext = pParse->pIdxEpr;
|
||||
p->pExpr = sqlite3ExprDup(pParse->db, pExpr, 0);
|
||||
p->iDataCur = pTabItem->iCursor;
|
||||
p->iIdxCur = iIdxCur;
|
||||
@@ -5523,7 +5602,7 @@ static SQLITE_NOINLINE void whereAddIndexedExpr(
|
||||
#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
|
||||
p->zIdxName = pIdx->zName;
|
||||
#endif
|
||||
pParse->pIdxExpr = p;
|
||||
pParse->pIdxEpr = p;
|
||||
if( p->pIENext==0 ){
|
||||
sqlite3ParserAddCleanup(pParse, whereIndexedExprCleanup, pParse);
|
||||
}
|
||||
@@ -6474,7 +6553,7 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
|
||||
last = pWInfo->iEndWhere;
|
||||
}
|
||||
if( pIdx->bHasExpr ){
|
||||
IndexedExpr *p = pParse->pIdxExpr;
|
||||
IndexedExpr *p = pParse->pIdxEpr;
|
||||
while( p ){
|
||||
if( p->iIdxCur==pLevel->iIdxCur ){
|
||||
p->iDataCur = -1;
|
||||
|
||||
@@ -634,5 +634,6 @@ void sqlite3WhereTabFuncArgs(Parse*, SrcItem*, WhereClause*);
|
||||
#define WHERE_SELFCULL 0x00800000 /* nOut reduced by extra WHERE terms */
|
||||
#define WHERE_OMIT_OFFSET 0x01000000 /* Set offset counter to zero */
|
||||
#define WHERE_VIEWSCAN 0x02000000 /* A full-scan of a VIEW or subquery */
|
||||
#define WHERE_EXPRIDX 0x04000000 /* Uses an index-on-expressions */
|
||||
|
||||
#endif /* !defined(SQLITE_WHEREINT_H) */
|
||||
|
||||
+1
-1
@@ -1069,7 +1069,7 @@ int sqlite3WindowRewrite(Parse *pParse, Select *p){
|
||||
pSub = sqlite3SelectNew(
|
||||
pParse, pSublist, pSrc, pWhere, pGroupBy, pHaving, pSort, 0, 0
|
||||
);
|
||||
SELECTTRACE(1,pParse,pSub,
|
||||
TREETRACE(0x40,pParse,pSub,
|
||||
("New window-function subquery in FROM clause of (%u/%p)\n",
|
||||
p->selId, p));
|
||||
p->pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0);
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
# 2022 November 22
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
set testprefix basexx
|
||||
|
||||
if {[catch {load_static_extension db basexx} error]} {
|
||||
puts "Skipping basexx tests, hit load error: $error"
|
||||
finish_test; return
|
||||
}
|
||||
|
||||
# Empty blobs encode to empty strings.
|
||||
do_execsql_test 100 {
|
||||
SELECT base64(x'')||base85(x'');
|
||||
} {{}}
|
||||
|
||||
# Empty strings decode to empty blobs.
|
||||
do_execsql_test 101 {
|
||||
SELECT hex(x'01'||base64('')||base85('')||x'02');
|
||||
} {0102}
|
||||
|
||||
# Basic base64 encoding
|
||||
do_execsql_test 102 {
|
||||
SELECT base64(x'000102030405');
|
||||
SELECT base64(x'0001020304');
|
||||
SELECT base64(x'00010203');
|
||||
} {{AAECAwQF
|
||||
} {AAECAwQ=
|
||||
} {AAECAw==
|
||||
}}
|
||||
|
||||
# Basic base64 decoding with pad chars
|
||||
do_execsql_test 103 {
|
||||
SELECT hex(base64('AAECAwQF'));
|
||||
SELECT hex(base64('AAECAwQ='));
|
||||
SELECT hex(base64('AAECAw=='));
|
||||
} {000102030405 0001020304 00010203}
|
||||
|
||||
# Basic base64 decoding without pad chars and with whitespace
|
||||
do_execsql_test 104 {
|
||||
SELECT hex(base64(' AAECAwQF '));
|
||||
SELECT hex(base64(' AAECAwQ'));
|
||||
SELECT hex(base64('AAECAw '));
|
||||
} {000102030405 0001020304 00010203}
|
||||
|
||||
# Basic base85 encoding
|
||||
do_execsql_test 105 {
|
||||
SELECT base85(x'000102030405');
|
||||
SELECT base85(x'0001020304');
|
||||
SELECT base85(x'00010203');
|
||||
} {{##/2,#2/
|
||||
} {##/2,#*
|
||||
} {##/2,
|
||||
}}
|
||||
|
||||
# Basic base85 decoding with and without whitespace
|
||||
do_execsql_test 106 {
|
||||
SELECT hex(base85('##/2,#2/'));
|
||||
SELECT hex(base85('##/2,#*'));
|
||||
SELECT hex(base85('##/2,'));
|
||||
SELECT hex(base85(' ##/2,#2/ '));
|
||||
SELECT hex(base85(' ##/2,#*'));
|
||||
SELECT hex(base85('##/2, '));
|
||||
} {000102030405 0001020304 00010203 000102030405 0001020304 00010203}
|
||||
|
||||
# Round-trip some random blobs.
|
||||
do_execsql_test 107 {
|
||||
CREATE TEMP TABLE rb( len int, b blob ) STRICT;
|
||||
INSERT INTO rb(len) VALUES (1),(2),(3),(4),(5),(150),(151),(152),(153),(1054);
|
||||
UPDATE rb SET b = randomblob(len);
|
||||
SELECT len, base64(base64(b))=b, base85(base85(b))=b
|
||||
FROM rb ORDER BY len;
|
||||
} {1 1 1 2 1 1 3 1 1 4 1 1 5 1 1 150 1 1 151 1 1 152 1 1 153 1 1 1054 1 1}
|
||||
|
||||
# Same round-trip but with space or junk prepended and/or appended or not.
|
||||
do_execsql_test 108 {
|
||||
CREATE TEMP TABLE junk(j text, rank int);
|
||||
INSERT INTO junk VALUES ('',0),(' ',1),('~',2);
|
||||
SELECT len, base64(j.j||base64(b)||j.j)=b, base85(j.j||base85(b)||j.j)=b
|
||||
FROM rb r, junk j WHERE j.rank=(r.len+r.len/25)%3 ORDER BY len;
|
||||
} {1 1 1 2 1 1 3 1 1 4 1 1 5 1 1 150 1 1 151 1 1 152 1 1 153 1 1 1054 1 1}
|
||||
|
||||
# Exercise the fail-on-too-large result feature.
|
||||
|
||||
set inLimit [sqlite3_limit db SQLITE_LIMIT_LENGTH -1]
|
||||
sqlite3_limit db SQLITE_LIMIT_LENGTH 1300
|
||||
|
||||
do_catchsql_test 109 {
|
||||
SELECT len, base64(b) FROM rb WHERE len>200;
|
||||
} {1 {blob expanded to base64 too big}}
|
||||
|
||||
do_catchsql_test 110 {
|
||||
SELECT len, base85(b) FROM rb WHERE len>200;
|
||||
} {1 {blob expanded to base85 too big}}
|
||||
|
||||
do_catchsql_test 111 {
|
||||
SELECT length(base85(b))=1335 FROM rb WHERE len=1054;
|
||||
} {1 {blob expanded to base85 too big}}
|
||||
|
||||
sqlite3_limit db SQLITE_LIMIT_LENGTH $inLimit
|
||||
|
||||
# Exercise is_base85(t)
|
||||
|
||||
do_execsql_test 112 {
|
||||
SELECT is_base85(' '||base85(x'123456')||char(10)),
|
||||
is_base85('#$%&*+,-./0123456789:;<=>?@'
|
||||
||'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
||'[\]^_`'
|
||||
||'abcdefghijklmnopqrstuvwxyz'),
|
||||
is_base85('!'), is_base85('"'), is_base85(''''), is_base85('('),
|
||||
is_base85(')'), is_base85(char(123)), is_base85('|'), is_base85(char(125)),
|
||||
is_base85('~'), is_base85(char(127));
|
||||
} {1 1 0 0 0 0 0 0 0 0 0 0}
|
||||
|
||||
do_execsql_test 113 {
|
||||
SELECT is_base85(NULL) IS NULL;
|
||||
} {1}
|
||||
|
||||
do_catchsql_test 114 {
|
||||
SELECT is_base85(1);
|
||||
} {1 {is_base85 accepts only text or NULL}}
|
||||
|
||||
do_catchsql_test 115 {
|
||||
SELECT is_base85(1.1);
|
||||
} {1 {is_base85 accepts only text or NULL}}
|
||||
|
||||
do_catchsql_test 116 {
|
||||
SELECT is_base85(x'00');
|
||||
} {1 {is_base85 accepts only text or NULL}}
|
||||
|
||||
# Round-trip many bigger random blobs.
|
||||
|
||||
do_execsql_test 117 {
|
||||
CREATE TABLE bs(b blob, num);
|
||||
INSERT INTO bs SELECT randomblob(4000 + n%3), n
|
||||
FROM (
|
||||
WITH RECURSIVE seq(n) AS (
|
||||
VALUES(1) UNION ALL SELECT n+1
|
||||
FROM seq WHERE n<100
|
||||
) SELECT n FROM seq);
|
||||
SELECT num FROM bs WHERE base64(base64(b))!=b;
|
||||
SELECT num FROM bs WHERE base85(base85(b))!=b;
|
||||
} {}
|
||||
|
||||
finish_test
|
||||
+10
-1
@@ -50,7 +50,16 @@ do_icu_expr_test 1.6 { "(x OR y)" } {PHRASE 3 0 ( x or y )}
|
||||
# is passed to the tokenizer.
|
||||
#
|
||||
do_icu_expr_test 1.7 {a:word} {PHRASE 0 0 word}
|
||||
do_icu_expr_test 1.8 {d:word} {PHRASE 3 0 d:word}
|
||||
# do_icu_expr_test 1.8 {d:word} {PHRASE 3 0 d:word}
|
||||
do_test 1.8 {
|
||||
set res [
|
||||
db one {SELECT fts3_exprtest('icu en_US', 'd:word', 'a', 'b', 'c')}
|
||||
]
|
||||
expr {
|
||||
$res=="PHRASE 3 0 d:word" ||
|
||||
$res=="AND {AND {PHRASE 3 0 d} {PHRASE 3 0 :}} {PHRASE 3 0 word}"
|
||||
}
|
||||
} 1
|
||||
|
||||
set sqlite_fts3_enable_parentheses 0
|
||||
|
||||
|
||||
+72
-11
@@ -420,7 +420,7 @@ do_execsql_test indexexpr1-1500 {
|
||||
# 2018-01-03 OSSFuzz discovers another test case for the same problem
|
||||
# above.
|
||||
#
|
||||
do_execsql_test indexexpr-1510 {
|
||||
do_execsql_test indexexpr1-1510 {
|
||||
DROP TABLE IF EXISTS t1;
|
||||
CREATE TABLE t1(a PRIMARY KEY,b UNIQUE);
|
||||
REPLACE INTO t1 VALUES(2, 1);
|
||||
@@ -434,18 +434,18 @@ do_execsql_test indexexpr-1510 {
|
||||
# a numeric table column, trouble can arise since there are multiple
|
||||
# string that can map to the same numeric value. (Ex: 123, 0123, 000123).
|
||||
#
|
||||
do_execsql_test indexexpr-1600 {
|
||||
do_execsql_test indexexpr1-1600 {
|
||||
DROP TABLE IF EXISTS t1;
|
||||
CREATE TABLE t1 (a INTEGER, b);
|
||||
CREATE INDEX idx1 ON t1 (lower(a));
|
||||
INSERT INTO t1 VALUES('0001234',3);
|
||||
PRAGMA integrity_check;
|
||||
} {ok}
|
||||
do_execsql_test indexexpr-1610 {
|
||||
do_execsql_test indexexpr1-1610 {
|
||||
INSERT INTO t1 VALUES('1234',0),('001234',2),('01234',1);
|
||||
SELECT b FROM t1 WHERE lower(a)='1234' ORDER BY +b;
|
||||
} {0 1 2 3}
|
||||
do_execsql_test indexexpr-1620 {
|
||||
do_execsql_test indexexpr1-1620 {
|
||||
SELECT b FROM t1 WHERE lower(a)='01234' ORDER BY +b;
|
||||
} {}
|
||||
|
||||
@@ -453,7 +453,7 @@ do_execsql_test indexexpr-1620 {
|
||||
# ExprImpliesExpr theorem prover bug:
|
||||
# "(NULL IS FALSE) IS FALSE" does not imply "NULL IS NULL"
|
||||
#
|
||||
do_execsql_test indexexpr-1700 {
|
||||
do_execsql_test indexexpr1-1700 {
|
||||
DROP TABLE IF EXISTS t0;
|
||||
CREATE TABLE t0(c0);
|
||||
INSERT INTO t0(c0) VALUES (0);
|
||||
@@ -468,17 +468,17 @@ do_execsql_test indexexpr-1700 {
|
||||
# computing the expression.
|
||||
#
|
||||
ifcapable like_match_blobs {
|
||||
do_execsql_test indexexpr-1800 {
|
||||
do_execsql_test indexexpr1-1800 {
|
||||
DROP TABLE IF EXISTS t0;
|
||||
CREATE TABLE t0(c0 REAL, c1 TEXT);
|
||||
CREATE INDEX i0 ON t0(+c0, c0);
|
||||
INSERT INTO t0(c0) VALUES(0);
|
||||
SELECT CAST(+ t0.c0 AS BLOB) LIKE 0 FROM t0;
|
||||
} {0}
|
||||
do_execsql_test indexexpr-1810 {
|
||||
do_execsql_test indexexpr1-1810 {
|
||||
SELECT CAST(+ t0.c0 AS BLOB) LIKE '0.0' FROM t0;
|
||||
} {1}
|
||||
do_execsql_test indexexpr-1820 {
|
||||
do_execsql_test indexexpr1-1820 {
|
||||
DROP TABLE IF EXISTS t1;
|
||||
CREATE TABLE t1(x REAL);
|
||||
CREATE INDEX t1x ON t1(x, +x);
|
||||
@@ -491,19 +491,80 @@ ifcapable like_match_blobs {
|
||||
# Assertion fault during a DELETE INDEXED BY.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test indexexpr-1900 {
|
||||
do_execsql_test indexexpr1-1900 {
|
||||
CREATE TABLE t1(x TEXT PRIMARY KEY, y TEXT, z INT);
|
||||
INSERT INTO t1(x,y,z) VALUES('alpha','ALPHA',1),('bravo','charlie',1);
|
||||
CREATE INDEX i1 ON t1(+y COLLATE NOCASE);
|
||||
SELECT * FROM t1;
|
||||
} {alpha ALPHA 1 bravo charlie 1}
|
||||
do_execsql_test indexexpr-1910 {
|
||||
do_execsql_test indexexpr1-1910 {
|
||||
DELETE FROM t1 INDEXED BY i1
|
||||
WHERE x IS +y COLLATE NOCASE IN (SELECT z FROM t1)
|
||||
RETURNING *;
|
||||
} {alpha ALPHA 1}
|
||||
do_execsql_test indexexpr-1920 {
|
||||
do_execsql_test indexexpr1-1920 {
|
||||
SELECT * FROM t1;
|
||||
} {bravo charlie 1}
|
||||
|
||||
# 2022-11-28 Ticket 695a1a53de
|
||||
# Improved ability to recognize that an index on an expression is a
|
||||
# covering index.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test indexexpr1-2000 {
|
||||
CREATE TABLE t1(a INT, b TEXT);
|
||||
INSERT INTO t1(a,b) VALUES
|
||||
(10, '{"one":5,"two":6}'),
|
||||
(10, '{"one":50,"two":60}'),
|
||||
(10, '{"three":99}'),
|
||||
(11, '{"one":100,"two":200}');
|
||||
CREATE INDEX t1_one ON t1(a, b->>'one');
|
||||
CREATE INDEX t1_two ON t1(a, b->>'two');
|
||||
}
|
||||
do_execsql_test indexexpr1-2010 {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT sum(b->>'one') FROM t1 WHERE a=10; /* Query AA */
|
||||
} {/.* t1_one .*/}
|
||||
do_execsql_test indexexpr1-2011 {
|
||||
SELECT sum(b->>'one') FROM t1 WHERE a=10; /* Query AA */
|
||||
} {55}
|
||||
do_execsql_test indexexpr1-2020 {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT sum(b->>'two') FROM t1 WHERE a=10; /* Query BB */
|
||||
} {/.* t1_two .*/}
|
||||
do_execsql_test indexexpr1-2021 {
|
||||
SELECT sum(b->>'two') FROM t1 WHERE a=10; /* Query BB */
|
||||
} {66}
|
||||
do_execsql_test indexexpr1-2030 {
|
||||
DROP TABLE t1;
|
||||
CREATE TABLE t1(a INT, b TEXT, c INT, d INT);
|
||||
INSERT INTO t1(a,b,c,d) VALUES
|
||||
(1, '{"x":1}', 12, 3),
|
||||
(1, '{"x":2}', 4, 5),
|
||||
(1, '{"x":1}', 6, 11),
|
||||
(2, '{"x":1}', 22, 3),
|
||||
(2, '{"x":2}', 4, 5),
|
||||
(3, '{"x":1}', 6, 7);
|
||||
CREATE INDEX t1x ON t1(d, a, b->>'x', c);
|
||||
}
|
||||
do_execsql_test indexexpr1-2030 {
|
||||
SELECT a,
|
||||
SUM(1) AS t1,
|
||||
SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2,
|
||||
SUM(c) AS t3,
|
||||
SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4
|
||||
FROM t1;
|
||||
} {1 6 4 54 46}
|
||||
do_execsql_test indexexpr1-2030 {
|
||||
explain query plan
|
||||
SELECT a,
|
||||
SUM(1) AS t1,
|
||||
SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2,
|
||||
SUM(c) AS t3,
|
||||
SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4
|
||||
FROM t1;
|
||||
} {/.*SCAN t1 USING INDEX t1x.*/}
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -82,6 +82,8 @@ do_faultsim_test 1 -prep {
|
||||
multiplex_restore_db
|
||||
sqlite3 db file:test.db?8_3_names=1
|
||||
sqlite3_multiplex_control db main chunk_size [expr 256*1024]
|
||||
execsql { PRAGMA journal_mode = truncate }
|
||||
execsql { PRAGMA synchronous = off }
|
||||
} -body {
|
||||
execsql {
|
||||
UPDATE t1 SET a=randomblob(12), b=randomblob(1500) WHERE (rowid%32)=0
|
||||
|
||||
+26
-1
@@ -86,6 +86,31 @@ do_test 2.2 {
|
||||
set L
|
||||
} {three}
|
||||
|
||||
|
||||
# 2022-11-25 dbsqlfuzz crash-3a548de406a50e896c1bf7142692d35d339d697f
|
||||
# Disable the push-down optimization for compound subqueries if any
|
||||
# arm of the compound has an incompatible affinity.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 3.1 {
|
||||
CREATE TABLE t0(c0 INT);
|
||||
INSERT INTO t0 VALUES(0);
|
||||
CREATE TABLE t1_a(a INTEGER PRIMARY KEY, b TEXT);
|
||||
INSERT INTO t1_a VALUES(1,'one'); --,(4,'four');
|
||||
CREATE TABLE t1_b(c INTEGER PRIMARY KEY, d TEXT);
|
||||
INSERT INTO t1_b VALUES(2,'two'); --,(5,'five');
|
||||
CREATE VIEW v0 AS SELECT CAST(t0.c0 AS INTEGER) AS c0 FROM t0;
|
||||
CREATE VIEW t1 AS SELECT a, b FROM t1_a UNION ALL SELECT c, 0 FROM t1_b;
|
||||
SELECT t1.a, quote(t1.b), t0.c0 AS cd FROM t0 LEFT JOIN v0 ON v0.c0!=0,t1;
|
||||
} {
|
||||
1 'one' 0
|
||||
2 '0' 0
|
||||
}
|
||||
do_execsql_test 3.2 {
|
||||
SELECT a, quote(b), cd FROM (
|
||||
SELECT t1.a, t1.b, t0.c0 AS cd FROM t0 LEFT JOIN v0 ON v0.c0!=0,t1
|
||||
) WHERE a=2 AND b='0' AND cd=0;
|
||||
} {
|
||||
2 '0' 0
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -376,4 +376,28 @@ do_execsql_test 16.1 {
|
||||
SELECT * FROM t2;
|
||||
} {1 2 3 a b c}
|
||||
|
||||
|
||||
foreach {tn temp} {
|
||||
1 ""
|
||||
2 TEMP
|
||||
} {
|
||||
reset_db
|
||||
do_execsql_test 17.$tn.0 "
|
||||
CREATE $temp TABLE foo (
|
||||
fooid INTEGER PRIMARY KEY,
|
||||
fooval INTEGER NOT NULL UNIQUE,
|
||||
refcnt INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
"
|
||||
do_execsql_test 17.$tn.1 {
|
||||
INSERT INTO foo (fooval) VALUES (17), (4711), (17)
|
||||
ON CONFLICT DO
|
||||
UPDATE SET refcnt = refcnt+1
|
||||
RETURNING fooid;
|
||||
} {
|
||||
1 2 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -125,6 +125,12 @@ SELECT * FROM t1;}}
|
||||
do_test shell4-2.5 {
|
||||
catchcmd ":memory:" "CREATE TABLE t1(x);\n.trace stdout\nSELECT * FROM t1;"
|
||||
} {0 {SELECT * FROM t1;}}
|
||||
do_test shell4-2.6 {
|
||||
catchcmd ":memory:" {
|
||||
CREATE TABLE t1(x);
|
||||
.trace --stmt stdout
|
||||
SELECT * FROM t1;}
|
||||
} {0 {SELECT * FROM t1;}}
|
||||
}
|
||||
|
||||
do_test shell4-3.1 {
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
# 2022-11-23
|
||||
#
|
||||
# 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 regression tests for SQLite library.
|
||||
#
|
||||
# This file implements tests to verify that the enhancement
|
||||
# request documented by ticket 99378177930f87bd is working.
|
||||
#
|
||||
# The enhancement is that if an aggregate query with a GROUP BY clause
|
||||
# uses subexpressions in the arguments to aggregate functions that are
|
||||
# also columns of an index, then the values are pulled from the index
|
||||
# rather than being recomputed. This has the potential to make some
|
||||
# indexed queries works as if the index were covering.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
|
||||
do_execsql_test tkt-99378-100 {
|
||||
CREATE TABLE t1(a INT, b TEXT, c INT, d INT);
|
||||
INSERT INTO t1(a,b,c,d) VALUES
|
||||
(1, '{"x":1}', 12, 3),
|
||||
(1, '{"x":2}', 4, 5),
|
||||
(1, '{"x":1}', 6, 11),
|
||||
(2, '{"x":1}', 22, 3),
|
||||
(2, '{"x":2}', 4, 5),
|
||||
(3, '{"x":1}', 6, 7);
|
||||
CREATE INDEX t1x ON t1(d, a, b->>'x', c);
|
||||
} {}
|
||||
do_execsql_test tkt-99378-110 {
|
||||
SELECT a,
|
||||
SUM(1) AS t1,
|
||||
SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2,
|
||||
SUM(c) AS t3,
|
||||
SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4
|
||||
FROM t1
|
||||
WHERE d BETWEEN 0 and 10
|
||||
GROUP BY a;
|
||||
} {
|
||||
1 2 1 16 12
|
||||
2 2 1 26 22
|
||||
3 1 1 6 6
|
||||
}
|
||||
|
||||
# The proof that the index on the expression is being used is in the
|
||||
# fact that the byte code contains no "Function" opcodes. In other words,
|
||||
# the ->> operator (which is implemented by a function) is never invoked.
|
||||
# Instead, the b->>'x' value is pulled out of the index.
|
||||
#
|
||||
do_execsql_test tkt-99378-120 {
|
||||
EXPLAIN
|
||||
SELECT a,
|
||||
SUM(1) AS t1,
|
||||
SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2,
|
||||
SUM(c) AS t3,
|
||||
SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4
|
||||
FROM t1
|
||||
WHERE d BETWEEN 0 and 10
|
||||
GROUP BY a;
|
||||
} {~/Function/}
|
||||
|
||||
|
||||
do_execsql_test tkt-99378-130 {
|
||||
SELECT a,
|
||||
SUM(1) AS t1,
|
||||
SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2,
|
||||
SUM(c) AS t3,
|
||||
SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4
|
||||
FROM t1
|
||||
WHERE d BETWEEN 0 and 10
|
||||
GROUP BY +a;
|
||||
} {
|
||||
1 2 1 16 12
|
||||
2 2 1 26 22
|
||||
3 1 1 6 6
|
||||
}
|
||||
do_execsql_test tkt-99378-140 {
|
||||
EXPLAIN
|
||||
SELECT a,
|
||||
SUM(1) AS t1,
|
||||
SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2,
|
||||
SUM(c) AS t3,
|
||||
SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4
|
||||
FROM t1
|
||||
WHERE d BETWEEN 0 and 10
|
||||
GROUP BY +a;
|
||||
} {~/Function/}
|
||||
|
||||
do_execsql_test tkt-99378-200 {
|
||||
DROP INDEX t1x;
|
||||
CREATE INDEX t1x ON t1(a, d, b->>'x', c);
|
||||
}
|
||||
do_execsql_test tkt-99378-210 {
|
||||
SELECT a,
|
||||
SUM(1) AS t1,
|
||||
SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2,
|
||||
SUM(c) AS t3,
|
||||
SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4
|
||||
FROM t1
|
||||
WHERE d BETWEEN 0 and 10
|
||||
GROUP BY a;
|
||||
} {
|
||||
1 2 1 16 12
|
||||
2 2 1 26 22
|
||||
3 1 1 6 6
|
||||
}
|
||||
do_execsql_test tkt-99378-220 {
|
||||
EXPLAIN
|
||||
SELECT a,
|
||||
SUM(1) AS t1,
|
||||
SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2,
|
||||
SUM(c) AS t3,
|
||||
SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4
|
||||
FROM t1
|
||||
WHERE d BETWEEN 0 and 10
|
||||
GROUP BY a;
|
||||
} {~/Function/}
|
||||
do_execsql_test tkt-99378-230 {
|
||||
SELECT a,
|
||||
SUM(1) AS t1,
|
||||
SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2,
|
||||
SUM(c) AS t3,
|
||||
SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4
|
||||
FROM t1
|
||||
WHERE d BETWEEN 0 and 10
|
||||
GROUP BY a;
|
||||
} {
|
||||
1 2 1 16 12
|
||||
2 2 1 26 22
|
||||
3 1 1 6 6
|
||||
}
|
||||
do_execsql_test tkt-99378-240 {
|
||||
EXPLAIN
|
||||
SELECT a,
|
||||
SUM(1) AS t1,
|
||||
SUM(CASE WHEN b->>'x'=1 THEN 1 END) AS t2,
|
||||
SUM(c) AS t3,
|
||||
SUM(CASE WHEN b->>'x'=1 THEN c END) AS t4
|
||||
FROM t1
|
||||
WHERE d BETWEEN 0 and 10
|
||||
GROUP BY a;
|
||||
} {~/Function/}
|
||||
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
+14
-4
@@ -40,11 +40,14 @@ set help {Usage: tclsh mksqlite3c.tcl <options>
|
||||
set addstatic 1
|
||||
set linemacros 0
|
||||
set useapicall 0
|
||||
set enable_recover 0
|
||||
set srcdir tsrc
|
||||
|
||||
for {set i 0} {$i<[llength $argv]} {incr i} {
|
||||
set x [lindex $argv $i]
|
||||
if {[regexp {^-?-nostatic$} $x]} {
|
||||
if {[regexp {^-?-enable-recover$} $x]} {
|
||||
set enable_recover 1
|
||||
} elseif {[regexp {^-?-nostatic$} $x]} {
|
||||
set addstatic 0
|
||||
} elseif {[regexp {^-?-linemacros(?:=([01]))?$} $x ma ulm]} {
|
||||
if {$ulm == ""} {set ulm 1}
|
||||
@@ -78,7 +81,9 @@ close $in
|
||||
# Open the output file and write a header comment at the beginning
|
||||
# of the file.
|
||||
#
|
||||
set out [open sqlite3.c w]
|
||||
set fname sqlite3.c
|
||||
if {$enable_recover} { set fname sqlite3r.c }
|
||||
set out [open $fname w]
|
||||
# Force the output to use unix line endings, even on Windows.
|
||||
fconfigure $out -translation lf
|
||||
set today [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S UTC" -gmt 1]
|
||||
@@ -162,6 +167,7 @@ foreach hdr {
|
||||
vxworks.h
|
||||
wal.h
|
||||
whereInt.h
|
||||
sqlite3recover.h
|
||||
} {
|
||||
set available_hdr($hdr) 1
|
||||
}
|
||||
@@ -325,7 +331,7 @@ proc copy_file {filename} {
|
||||
# used subroutines first in order to help the compiler find
|
||||
# inlining opportunities.
|
||||
#
|
||||
foreach file {
|
||||
set flist {
|
||||
sqliteInt.h
|
||||
os_common.h
|
||||
ctime.c
|
||||
@@ -441,7 +447,11 @@ foreach file {
|
||||
sqlite3session.c
|
||||
fts5.c
|
||||
stmt.c
|
||||
} {
|
||||
}
|
||||
if {$enable_recover} {
|
||||
lappend flist sqlite3recover.c dbdata.c
|
||||
}
|
||||
foreach file $flist {
|
||||
copy_file $srcdir/$file
|
||||
}
|
||||
|
||||
|
||||
@@ -40,9 +40,16 @@ set TOP [lindex $argv 0]
|
||||
#
|
||||
set useapicall 0
|
||||
|
||||
# Include sqlite3recover.h?
|
||||
#
|
||||
set enable_recover 0
|
||||
|
||||
if {[lsearch -regexp [lrange $argv 1 end] {^-+useapicall}] != -1} {
|
||||
set useapicall 1
|
||||
}
|
||||
if {[lsearch -regexp [lrange $argv 1 end] {^-+enable-recover}] != -1} {
|
||||
set enable_recover 1
|
||||
}
|
||||
|
||||
# Get the SQLite version number (ex: 3.6.18) from the $TOP/VERSION file.
|
||||
#
|
||||
@@ -84,6 +91,9 @@ set filelist [subst {
|
||||
$TOP/ext/session/sqlite3session.h
|
||||
$TOP/ext/fts5/fts5.h
|
||||
}]
|
||||
if {$enable_recover} {
|
||||
lappend filelist "$TOP/ext/recover/sqlite3recover.h"
|
||||
}
|
||||
|
||||
# These are the functions that accept a variable number of arguments. They
|
||||
# always need to use the "cdecl" calling convention even when another calling
|
||||
|
||||
Reference in New Issue
Block a user