Merge sqlite-release(3.15.2) into prerelease-integration
This commit is contained in:
commit
bae72ae4e3
73
Makefile.in
73
Makefile.in
@ -31,7 +31,8 @@ BCC = @BUILD_CC@ @BUILD_CFLAGS@
|
||||
#
|
||||
CC = @CC@
|
||||
CFLAGS = @CPPFLAGS@ @CFLAGS@
|
||||
TCC = $(CC) $(CFLAGS) -I. -I${TOP}/src -I${TOP}/ext/rtree -I${TOP}/ext/fts3
|
||||
TCC = ${CC} ${CFLAGS} -I. -I${TOP}/src -I${TOP}/ext/rtree -I${TOP}/ext/icu
|
||||
TCC += -I${TOP}/ext/fts3 -I${TOP}/ext/async -I${TOP}/ext/session
|
||||
|
||||
# Define this for the autoconf-based build, so that the code knows it can
|
||||
# include the generated config.h
|
||||
@ -192,13 +193,14 @@ LIBOBJS0 = alter.lo analyze.lo attach.lo auth.lo \
|
||||
fts3_unicode.lo fts3_unicode2.lo fts3_write.lo \
|
||||
fts5.lo \
|
||||
func.lo global.lo hash.lo \
|
||||
icu.lo insert.lo journal.lo json1.lo legacy.lo loadext.lo \
|
||||
icu.lo insert.lo json1.lo legacy.lo loadext.lo \
|
||||
main.lo malloc.lo mem0.lo mem1.lo mem2.lo mem3.lo mem5.lo \
|
||||
memjournal.lo \
|
||||
mutex.lo mutex_noop.lo mutex_unix.lo mutex_w32.lo \
|
||||
notify.lo opcodes.lo os.lo os_unix.lo os_win.lo \
|
||||
pager.lo parse.lo pcache.lo pcache1.lo pragma.lo prepare.lo printf.lo \
|
||||
random.lo resolve.lo rowset.lo rtree.lo select.lo sqlite3rbu.lo status.lo \
|
||||
random.lo resolve.lo rowset.lo rtree.lo \
|
||||
sqlite3session.lo select.lo sqlite3rbu.lo status.lo \
|
||||
table.lo threads.lo tokenize.lo treeview.lo trigger.lo \
|
||||
update.lo util.lo vacuum.lo \
|
||||
vdbe.lo vdbeapi.lo vdbeaux.lo vdbeblob.lo vdbemem.lo vdbesort.lo \
|
||||
@ -244,7 +246,6 @@ SRC = \
|
||||
$(TOP)/src/hash.h \
|
||||
$(TOP)/src/hwtime.h \
|
||||
$(TOP)/src/insert.c \
|
||||
$(TOP)/src/journal.c \
|
||||
$(TOP)/src/legacy.c \
|
||||
$(TOP)/src/loadext.c \
|
||||
$(TOP)/src/main.c \
|
||||
@ -362,14 +363,15 @@ SRC += \
|
||||
SRC += \
|
||||
$(TOP)/ext/rtree/rtree.h \
|
||||
$(TOP)/ext/rtree/rtree.c
|
||||
SRC += \
|
||||
$(TOP)/ext/session/sqlite3session.c \
|
||||
$(TOP)/ext/session/sqlite3session.h
|
||||
SRC += \
|
||||
$(TOP)/ext/rbu/sqlite3rbu.h \
|
||||
$(TOP)/ext/rbu/sqlite3rbu.c
|
||||
SRC += \
|
||||
$(TOP)/ext/misc/json1.c
|
||||
|
||||
|
||||
|
||||
# Generated source code files
|
||||
#
|
||||
SRC += \
|
||||
@ -396,9 +398,11 @@ TESTSRC = \
|
||||
$(TOP)/src/test_autoext.c \
|
||||
$(TOP)/src/test_async.c \
|
||||
$(TOP)/src/test_backup.c \
|
||||
$(TOP)/src/test_bestindex.c \
|
||||
$(TOP)/src/test_blob.c \
|
||||
$(TOP)/src/test_btree.c \
|
||||
$(TOP)/src/test_config.c \
|
||||
$(TOP)/src/test_delete.c \
|
||||
$(TOP)/src/test_demovfs.c \
|
||||
$(TOP)/src/test_devsym.c \
|
||||
$(TOP)/src/test_fs.c \
|
||||
@ -425,14 +429,17 @@ TESTSRC = \
|
||||
$(TOP)/src/test_windirent.c \
|
||||
$(TOP)/src/test_wsd.c \
|
||||
$(TOP)/ext/fts3/fts3_term.c \
|
||||
$(TOP)/ext/fts3/fts3_test.c \
|
||||
$(TOP)/ext/fts3/fts3_test.c \
|
||||
$(TOP)/ext/session/test_session.c \
|
||||
$(TOP)/ext/rbu/test_rbu.c
|
||||
|
||||
# Statically linked extensions
|
||||
#
|
||||
TESTSRC += \
|
||||
$(TOP)/ext/misc/amatch.c \
|
||||
$(TOP)/ext/misc/carray.c \
|
||||
$(TOP)/ext/misc/closure.c \
|
||||
$(TOP)/ext/misc/csv.c \
|
||||
$(TOP)/ext/misc/eval.c \
|
||||
$(TOP)/ext/misc/fileio.c \
|
||||
$(TOP)/ext/misc/fuzzer.c \
|
||||
@ -494,7 +501,8 @@ TESTSRC2 = \
|
||||
$(TOP)/ext/fts3/fts3_term.c \
|
||||
$(TOP)/ext/fts3/fts3_tokenizer.c \
|
||||
$(TOP)/ext/fts3/fts3_write.c \
|
||||
$(TOP)/ext/async/sqlite3async.c
|
||||
$(TOP)/ext/async/sqlite3async.c \
|
||||
$(TOP)/ext/session/sqlite3session.c
|
||||
|
||||
# Header files used by all library source files.
|
||||
#
|
||||
@ -553,7 +561,8 @@ TESTPROGS = \
|
||||
testfixture$(TEXE) \
|
||||
sqlite3$(TEXE) \
|
||||
sqlite3_analyzer$(TEXE) \
|
||||
sqldiff$(TEXE)
|
||||
sqldiff$(TEXE) \
|
||||
dbhash$(TEXE)
|
||||
|
||||
# Databases containing fuzzer test cases
|
||||
#
|
||||
@ -570,7 +579,9 @@ TESTOPTS = --verbose=file --output=test-out.txt
|
||||
# Extra compiler options for various shell tools
|
||||
#
|
||||
SHELL_OPT = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS4
|
||||
# SHELL_OPT += -DSQLITE_ENABLE_FTS5
|
||||
SHELL_OPT += -DSQLITE_ENABLE_EXPLAIN_COMMENTS
|
||||
SHELL_OPT += -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
|
||||
FUZZERSHELL_OPT = -DSQLITE_ENABLE_JSON1
|
||||
FUZZCHECK_OPT = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_MEMSYS5
|
||||
|
||||
@ -586,11 +597,11 @@ sqlcipher.pc: $(TOP)/sqlcipher.pc.in
|
||||
./config.status
|
||||
|
||||
libsqlcipher.la: $(LIBOBJ)
|
||||
$(LTLINK) -o $@ $(LIBOBJ) $(TLIBS) \
|
||||
$(LTLINK) -no-undefined -o $@ $(LIBOBJ) $(TLIBS) \
|
||||
${ALLOWRELEASE} -rpath "$(libdir)" -version-info "8:6:8"
|
||||
|
||||
libtclsqlite3.la: tclsqlite.lo libsqlcipher.la
|
||||
$(LTLINK) -o $@ tclsqlite.lo \
|
||||
$(LTLINK) -no-undefined -o $@ tclsqlite.lo \
|
||||
libsqlcipher.la @TCL_STUB_LIB_SPEC@ $(TLIBS) \
|
||||
-rpath "$(TCLLIBDIR)" \
|
||||
-version-info "8:6:8" \
|
||||
@ -601,8 +612,15 @@ sqlcipher$(TEXE): $(TOP)/src/shell.c libsqlcipher.la sqlite3.h
|
||||
-o $@ $(TOP)/src/shell.c libsqlcipher.la \
|
||||
$(LIBREADLINE) $(TLIBS) -rpath "$(libdir)"
|
||||
|
||||
sqldiff$(TEXE): $(TOP)/tool/sqldiff.c sqlite3.c sqlite3.h
|
||||
$(LTLINK) -o $@ $(TOP)/tool/sqldiff.c sqlite3.c $(TLIBS)
|
||||
sqldiff$(TEXE): $(TOP)/tool/sqldiff.c sqlite3.lo sqlite3.h
|
||||
$(LTLINK) -o $@ $(TOP)/tool/sqldiff.c sqlite3.lo $(TLIBS)
|
||||
|
||||
dbhash$(TEXE): $(TOP)/tool/dbhash.c sqlite3.lo sqlite3.h
|
||||
$(LTLINK) -o $@ $(TOP)/tool/dbhash.c sqlite3.lo $(TLIBS)
|
||||
|
||||
scrub$(TEXE): $(TOP)/ext/misc/scrub.c sqlite3.lo
|
||||
$(LTLINK) -o $@ -I. -DSCRUB_STANDALONE \
|
||||
$(TOP)/ext/misc/scrub.c sqlite3.lo $(TLIBS)
|
||||
|
||||
srcck1$(BEXE): $(TOP)/tool/srcck1.c
|
||||
$(BCC) -o srcck1$(BEXE) $(TOP)/tool/srcck1.c
|
||||
@ -617,8 +635,8 @@ fuzzershell$(TEXE): $(TOP)/tool/fuzzershell.c sqlite3.c sqlite3.h
|
||||
fuzzcheck$(TEXE): $(TOP)/test/fuzzcheck.c sqlite3.c sqlite3.h
|
||||
$(LTLINK) -o $@ $(FUZZCHECK_OPT) $(TOP)/test/fuzzcheck.c sqlite3.c $(TLIBS)
|
||||
|
||||
mptester$(TEXE): sqlite3.c $(TOP)/mptest/mptest.c
|
||||
$(LTLINK) -o $@ -I. $(TOP)/mptest/mptest.c sqlite3.c \
|
||||
mptester$(TEXE): sqlite3.lo $(TOP)/mptest/mptest.c
|
||||
$(LTLINK) -o $@ -I. $(TOP)/mptest/mptest.c sqlite3.lo \
|
||||
$(TLIBS) -rpath "$(libdir)"
|
||||
|
||||
MPTEST1=./mptester$(TEXE) mptest.db $(TOP)/mptest/crash01.test --repeat 20
|
||||
@ -654,6 +672,7 @@ mptest: mptester$(TEXE)
|
||||
sqlite3.c: .target_source $(TOP)/tool/mksqlite3c.tcl
|
||||
$(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl
|
||||
cp tsrc/shell.c tsrc/sqlite3ext.h .
|
||||
cp $(TOP)/ext/session/sqlite3session.h .
|
||||
|
||||
sqlite3ext.h: .target_source
|
||||
cp tsrc/sqlite3ext.h .
|
||||
@ -771,9 +790,6 @@ hash.lo: $(TOP)/src/hash.c $(HDR)
|
||||
insert.lo: $(TOP)/src/insert.c $(HDR)
|
||||
$(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/insert.c
|
||||
|
||||
journal.lo: $(TOP)/src/journal.c $(HDR)
|
||||
$(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/journal.c
|
||||
|
||||
legacy.lo: $(TOP)/src/legacy.c $(HDR)
|
||||
$(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/legacy.c
|
||||
|
||||
@ -1033,6 +1049,9 @@ fts3_write.lo: $(TOP)/ext/fts3/fts3_write.c $(HDR) $(EXTHDR)
|
||||
rtree.lo: $(TOP)/ext/rtree/rtree.c $(HDR) $(EXTHDR)
|
||||
$(LTCOMPILE) -DSQLITE_CORE -c $(TOP)/ext/rtree/rtree.c
|
||||
|
||||
sqlite3session.lo: $(TOP)/ext/session/sqlite3session.c $(HDR) $(EXTHDR)
|
||||
$(LTCOMPILE) -DSQLITE_CORE -c $(TOP)/ext/session/sqlite3session.c
|
||||
|
||||
json1.lo: $(TOP)/ext/misc/json1.c
|
||||
$(LTCOMPILE) -DSQLITE_CORE -c $(TOP)/ext/misc/json1.c
|
||||
|
||||
@ -1058,7 +1077,7 @@ FTS5_SRC = \
|
||||
fts5parse.c: $(TOP)/ext/fts5/fts5parse.y lemon
|
||||
cp $(TOP)/ext/fts5/fts5parse.y .
|
||||
rm -f fts5parse.h
|
||||
./lemon $(OPTS) fts5parse.y
|
||||
./lemon$(BEXE) $(OPTS) fts5parse.y
|
||||
|
||||
fts5parse.h: fts5parse.c
|
||||
|
||||
@ -1083,6 +1102,8 @@ sqlite3rbu.lo: $(TOP)/ext/rbu/sqlite3rbu.c $(HDR) $(EXTHDR)
|
||||
TESTFIXTURE_FLAGS = -DTCLSH=1 -DSQLITE_TEST=1 -DSQLITE_CRASH_TEST=1
|
||||
TESTFIXTURE_FLAGS += -DSQLITE_SERVER=1 -DSQLITE_PRIVATE="" -DSQLITE_CORE
|
||||
TESTFIXTURE_FLAGS += -DBUILD_sqlite
|
||||
TESTFIXTURE_FLAGS += -DSQLITE_SERIES_CONSTRAINT_VERIFY=1
|
||||
TESTFIXTURE_FLAGS += -DSQLITE_DEFAULT_PAGE_SIZE=1024
|
||||
|
||||
TESTFIXTURE_SRC0 = $(TESTSRC2) libsqlcipher.la
|
||||
TESTFIXTURE_SRC1 = sqlite3.c
|
||||
@ -1163,14 +1184,17 @@ showjournal$(TEXE): $(TOP)/tool/showjournal.c sqlite3.lo
|
||||
showwal$(TEXE): $(TOP)/tool/showwal.c sqlite3.lo
|
||||
$(LTLINK) -o $@ $(TOP)/tool/showwal.c sqlite3.lo $(TLIBS)
|
||||
|
||||
changeset$(TEXE): $(TOP)/ext/session/changeset.c sqlite3.lo
|
||||
$(LTLINK) -o $@ $(TOP)/ext/session/changeset.c sqlite3.lo $(TLIBS)
|
||||
|
||||
rollback-test$(TEXE): $(TOP)/tool/rollback-test.c sqlite3.lo
|
||||
$(LTLINK) -o $@ $(TOP)/tool/rollback-test.c sqlite3.lo $(TLIBS)
|
||||
|
||||
LogEst$(TEXE): $(TOP)/tool/logest.c sqlite3.h
|
||||
$(LTLINK) -I. -o $@ $(TOP)/tool/logest.c
|
||||
|
||||
wordcount$(TEXE): $(TOP)/test/wordcount.c sqlite3.c
|
||||
$(LTLINK) -o $@ $(TOP)/test/wordcount.c sqlite3.c $(TLIBS)
|
||||
wordcount$(TEXE): $(TOP)/test/wordcount.c sqlite3.lo
|
||||
$(LTLINK) -o $@ $(TOP)/test/wordcount.c sqlite3.lo $(TLIBS)
|
||||
|
||||
speedtest1$(TEXE): $(TOP)/test/speedtest1.c sqlite3.lo
|
||||
$(LTLINK) -o $@ $(TOP)/test/speedtest1.c sqlite3.lo $(TLIBS)
|
||||
@ -1185,8 +1209,9 @@ loadfts$(EXE): $(TOP)/tool/loadfts.c libsqlite3.la
|
||||
# symbols that do not begin with "sqlite3_". It is run as part of the
|
||||
# releasetest.tcl script.
|
||||
#
|
||||
VALIDIDS=' sqlite3(changeset|changegroup|session)?_'
|
||||
checksymbols: sqlite3.lo
|
||||
nm -g --defined-only sqlite3.o | grep -v " sqlite3_" ; test $$? -ne 0
|
||||
nm -g --defined-only sqlite3.lo | egrep -v $(VALIDIDS); test $$? -ne 0
|
||||
echo '0 errors out of 1 tests'
|
||||
|
||||
# Build the amalgamation-autoconf package. The amalamgation-tarball target builds
|
||||
@ -1249,13 +1274,12 @@ clean:
|
||||
rm -f lemon$(BEXE) lempar.c parse.* sqlite*.tar.gz
|
||||
rm -f mkkeywordhash$(BEXE) keywordhash.h
|
||||
rm -f *.da *.bb *.bbg gmon.out
|
||||
rm -rf quota2a quota2b quota2c
|
||||
rm -rf tsrc .target_source
|
||||
rm -f tclsqlcipher$(TEXE)
|
||||
rm -f testfixture$(TEXE) test.db
|
||||
rm -f LogEst$(TEXE) fts3view$(TEXE) rollback-test$(TEXE) showdb$(TEXE)
|
||||
rm -f showjournal$(TEXE) showstat4$(TEXE) showwal$(TEXE) speedtest1$(TEXE)
|
||||
rm -f wordcount$(TEXE)
|
||||
rm -f wordcount$(TEXE) changeset$(TEXE)
|
||||
rm -f sqlite3.dll sqlite3.lib sqlite3.exp sqlite3.def
|
||||
rm -f sqlite3.c
|
||||
rm -f sqlite3rc.h
|
||||
@ -1268,6 +1292,7 @@ clean:
|
||||
rm -f fuzzershell fuzzershell.exe
|
||||
rm -f fuzzcheck fuzzcheck.exe
|
||||
rm -f sqldiff sqldiff.exe
|
||||
rm -f dbhash dbhash.exe
|
||||
rm -f fts5.* fts5parse.*
|
||||
|
||||
distclean: clean
|
||||
|
||||
303
Makefile.msc
303
Makefile.msc
@ -24,6 +24,20 @@ USE_AMALGAMATION = 1
|
||||
USE_FULLWARN = 0
|
||||
!ENDIF
|
||||
|
||||
# Set this non-0 to enable full runtime error checks (-RTC1, etc). This
|
||||
# has no effect if (any) optimizations are enabled.
|
||||
#
|
||||
!IFNDEF USE_RUNTIME_CHECKS
|
||||
USE_RUNTIME_CHECKS = 0
|
||||
!ENDIF
|
||||
|
||||
# Set this non-0 to create a SQLite amalgamation file that excludes the
|
||||
# various built-in extensions.
|
||||
#
|
||||
!IFNDEF MINIMAL_AMALGAMATION
|
||||
MINIMAL_AMALGAMATION = 0
|
||||
!ENDIF
|
||||
|
||||
# Set this non-0 to use "stdcall" calling convention for the core library
|
||||
# and shell executable.
|
||||
#
|
||||
@ -198,6 +212,12 @@ DEBUG = 0
|
||||
OPTIMIZATIONS = 2
|
||||
!ENDIF
|
||||
|
||||
# Set this to non-0 to enable support for the session extension.
|
||||
#
|
||||
!IFNDEF SESSION
|
||||
SESSION = 0
|
||||
!ENDIF
|
||||
|
||||
# Set the source code file to be used by executables and libraries when
|
||||
# they need the amalgamation.
|
||||
#
|
||||
@ -219,37 +239,88 @@ SQLITE3H = sqlite3.h
|
||||
# This is the name to use for the SQLite dynamic link library (DLL).
|
||||
#
|
||||
!IFNDEF SQLITE3DLL
|
||||
!IF $(FOR_WIN10)!=0
|
||||
SQLITE3DLL = winsqlite3.dll
|
||||
!ELSE
|
||||
SQLITE3DLL = sqlite3.dll
|
||||
!ENDIF
|
||||
!ENDIF
|
||||
|
||||
# This is the name to use for the SQLite import library (LIB).
|
||||
#
|
||||
!IFNDEF SQLITE3LIB
|
||||
!IF $(FOR_WIN10)!=0
|
||||
SQLITE3LIB = winsqlite3.lib
|
||||
!ELSE
|
||||
SQLITE3LIB = sqlite3.lib
|
||||
!ENDIF
|
||||
!ENDIF
|
||||
|
||||
# This is the name to use for the SQLite shell executable (EXE).
|
||||
#
|
||||
!IFNDEF SQLITE3EXE
|
||||
!IF $(FOR_WIN10)!=0
|
||||
SQLITE3EXE = winsqlite3shell.exe
|
||||
!ELSE
|
||||
SQLITE3EXE = sqlite3.exe
|
||||
!ENDIF
|
||||
!ENDIF
|
||||
|
||||
# This is the argument used to set the program database (PDB) file for the
|
||||
# SQLite shell executable (EXE).
|
||||
#
|
||||
!IFNDEF SQLITE3EXEPDB
|
||||
!IF $(FOR_WIN10)!=0
|
||||
SQLITE3EXEPDB =
|
||||
!ELSE
|
||||
SQLITE3EXEPDB = /pdb:sqlite3sh.pdb
|
||||
!ENDIF
|
||||
!ENDIF
|
||||
|
||||
# <<mark>>
|
||||
# These are the names of the customized Tcl header files used by various parts
|
||||
# of this makefile when the stdcall calling convention is in use. It is not
|
||||
# used for any other purpose.
|
||||
#
|
||||
!IFNDEF SQLITETCLH
|
||||
SQLITETCLH = sqlite_tcl.h
|
||||
!ENDIF
|
||||
|
||||
!IFNDEF SQLITETCLDECLSH
|
||||
SQLITETCLDECLSH = sqlite_tclDecls.h
|
||||
!ENDIF
|
||||
|
||||
# These are the additional targets that the targets that integrate with the
|
||||
# Tcl library should depend on when compiling, etc.
|
||||
#
|
||||
!IFNDEF SQLITE_TCL_DEP
|
||||
!IF $(USE_STDCALL)!=0 || $(FOR_WIN10)!=0
|
||||
SQLITE_TCL_DEP = $(SQLITETCLDECLSH) $(SQLITETCLH)
|
||||
!ELSE
|
||||
SQLITE_TCL_DEP =
|
||||
!ENDIF
|
||||
!ENDIF
|
||||
# <</mark>>
|
||||
|
||||
# These are the "standard" SQLite compilation options used when compiling for
|
||||
# the Windows platform.
|
||||
#
|
||||
!IFNDEF OPT_FEATURE_FLAGS
|
||||
!IF $(MINIMAL_AMALGAMATION)==0
|
||||
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS3=1
|
||||
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RTREE=1
|
||||
!ENDIF
|
||||
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_COLUMN_METADATA=1
|
||||
!ENDIF
|
||||
|
||||
# Should the session extension be enabled? If so, add compilation options
|
||||
# to enable it.
|
||||
#
|
||||
!IF $(SESSION)!=0
|
||||
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_SESSION=1
|
||||
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_PREUPDATE_HOOK=1
|
||||
!ENDIF
|
||||
|
||||
# These are the "extended" SQLite compilation options used when compiling for
|
||||
# the Windows 10 platform.
|
||||
#
|
||||
@ -290,6 +361,14 @@ PROGRAMFILES_X86 = $(PROGRAMFILES_X86:\\=\)
|
||||
CC = cl.exe
|
||||
!ENDIF
|
||||
|
||||
# Check for the predefined command macro CSC. This should point to a working
|
||||
# C Sharp compiler binary. If it is not defined, simply define it to the
|
||||
# legacy default value 'csc.exe'.
|
||||
#
|
||||
!IFNDEF CSC
|
||||
CSC = csc.exe
|
||||
!ENDIF
|
||||
|
||||
# Check for the command macro LD. This should point to the linker binary for
|
||||
# the target platform. If it is not defined, simply define it to the legacy
|
||||
# default value 'link.exe'.
|
||||
@ -417,15 +496,6 @@ TCC = $(CC) -nologo -W3 $(CCOPTS) $(TCCOPTS)
|
||||
TCC = $(TCC) -DSQLITE_OS_WIN=1 -I. -I$(TOP) -I$(TOP)\src -fp:precise
|
||||
RCC = $(RC) -DSQLITE_OS_WIN=1 -I. -I$(TOP) -I$(TOP)\src $(RCOPTS) $(RCCOPTS)
|
||||
|
||||
# Adjust the names of the primary targets for use with Windows 10.
|
||||
#
|
||||
!IF $(FOR_WIN10)!=0
|
||||
SQLITE3DLL = winsqlite3.dll
|
||||
SQLITE3LIB = winsqlite3.lib
|
||||
SQLITE3EXE = winsqlite3shell.exe
|
||||
SQLITE3EXEPDB =
|
||||
!ENDIF
|
||||
|
||||
# Check if we want to use the "stdcall" calling convention when compiling.
|
||||
# This is not supported by the compilers for non-x86 platforms. It should
|
||||
# also be noted here that building any target with these "stdcall" options
|
||||
@ -435,20 +505,32 @@ SQLITE3EXEPDB =
|
||||
#
|
||||
!IF $(USE_STDCALL)!=0 || $(FOR_WIN10)!=0
|
||||
!IF "$(PLATFORM)"=="x86"
|
||||
CORE_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_STDCALL=__stdcall
|
||||
SHELL_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_STDCALL=__stdcall
|
||||
CORE_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall
|
||||
SHELL_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall
|
||||
# <<mark>>
|
||||
TEST_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall -DINCLUDE_SQLITE_TCL_H=1 -DSQLITE_TCLAPI=__cdecl
|
||||
# <</mark>>
|
||||
!ELSE
|
||||
!IFNDEF PLATFORM
|
||||
CORE_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_STDCALL=__stdcall
|
||||
SHELL_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_STDCALL=__stdcall
|
||||
CORE_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall
|
||||
SHELL_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall
|
||||
# <<mark>>
|
||||
TEST_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall -DINCLUDE_SQLITE_TCL_H=1 -DSQLITE_TCLAPI=__cdecl
|
||||
# <</mark>>
|
||||
!ELSE
|
||||
CORE_CCONV_OPTS =
|
||||
SHELL_CCONV_OPTS =
|
||||
# <<mark>>
|
||||
TEST_CCONV_OPTS =
|
||||
# <</mark>>
|
||||
!ENDIF
|
||||
!ENDIF
|
||||
!ELSE
|
||||
CORE_CCONV_OPTS =
|
||||
SHELL_CCONV_OPTS =
|
||||
# <<mark>>
|
||||
TEST_CCONV_OPTS =
|
||||
# <</mark>>
|
||||
!ENDIF
|
||||
|
||||
# These are additional compiler options used for the core library.
|
||||
@ -465,20 +547,24 @@ CORE_COMPILE_OPTS = $(CORE_CCONV_OPTS)
|
||||
# when linking.
|
||||
#
|
||||
!IFNDEF CORE_LINK_DEP
|
||||
!IF $(DYNAMIC_SHELL)!=0 || $(FOR_WIN10)!=0
|
||||
!IF $(DYNAMIC_SHELL)!=0
|
||||
CORE_LINK_DEP =
|
||||
!ELSE
|
||||
!ELSEIF $(FOR_WIN10)==0 || "$(PLATFORM)"=="x86"
|
||||
CORE_LINK_DEP = sqlite3.def
|
||||
!ELSE
|
||||
CORE_LINK_DEP =
|
||||
!ENDIF
|
||||
!ENDIF
|
||||
|
||||
# These are additional linker options used for the core library.
|
||||
#
|
||||
!IFNDEF CORE_LINK_OPTS
|
||||
!IF $(DYNAMIC_SHELL)!=0 || $(FOR_WIN10)!=0
|
||||
!IF $(DYNAMIC_SHELL)!=0
|
||||
CORE_LINK_OPTS =
|
||||
!ELSE
|
||||
!ELSEIF $(FOR_WIN10)==0 || "$(PLATFORM)"=="x86"
|
||||
CORE_LINK_OPTS = /DEF:sqlite3.def
|
||||
!ELSE
|
||||
CORE_LINK_OPTS =
|
||||
!ENDIF
|
||||
!ENDIF
|
||||
|
||||
@ -588,6 +674,8 @@ TCC = $(TCC) -I$(TOP)\ext\fts3
|
||||
RCC = $(RCC) -I$(TOP)\ext\fts3
|
||||
TCC = $(TCC) -I$(TOP)\ext\rtree
|
||||
RCC = $(RCC) -I$(TOP)\ext\rtree
|
||||
TCC = $(TCC) -I$(TOP)\ext\session
|
||||
RCC = $(RCC) -I$(TOP)\ext\session
|
||||
!ENDIF
|
||||
|
||||
# The mksqlite3c.tcl script accepts some options on the command
|
||||
@ -595,12 +683,35 @@ RCC = $(RCC) -I$(TOP)\ext\rtree
|
||||
# options are necessary in order to allow debugging symbols to
|
||||
# work correctly with Visual Studio when using the amalgamation.
|
||||
#
|
||||
!IFNDEF MKSQLITE3C_TOOL
|
||||
!IF $(MINIMAL_AMALGAMATION)!=0
|
||||
MKSQLITE3C_TOOL = $(TOP)\tool\mksqlite3c-noext.tcl
|
||||
!ELSE
|
||||
MKSQLITE3C_TOOL = $(TOP)\tool\mksqlite3c.tcl
|
||||
!ENDIF
|
||||
!ENDIF
|
||||
|
||||
!IFNDEF MKSQLITE3C_ARGS
|
||||
!IF $(DEBUG)>1
|
||||
MKSQLITE3C_ARGS = --linemacros
|
||||
!ELSE
|
||||
MKSQLITE3C_ARGS =
|
||||
!ENDIF
|
||||
!IF $(USE_STDCALL)!=0 || $(FOR_WIN10)!=0
|
||||
MKSQLITE3C_ARGS = $(MKSQLITE3C_ARGS) --useapicall
|
||||
!ENDIF
|
||||
!ENDIF
|
||||
|
||||
# The mksqlite3h.tcl script accepts some options on the command line.
|
||||
# When compiling with stdcall support, some of these options are
|
||||
# necessary.
|
||||
#
|
||||
!IFNDEF MKSQLITE3H_ARGS
|
||||
!IF $(USE_STDCALL)!=0 || $(FOR_WIN10)!=0
|
||||
MKSQLITE3H_ARGS = --useapicall
|
||||
!ELSE
|
||||
MKSQLITE3H_ARGS =
|
||||
!ENDIF
|
||||
!ENDIF
|
||||
# <</mark>>
|
||||
|
||||
@ -683,11 +794,11 @@ TCLLIBDIR = c:\tcl\lib
|
||||
!ENDIF
|
||||
|
||||
!IFNDEF LIBTCL
|
||||
LIBTCL = tcl85.lib
|
||||
LIBTCL = tcl86.lib
|
||||
!ENDIF
|
||||
|
||||
!IFNDEF LIBTCLSTUB
|
||||
LIBTCLSTUB = tclstub85.lib
|
||||
LIBTCLSTUB = tclstub86.lib
|
||||
!ENDIF
|
||||
|
||||
!IFNDEF LIBTCLPATH
|
||||
@ -717,7 +828,7 @@ LIBICU = icuuc.lib icuin.lib
|
||||
# specific Tcl shell to use.
|
||||
#
|
||||
!IFNDEF TCLSH_CMD
|
||||
TCLSH_CMD = tclsh85
|
||||
TCLSH_CMD = tclsh
|
||||
!ENDIF
|
||||
# <</mark>>
|
||||
|
||||
@ -800,6 +911,10 @@ RCC = $(RCC) -D_DEBUG
|
||||
!IF $(DEBUG)>1 || $(OPTIMIZATIONS)==0
|
||||
TCC = $(TCC) -Od
|
||||
BCC = $(BCC) -Od
|
||||
!IF $(USE_RUNTIME_CHECKS)!=0
|
||||
TCC = $(TCC) -RTC1
|
||||
BCC = $(BCC) -RTC1
|
||||
!ENDIF
|
||||
!ELSEIF $(OPTIMIZATIONS)>=3
|
||||
TCC = $(TCC) -Ox
|
||||
BCC = $(BCC) -Ox
|
||||
@ -966,13 +1081,14 @@ LIBOBJS0 = vdbe.lo parse.lo alter.lo analyze.lo attach.lo auth.lo \
|
||||
fts3_tokenize_vtab.lo fts3_unicode.lo fts3_unicode2.lo fts3_write.lo \
|
||||
fts5.lo \
|
||||
func.lo global.lo hash.lo \
|
||||
icu.lo insert.lo journal.lo legacy.lo loadext.lo \
|
||||
icu.lo insert.lo legacy.lo loadext.lo \
|
||||
main.lo malloc.lo mem0.lo mem1.lo mem2.lo mem3.lo mem5.lo \
|
||||
memjournal.lo \
|
||||
mutex.lo mutex_noop.lo mutex_unix.lo mutex_w32.lo \
|
||||
notify.lo opcodes.lo os.lo os_unix.lo os_win.lo \
|
||||
pager.lo pcache.lo pcache1.lo pragma.lo prepare.lo printf.lo \
|
||||
random.lo resolve.lo rowset.lo rtree.lo select.lo sqlite3rbu.lo status.lo \
|
||||
random.lo resolve.lo rowset.lo rtree.lo \
|
||||
sqlite3session.lo select.lo sqlite3rbu.lo status.lo \
|
||||
table.lo threads.lo tokenize.lo treeview.lo trigger.lo \
|
||||
update.lo util.lo vacuum.lo \
|
||||
vdbeapi.lo vdbeaux.lo vdbeblob.lo vdbemem.lo vdbesort.lo \
|
||||
@ -1037,7 +1153,6 @@ SRC00 = \
|
||||
$(TOP)\src\global.c \
|
||||
$(TOP)\src\hash.c \
|
||||
$(TOP)\src\insert.c \
|
||||
$(TOP)\src\journal.c \
|
||||
$(TOP)\src\legacy.c \
|
||||
$(TOP)\src\loadext.c \
|
||||
$(TOP)\src\main.c \
|
||||
@ -1167,6 +1282,7 @@ SRC07 = \
|
||||
$(TOP)\ext\fts3\fts3_write.c \
|
||||
$(TOP)\ext\icu\icu.c \
|
||||
$(TOP)\ext\rtree\rtree.c \
|
||||
$(TOP)\ext\session\sqlite3session.c \
|
||||
$(TOP)\ext\rbu\sqlite3rbu.c \
|
||||
$(TOP)\ext\misc\json1.c
|
||||
|
||||
@ -1189,7 +1305,8 @@ SRC09 = \
|
||||
$(TOP)\ext\fts3\fts3_tokenizer.h \
|
||||
$(TOP)\ext\icu\sqliteicu.h \
|
||||
$(TOP)\ext\rtree\rtree.h \
|
||||
$(TOP)\ext\rbu\sqlite3rbu.h
|
||||
$(TOP)\ext\rbu\sqlite3rbu.h \
|
||||
$(TOP)\ext\session\sqlite3session.h
|
||||
|
||||
# Generated source code files
|
||||
#
|
||||
@ -1205,6 +1322,16 @@ SRC11 = \
|
||||
parse.h \
|
||||
$(SQLITE3H)
|
||||
|
||||
# Generated Tcl header files
|
||||
#
|
||||
!IF $(USE_STDCALL)!=0 || $(FOR_WIN10)!=0
|
||||
SRC12 = \
|
||||
$(SQLITETCLH) \
|
||||
$(SQLITETCLDECLSH)
|
||||
!ELSE
|
||||
SRC12 =
|
||||
!ENDIF
|
||||
|
||||
# All source code files.
|
||||
#
|
||||
SRC = $(SRC00) $(SRC01) $(SRC02) $(SRC03) $(SRC04) $(SRC05) $(SRC06) $(SRC07) $(SRC08) $(SRC09) $(SRC10) $(SRC11)
|
||||
@ -1224,9 +1351,11 @@ TESTSRC = \
|
||||
$(TOP)\src\test_autoext.c \
|
||||
$(TOP)\src\test_async.c \
|
||||
$(TOP)\src\test_backup.c \
|
||||
$(TOP)\src\test_bestindex.c \
|
||||
$(TOP)\src\test_blob.c \
|
||||
$(TOP)\src\test_btree.c \
|
||||
$(TOP)\src\test_config.c \
|
||||
$(TOP)\src\test_delete.c \
|
||||
$(TOP)\src\test_demovfs.c \
|
||||
$(TOP)\src\test_devsym.c \
|
||||
$(TOP)\src\test_fs.c \
|
||||
@ -1254,13 +1383,16 @@ TESTSRC = \
|
||||
$(TOP)\src\test_wsd.c \
|
||||
$(TOP)\ext\fts3\fts3_term.c \
|
||||
$(TOP)\ext\fts3\fts3_test.c \
|
||||
$(TOP)\ext\rbu\test_rbu.c
|
||||
$(TOP)\ext\rbu\test_rbu.c \
|
||||
$(TOP)\ext\session\test_session.c
|
||||
|
||||
# Statically linked extensions.
|
||||
#
|
||||
TESTEXT = \
|
||||
$(TOP)\ext\misc\amatch.c \
|
||||
$(TOP)\ext\misc\carray.c \
|
||||
$(TOP)\ext\misc\closure.c \
|
||||
$(TOP)\ext\misc\csv.c \
|
||||
$(TOP)\ext\misc\eval.c \
|
||||
$(TOP)\ext\misc\fileio.c \
|
||||
$(TOP)\ext\misc\fuzzer.c \
|
||||
@ -1277,6 +1409,7 @@ TESTEXT = \
|
||||
$(TOP)\ext\misc\wholenumber.c
|
||||
|
||||
# Source code to the library files needed by the test fixture
|
||||
# (non-amalgamation)
|
||||
#
|
||||
TESTSRC2 = \
|
||||
$(SRC00) \
|
||||
@ -1306,7 +1439,7 @@ HDR = \
|
||||
parse.h \
|
||||
$(TOP)\src\pragma.h \
|
||||
$(SQLITE3H) \
|
||||
$(TOP)\src\sqlite3ext.h \
|
||||
sqlite3ext.h \
|
||||
$(TOP)\src\sqliteInt.h \
|
||||
$(TOP)\src\sqliteLimit.h \
|
||||
$(TOP)\src\vdbe.h \
|
||||
@ -1335,6 +1468,8 @@ EXTHDR = $(EXTHDR) \
|
||||
$(TOP)\ext\icu\sqliteicu.h
|
||||
EXTHDR = $(EXTHDR) \
|
||||
$(TOP)\ext\rtree\sqlite3rtree.h
|
||||
EXTHDR = $(EXTHDR) \
|
||||
$(TOP)\ext\session\sqlite3session.h
|
||||
|
||||
# executables needed for testing
|
||||
#
|
||||
@ -1342,7 +1477,8 @@ TESTPROGS = \
|
||||
testfixture.exe \
|
||||
$(SQLITE3EXE) \
|
||||
sqlite3_analyzer.exe \
|
||||
sqldiff.exe
|
||||
sqldiff.exe \
|
||||
dbhash.exe
|
||||
|
||||
# Databases containing fuzzer test cases
|
||||
#
|
||||
@ -1387,30 +1523,30 @@ all: dll libsqlite3.lib shell $(ALL_TCL_TARGETS)
|
||||
|
||||
# Dynamic link library section.
|
||||
#
|
||||
dll: $(SQLITE3DLL)
|
||||
dll: $(SQLITE3DLL)
|
||||
|
||||
# Shell executable.
|
||||
#
|
||||
shell: $(SQLITE3EXE)
|
||||
shell: $(SQLITE3EXE)
|
||||
|
||||
# <<mark>>
|
||||
libsqlite3.lib: $(LIBOBJ)
|
||||
$(LTLIB) $(LTLIBOPTS) /OUT:$@ $(LIBOBJ) $(TLIBS)
|
||||
|
||||
# <<mark>>
|
||||
libtclsqlite3.lib: tclsqlite.lo libsqlite3.lib
|
||||
$(LTLIB) $(LTLIBOPTS) $(LTLIBPATHS) /OUT:$@ tclsqlite.lo libsqlite3.lib $(LIBTCLSTUB) $(TLIBS)
|
||||
# <</mark>>
|
||||
|
||||
$(SQLITE3DLL): $(LIBOBJ) $(LIBRESOBJS) $(CORE_LINK_DEP)
|
||||
$(SQLITE3DLL): $(LIBOBJ) $(LIBRESOBJS) $(CORE_LINK_DEP)
|
||||
$(LD) $(LDFLAGS) $(LTLINKOPTS) $(LTLIBPATHS) /DLL $(CORE_LINK_OPTS) /OUT:$@ $(LIBOBJ) $(LIBRESOBJS) $(LTLIBS) $(TLIBS)
|
||||
|
||||
# <<mark>>
|
||||
sqlite3.def: libsqlite3.lib
|
||||
# <<block2>>
|
||||
sqlite3.def: libsqlite3.lib
|
||||
echo EXPORTS > sqlite3.def
|
||||
dumpbin /all libsqlite3.lib \
|
||||
| $(TCLSH_CMD) $(TOP)\tool\replace.tcl include "^\s+1 _?(sqlite3_.*)$$" \1 \
|
||||
| $(TCLSH_CMD) $(TOP)\tool\replace.tcl include "^\s+1 _?(sqlite3_[^@]*)(?:@\d+)?$$" \1 \
|
||||
| sort >> sqlite3.def
|
||||
# <</mark>>
|
||||
# <</block2>>
|
||||
|
||||
$(SQLITE3EXE): $(TOP)\src\shell.c $(SHELL_CORE_DEP) $(LIBRESOBJS) $(SHELL_CORE_SRC) $(SQLITE3H)
|
||||
$(LTLINK) $(SHELL_COMPILE_OPTS) $(READLINE_FLAGS) $(TOP)\src\shell.c $(SHELL_CORE_SRC) \
|
||||
@ -1420,6 +1556,12 @@ $(SQLITE3EXE): $(TOP)\src\shell.c $(SHELL_CORE_DEP) $(LIBRESOBJS) $(SHELL_CORE_S
|
||||
sqldiff.exe: $(TOP)\tool\sqldiff.c $(SQLITE3C) $(SQLITE3H)
|
||||
$(LTLINK) $(NO_WARN) $(TOP)\tool\sqldiff.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
|
||||
|
||||
dbhash.exe: $(TOP)\tool\dbhash.c $(SQLITE3C) $(SQLITE3H)
|
||||
$(LTLINK) $(NO_WARN) $(TOP)\tool\dbhash.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
|
||||
|
||||
scrub.exe: $(TOP)\ext\misc\scrub.c $(SQLITE3C) $(SQLITE3H)
|
||||
$(LTLINK) $(NO_WARN) $(TOP)\ext\misc\scrub.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
|
||||
|
||||
srcck1.exe: $(TOP)\tool\srcck1.c
|
||||
$(BCC) $(NO_WARN) -Fe$@ $(TOP)\tool\srcck1.c
|
||||
|
||||
@ -1455,7 +1597,7 @@ mptest: mptester.exe
|
||||
# files are automatically generated. This target takes care of
|
||||
# all that automatic generation.
|
||||
#
|
||||
.target_source: $(SRC) $(TOP)\tool\vdbe-compress.tcl fts5.c
|
||||
.target_source: $(SRC) $(TOP)\tool\vdbe-compress.tcl fts5.c $(SQLITE_TCL_DEP)
|
||||
-rmdir /Q/S tsrc 2>NUL
|
||||
-mkdir tsrc
|
||||
for %i in ($(SRC00)) do copy /Y %i tsrc
|
||||
@ -1470,6 +1612,7 @@ mptest: mptester.exe
|
||||
for %i in ($(SRC09)) do copy /Y %i tsrc
|
||||
for %i in ($(SRC10)) do copy /Y %i tsrc
|
||||
for %i in ($(SRC11)) do copy /Y %i tsrc
|
||||
for %i in ($(SRC12)) do copy /Y %i tsrc
|
||||
copy /Y fts5.c tsrc
|
||||
copy /Y fts5.h tsrc
|
||||
del /Q tsrc\sqlite.h.in tsrc\parse.y 2>NUL
|
||||
@ -1477,9 +1620,10 @@ mptest: mptester.exe
|
||||
move vdbe.new tsrc\vdbe.c
|
||||
echo > .target_source
|
||||
|
||||
sqlite3.c: .target_source sqlite3ext.h $(TOP)\tool\mksqlite3c.tcl
|
||||
$(TCLSH_CMD) $(TOP)\tool\mksqlite3c.tcl $(MKSQLITE3C_ARGS)
|
||||
sqlite3.c: .target_source sqlite3ext.h $(MKSQLITE3C_TOOL)
|
||||
$(TCLSH_CMD) $(MKSQLITE3C_TOOL) $(MKSQLITE3C_ARGS)
|
||||
copy tsrc\shell.c .
|
||||
copy $(TOP)\ext\session\sqlite3session.h .
|
||||
|
||||
sqlite3-all.c: sqlite3.c $(TOP)\tool\split-sqlite3c.tcl
|
||||
$(TCLSH_CMD) $(TOP)\tool\split-sqlite3c.tcl
|
||||
@ -1597,9 +1741,6 @@ hash.lo: $(TOP)\src\hash.c $(HDR)
|
||||
insert.lo: $(TOP)\src\insert.c $(HDR)
|
||||
$(LTCOMPILE) $(CORE_COMPILE_OPTS) -c $(TOP)\src\insert.c
|
||||
|
||||
journal.lo: $(TOP)\src\journal.c $(HDR)
|
||||
$(LTCOMPILE) $(CORE_COMPILE_OPTS) -c $(TOP)\src\journal.c
|
||||
|
||||
legacy.lo: $(TOP)\src\legacy.c $(HDR)
|
||||
$(LTCOMPILE) $(CORE_COMPILE_OPTS) -c $(TOP)\src\legacy.c
|
||||
|
||||
@ -1753,10 +1894,10 @@ wherecode.lo: $(TOP)\src\wherecode.c $(HDR)
|
||||
whereexpr.lo: $(TOP)\src\whereexpr.c $(HDR)
|
||||
$(LTCOMPILE) $(CORE_COMPILE_OPTS) -c $(TOP)\src\whereexpr.c
|
||||
|
||||
tclsqlite.lo: $(TOP)\src\tclsqlite.c $(HDR)
|
||||
tclsqlite.lo: $(TOP)\src\tclsqlite.c $(HDR) $(SQLITE_TCL_DEP)
|
||||
$(LTCOMPILE) $(NO_WARN) -DUSE_TCL_STUBS=1 -DBUILD_sqlite -I$(TCLINCDIR) -c $(TOP)\src\tclsqlite.c
|
||||
|
||||
tclsqlite-shell.lo: $(TOP)\src\tclsqlite.c $(HDR)
|
||||
tclsqlite-shell.lo: $(TOP)\src\tclsqlite.c $(HDR) $(SQLITE_TCL_DEP)
|
||||
$(LTCOMPILE) $(NO_WARN) -DTCLSH=1 -DBUILD_sqlite -I$(TCLINCDIR) -c $(TOP)\src\tclsqlite.c
|
||||
|
||||
tclsqlite3.exe: tclsqlite-shell.lo $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS)
|
||||
@ -1782,10 +1923,16 @@ parse.c: $(TOP)\src\parse.y lemon.exe $(TOP)\tool\addopcodes.tcl
|
||||
$(TCLSH_CMD) $(TOP)\tool\addopcodes.tcl parse.h.temp > parse.h
|
||||
|
||||
$(SQLITE3H): $(TOP)\src\sqlite.h.in $(TOP)\manifest.uuid $(TOP)\VERSION
|
||||
$(TCLSH_CMD) $(TOP)\tool\mksqlite3h.tcl $(TOP:\=/) > $(SQLITE3H)
|
||||
$(TCLSH_CMD) $(TOP)\tool\mksqlite3h.tcl $(TOP:\=/) > $(SQLITE3H) $(MKSQLITE3H_ARGS)
|
||||
|
||||
sqlite3ext.h: .target_source
|
||||
copy tsrc\sqlite3ext.h .
|
||||
sqlite3ext.h: .target_source
|
||||
!IF $(USE_STDCALL)!=0 || $(FOR_WIN10)!=0
|
||||
type tsrc\sqlite3ext.h | $(TCLSH_CMD) $(TOP)\tool\replace.tcl regsub "\(\*\)" "(SQLITE_CALLBACK *)" \
|
||||
| $(TCLSH_CMD) $(TOP)\tool\replace.tcl regsub "\(\*" "(SQLITE_APICALL *" > sqlite3ext.h
|
||||
copy /Y sqlite3ext.h tsrc\sqlite3ext.h
|
||||
!ELSE
|
||||
copy /Y tsrc\sqlite3ext.h sqlite3ext.h
|
||||
!ENDIF
|
||||
|
||||
mkkeywordhash.exe: $(TOP)\tool\mkkeywordhash.c
|
||||
$(BCC) $(NO_WARN) -Fe$@ $(REQ_FEATURE_FLAGS) $(OPT_FEATURE_FLAGS) $(EXT_FEATURE_FLAGS) $(OPTS) \
|
||||
@ -1861,6 +2008,9 @@ fts3_write.lo: $(TOP)\ext\fts3\fts3_write.c $(HDR) $(EXTHDR)
|
||||
rtree.lo: $(TOP)\ext\rtree\rtree.c $(HDR) $(EXTHDR)
|
||||
$(LTCOMPILE) $(CORE_COMPILE_OPTS) $(NO_WARN) -DSQLITE_CORE -c $(TOP)\ext\rtree\rtree.c
|
||||
|
||||
sqlite3session.lo: $(TOP)\ext\session\sqlite3session.c $(HDR) $(EXTHDR)
|
||||
$(LTCOMPILE) $(CORE_COMPILE_OPTS) $(NO_WARN) -DSQLITE_CORE -c $(TOP)\ext\session\sqlite3session.c
|
||||
|
||||
# FTS5 things
|
||||
#
|
||||
FTS5_SRC = \
|
||||
@ -1885,9 +2035,9 @@ fts5parse.c: $(TOP)\ext\fts5\fts5parse.y lemon.exe
|
||||
del /Q fts5parse.h 2>NUL
|
||||
.\lemon.exe $(REQ_FEATURE_FLAGS) $(OPT_FEATURE_FLAGS) $(EXT_FEATURE_FLAGS) $(OPTS) fts5parse.y
|
||||
|
||||
fts5parse.h: fts5parse.c
|
||||
fts5parse.h: fts5parse.c
|
||||
|
||||
fts5.c: $(FTS5_SRC)
|
||||
fts5.c: $(FTS5_SRC)
|
||||
$(TCLSH_CMD) $(TOP)\ext\fts5\tool\mkfts5c.tcl
|
||||
copy $(TOP)\ext\fts5\fts5.h .
|
||||
|
||||
@ -1913,6 +2063,9 @@ sqlite3rbu.lo: $(TOP)\ext\rbu\sqlite3rbu.c $(HDR) $(EXTHDR)
|
||||
TESTFIXTURE_FLAGS = -DTCLSH=1 -DSQLITE_TEST=1 -DSQLITE_CRASH_TEST=1
|
||||
TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_SERVER=1 -DSQLITE_PRIVATE=""
|
||||
TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_CORE $(NO_WARN)
|
||||
TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_SERIES_CONSTRAINT_VERIFY=1
|
||||
TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_DEFAULT_PAGE_SIZE=1024
|
||||
TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) $(TEST_CCONV_OPTS)
|
||||
|
||||
TESTFIXTURE_SRC0 = $(TESTEXT) $(TESTSRC2)
|
||||
TESTFIXTURE_SRC1 = $(TESTEXT) $(SQLITE3C)
|
||||
@ -1922,13 +2075,33 @@ TESTFIXTURE_SRC = $(TESTSRC) $(TOP)\src\tclsqlite.c $(TESTFIXTURE_SRC0)
|
||||
TESTFIXTURE_SRC = $(TESTSRC) $(TOP)\src\tclsqlite.c $(TESTFIXTURE_SRC1)
|
||||
!ENDIF
|
||||
|
||||
testfixture.exe: $(TESTFIXTURE_SRC) $(SQLITE3H) $(LIBRESOBJS) $(HDR)
|
||||
!IF $(USE_STDCALL)!=0 || $(FOR_WIN10)!=0
|
||||
sqlite_tclDecls.h:
|
||||
echo #ifndef SQLITE_TCLAPI > $(SQLITETCLDECLSH)
|
||||
echo # define SQLITE_TCLAPI >> $(SQLITETCLDECLSH)
|
||||
echo #endif >> $(SQLITETCLDECLSH)
|
||||
type "$(TCLINCDIR)\tclDecls.h" \
|
||||
| $(TCLSH_CMD) $(TOP)\tool\replace.tcl regsub "^(EXTERN(?: CONST\d+?)?\s+?[^\(]*?\s+?)Tcl_" "\1 SQLITE_TCLAPI Tcl_" \
|
||||
| $(TCLSH_CMD) $(TOP)\tool\replace.tcl regsub "^(EXTERN\s+?(?:void|VOID)\s+?)TclFreeObj" "\1 SQLITE_TCLAPI TclFreeObj" \
|
||||
| $(TCLSH_CMD) $(TOP)\tool\replace.tcl regsub "\(\*tcl_" "(SQLITE_TCLAPI *tcl_" \
|
||||
| $(TCLSH_CMD) $(TOP)\tool\replace.tcl regsub "\(\*tclFreeObj" "(SQLITE_TCLAPI *tclFreeObj" \
|
||||
| $(TCLSH_CMD) $(TOP)\tool\replace.tcl regsub "\(\*" "(SQLITE_TCLAPI *" >> $(SQLITETCLDECLSH)
|
||||
|
||||
sqlite_tcl.h:
|
||||
type "$(TCLINCDIR)\tcl.h" | $(TCLSH_CMD) $(TOP)\tool\replace.tcl exact tclDecls.h sqlite_tclDecls.h \
|
||||
| $(TCLSH_CMD) $(TOP)\tool\replace.tcl regsub "typedef (.*?)\(Tcl_" "typedef \1 (SQLITE_TCLAPI Tcl_" \
|
||||
| $(TCLSH_CMD) $(TOP)\tool\replace.tcl exact "void (*freeProc)" "void (SQLITE_TCLAPI *freeProc)" \
|
||||
| $(TCLSH_CMD) $(TOP)\tool\replace.tcl exact "Tcl_HashEntry *(*findProc)" "Tcl_HashEntry *(SQLITE_TCLAPI *findProc)" \
|
||||
| $(TCLSH_CMD) $(TOP)\tool\replace.tcl exact "Tcl_HashEntry *(*createProc)" "Tcl_HashEntry *(SQLITE_TCLAPI *createProc)" >> $(SQLITETCLH)
|
||||
!ENDIF
|
||||
|
||||
testfixture.exe: $(TESTFIXTURE_SRC) $(SQLITE3H) $(LIBRESOBJS) $(HDR) $(SQLITE_TCL_DEP)
|
||||
$(LTLINK) -DSQLITE_NO_SYNC=1 $(TESTFIXTURE_FLAGS) \
|
||||
-DBUILD_sqlite -I$(TCLINCDIR) \
|
||||
$(TESTFIXTURE_SRC) \
|
||||
/link $(LDFLAGS) $(LTLINKOPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LTLIBS) $(TLIBS)
|
||||
|
||||
extensiontest: testfixture.exe testloadext.dll
|
||||
extensiontest: testfixture.exe testloadext.dll
|
||||
@set PATH=$(LIBTCLPATH);$(PATH)
|
||||
.\testfixture.exe $(TOP)\test\loadext.test $(TESTOPTS)
|
||||
|
||||
@ -1971,7 +2144,7 @@ smoketest: $(TESTPROGS)
|
||||
@set PATH=$(LIBTCLPATH);$(PATH)
|
||||
.\testfixture.exe $(TOP)\test\main.test $(TESTOPTS)
|
||||
|
||||
sqlite3_analyzer.c: $(SQLITE3C) $(SQLITE3H) $(TOP)\src\tclsqlite.c $(TOP)\tool\spaceanal.tcl
|
||||
sqlite3_analyzer.c: $(SQLITE3C) $(SQLITE3H) $(TOP)\src\tclsqlite.c $(TOP)\tool\spaceanal.tcl $(SQLITE_TCL_DEP)
|
||||
echo #define TCLSH 2 > $@
|
||||
echo #define SQLITE_ENABLE_DBSTAT_VTAB 1 >> $@
|
||||
copy $@ + $(SQLITE3C) + $(TOP)\src\tclsqlite.c $@
|
||||
@ -1987,7 +2160,7 @@ sqlite3_analyzer.exe: sqlite3_analyzer.c $(LIBRESOBJS)
|
||||
testloadext.lo: $(TOP)\src\test_loadext.c
|
||||
$(LTCOMPILE) $(NO_WARN) -c $(TOP)\src\test_loadext.c
|
||||
|
||||
testloadext.dll: testloadext.lo
|
||||
testloadext.dll: testloadext.lo
|
||||
$(LD) $(LDFLAGS) $(LTLINKOPTS) $(LTLIBPATHS) /DLL /OUT:$@ testloadext.lo
|
||||
|
||||
showdb.exe: $(TOP)\tool\showdb.c $(SQLITE3C) $(SQLITE3H)
|
||||
@ -2006,6 +2179,10 @@ showwal.exe: $(TOP)\tool\showwal.c $(SQLITE3C) $(SQLITE3H)
|
||||
$(LTLINK) $(NO_WARN) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -Fe$@ \
|
||||
$(TOP)\tool\showwal.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
|
||||
|
||||
changeset.exe: $(TOP)\ext\session\changeset.c $(SQLITE3C)
|
||||
$(LTLINK) $(NO_WARN) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -Fe$@ \
|
||||
$(TOP)\ext\session\changeset.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
|
||||
|
||||
fts3view.exe: $(TOP)\ext\fts3\tool\fts3view.c $(SQLITE3C) $(SQLITE3H)
|
||||
$(LTLINK) $(NO_WARN) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -Fe$@ \
|
||||
$(TOP)\ext\fts3\tool\fts3view.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
|
||||
@ -2025,38 +2202,40 @@ speedtest1.exe: $(TOP)\test\speedtest1.c $(SQLITE3C) $(SQLITE3H)
|
||||
$(LTLINK) $(NO_WARN) -DSQLITE_OMIT_LOAD_EXTENSION -Fe$@ \
|
||||
$(TOP)\test\speedtest1.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
|
||||
|
||||
rbu.exe: $(TOP)\ext\rbu\rbu.c $(TOP)\ext\rbu\sqlite3rbu.c $(SQLITE3C) $(SQLITE3H)
|
||||
rbu.exe: $(TOP)\ext\rbu\rbu.c $(TOP)\ext\rbu\sqlite3rbu.c $(SQLITE3C) $(SQLITE3H)
|
||||
$(LTLINK) $(NO_WARN) -DSQLITE_ENABLE_RBU -Fe$@ \
|
||||
$(TOP)\ext\rbu\rbu.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
|
||||
|
||||
moreclean: clean
|
||||
del /Q $(SQLITE3C) $(SQLITE3H) 2>NUL
|
||||
# <</mark>>
|
||||
|
||||
clean:
|
||||
del /Q *.exp *.lo *.ilk *.lib *.obj *.ncb *.pdb *.sdf *.suo 2>NUL
|
||||
del /Q *.bsc *.cod *.da *.bb *.bbg *.vc gmon.out 2>NUL
|
||||
del /Q *.bsc *.def *.cod *.da *.bb *.bbg *.vc gmon.out 2>NUL
|
||||
del /Q $(SQLITE3EXE) $(SQLITE3DLL) Replace.exe 2>NUL
|
||||
# <<mark>>
|
||||
del /Q $(SQLITE3C) $(SQLITE3H) opcodes.c opcodes.h 2>NUL
|
||||
del /Q sqlite3.c sqlite3.h 2>NUL
|
||||
del /Q opcodes.c opcodes.h 2>NUL
|
||||
del /Q lemon.* lempar.c parse.* 2>NUL
|
||||
del /Q mkkeywordhash.* keywordhash.h 2>NUL
|
||||
del /Q notasharedlib.* 2>NUL
|
||||
-rmdir /Q/S .deps 2>NUL
|
||||
-rmdir /Q/S .libs 2>NUL
|
||||
-rmdir /Q/S quota2a 2>NUL
|
||||
-rmdir /Q/S quota2b 2>NUL
|
||||
-rmdir /Q/S quota2c 2>NUL
|
||||
-rmdir /Q/S tsrc 2>NUL
|
||||
del /Q .target_source 2>NUL
|
||||
del /Q tclsqlite3.exe 2>NUL
|
||||
del /Q tclsqlite3.exe $(SQLITETCLH) $(SQLITETCLDECLSH) 2>NUL
|
||||
del /Q testloadext.dll 2>NUL
|
||||
del /Q testfixture.exe test.db 2>NUL
|
||||
del /Q LogEst.exe fts3view.exe rollback-test.exe showdb.exe 2>NUL
|
||||
del /Q changeset.exe 2>NUL
|
||||
del /Q showjournal.exe showstat4.exe showwal.exe speedtest1.exe 2>NUL
|
||||
del /Q mptester.exe wordcount.exe rbu.exe srcck1.exe 2>NUL
|
||||
del /Q $(SQLITE3EXE) $(SQLITE3DLL) sqlite3.def 2>NUL
|
||||
del /Q sqlite3.c sqlite3-*.c 2>NUL
|
||||
del /Q sqlite3rc.h 2>NUL
|
||||
del /Q shell.c sqlite3ext.h 2>NUL
|
||||
del /Q shell.c sqlite3ext.h sqlite3session.h 2>NUL
|
||||
del /Q sqlite3_analyzer.exe sqlite3_analyzer.c 2>NUL
|
||||
del /Q sqlite-*-output.vsix 2>NUL
|
||||
del /Q fuzzershell.exe fuzzcheck.exe sqldiff.exe 2>NUL
|
||||
del /Q fuzzershell.exe fuzzcheck.exe sqldiff.exe dbhash.exe 2>NUL
|
||||
del /Q fts5.* fts5parse.* 2>NUL
|
||||
# <</mark>>
|
||||
|
||||
@ -1,19 +1,20 @@
|
||||
|
||||
AM_CFLAGS = @THREADSAFE_FLAGS@ @DYNAMIC_EXTENSION_FLAGS@ @FTS5_FLAGS@ @JSON1_FLAGS@ -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_RTREE
|
||||
AM_CFLAGS = @THREADSAFE_FLAGS@ @DYNAMIC_EXTENSION_FLAGS@ @FTS5_FLAGS@ @JSON1_FLAGS@ @SESSION_FLAGS@ -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_RTREE
|
||||
|
||||
lib_LTLIBRARIES = libsqlite3.la
|
||||
libsqlite3_la_SOURCES = sqlite3.c
|
||||
libsqlite3_la_LDFLAGS = -no-undefined -version-info 8:6:8
|
||||
|
||||
bin_PROGRAMS = sqlite3
|
||||
sqlite3_SOURCES = shell.c sqlite3.c sqlite3.h
|
||||
sqlite3_LDADD = @READLINE_LIBS@
|
||||
sqlite3_SOURCES = shell.c sqlite3.h
|
||||
EXTRA_sqlite3_SOURCES = sqlite3.c
|
||||
sqlite3_LDADD = @EXTRA_SHELL_OBJ@ @READLINE_LIBS@
|
||||
sqlite3_DEPENDENCIES = @EXTRA_SHELL_OBJ@
|
||||
sqlite3_CFLAGS = $(AM_CFLAGS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS
|
||||
|
||||
include_HEADERS = sqlite3.h sqlite3ext.h
|
||||
|
||||
EXTRA_DIST = sqlite3.1 tea Makefile.msc sqlite3.rc README.txt
|
||||
EXTRA_DIST = sqlite3.1 tea Makefile.msc sqlite3.rc README.txt Replace.cs
|
||||
pkgconfigdir = ${libdir}/pkgconfig
|
||||
pkgconfig_DATA = sqlite3.pc
|
||||
|
||||
|
||||
@ -24,6 +24,20 @@ TOP = .
|
||||
USE_FULLWARN = 0
|
||||
!ENDIF
|
||||
|
||||
# Set this non-0 to enable full runtime error checks (-RTC1, etc). This
|
||||
# has no effect if (any) optimizations are enabled.
|
||||
#
|
||||
!IFNDEF USE_RUNTIME_CHECKS
|
||||
USE_RUNTIME_CHECKS = 0
|
||||
!ENDIF
|
||||
|
||||
# Set this non-0 to create a SQLite amalgamation file that excludes the
|
||||
# various built-in extensions.
|
||||
#
|
||||
!IFNDEF MINIMAL_AMALGAMATION
|
||||
MINIMAL_AMALGAMATION = 0
|
||||
!ENDIF
|
||||
|
||||
# Set this non-0 to use "stdcall" calling convention for the core library
|
||||
# and shell executable.
|
||||
#
|
||||
@ -183,6 +197,12 @@ DEBUG = 0
|
||||
OPTIMIZATIONS = 2
|
||||
!ENDIF
|
||||
|
||||
# Set this to non-0 to enable support for the session extension.
|
||||
#
|
||||
!IFNDEF SESSION
|
||||
SESSION = 0
|
||||
!ENDIF
|
||||
|
||||
# Set the source code file to be used by executables and libraries when
|
||||
# they need the amalgamation.
|
||||
#
|
||||
@ -204,37 +224,64 @@ SQLITE3H = sqlite3.h
|
||||
# This is the name to use for the SQLite dynamic link library (DLL).
|
||||
#
|
||||
!IFNDEF SQLITE3DLL
|
||||
!IF $(FOR_WIN10)!=0
|
||||
SQLITE3DLL = winsqlite3.dll
|
||||
!ELSE
|
||||
SQLITE3DLL = sqlite3.dll
|
||||
!ENDIF
|
||||
!ENDIF
|
||||
|
||||
# This is the name to use for the SQLite import library (LIB).
|
||||
#
|
||||
!IFNDEF SQLITE3LIB
|
||||
!IF $(FOR_WIN10)!=0
|
||||
SQLITE3LIB = winsqlite3.lib
|
||||
!ELSE
|
||||
SQLITE3LIB = sqlite3.lib
|
||||
!ENDIF
|
||||
!ENDIF
|
||||
|
||||
# This is the name to use for the SQLite shell executable (EXE).
|
||||
#
|
||||
!IFNDEF SQLITE3EXE
|
||||
!IF $(FOR_WIN10)!=0
|
||||
SQLITE3EXE = winsqlite3shell.exe
|
||||
!ELSE
|
||||
SQLITE3EXE = sqlite3.exe
|
||||
!ENDIF
|
||||
!ENDIF
|
||||
|
||||
# This is the argument used to set the program database (PDB) file for the
|
||||
# SQLite shell executable (EXE).
|
||||
#
|
||||
!IFNDEF SQLITE3EXEPDB
|
||||
!IF $(FOR_WIN10)!=0
|
||||
SQLITE3EXEPDB =
|
||||
!ELSE
|
||||
SQLITE3EXEPDB = /pdb:sqlite3sh.pdb
|
||||
!ENDIF
|
||||
!ENDIF
|
||||
|
||||
|
||||
# These are the "standard" SQLite compilation options used when compiling for
|
||||
# the Windows platform.
|
||||
#
|
||||
!IFNDEF OPT_FEATURE_FLAGS
|
||||
!IF $(MINIMAL_AMALGAMATION)==0
|
||||
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS3=1
|
||||
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RTREE=1
|
||||
!ENDIF
|
||||
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_COLUMN_METADATA=1
|
||||
!ENDIF
|
||||
|
||||
# Should the session extension be enabled? If so, add compilation options
|
||||
# to enable it.
|
||||
#
|
||||
!IF $(SESSION)!=0
|
||||
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_SESSION=1
|
||||
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_PREUPDATE_HOOK=1
|
||||
!ENDIF
|
||||
|
||||
# These are the "extended" SQLite compilation options used when compiling for
|
||||
# the Windows 10 platform.
|
||||
#
|
||||
@ -275,6 +322,14 @@ PROGRAMFILES_X86 = $(PROGRAMFILES_X86:\\=\)
|
||||
CC = cl.exe
|
||||
!ENDIF
|
||||
|
||||
# Check for the predefined command macro CSC. This should point to a working
|
||||
# C Sharp compiler binary. If it is not defined, simply define it to the
|
||||
# legacy default value 'csc.exe'.
|
||||
#
|
||||
!IFNDEF CSC
|
||||
CSC = csc.exe
|
||||
!ENDIF
|
||||
|
||||
# Check for the command macro LD. This should point to the linker binary for
|
||||
# the target platform. If it is not defined, simply define it to the legacy
|
||||
# default value 'link.exe'.
|
||||
@ -402,15 +457,6 @@ TCC = $(CC) -nologo -W3 $(CCOPTS) $(TCCOPTS)
|
||||
TCC = $(TCC) -DSQLITE_OS_WIN=1 -I. -I$(TOP) -fp:precise
|
||||
RCC = $(RC) -DSQLITE_OS_WIN=1 -I. -I$(TOP) $(RCOPTS) $(RCCOPTS)
|
||||
|
||||
# Adjust the names of the primary targets for use with Windows 10.
|
||||
#
|
||||
!IF $(FOR_WIN10)!=0
|
||||
SQLITE3DLL = winsqlite3.dll
|
||||
SQLITE3LIB = winsqlite3.lib
|
||||
SQLITE3EXE = winsqlite3shell.exe
|
||||
SQLITE3EXEPDB =
|
||||
!ENDIF
|
||||
|
||||
# Check if we want to use the "stdcall" calling convention when compiling.
|
||||
# This is not supported by the compilers for non-x86 platforms. It should
|
||||
# also be noted here that building any target with these "stdcall" options
|
||||
@ -420,12 +466,12 @@ SQLITE3EXEPDB =
|
||||
#
|
||||
!IF $(USE_STDCALL)!=0 || $(FOR_WIN10)!=0
|
||||
!IF "$(PLATFORM)"=="x86"
|
||||
CORE_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_STDCALL=__stdcall
|
||||
SHELL_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_STDCALL=__stdcall
|
||||
CORE_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall
|
||||
SHELL_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall
|
||||
!ELSE
|
||||
!IFNDEF PLATFORM
|
||||
CORE_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_STDCALL=__stdcall
|
||||
SHELL_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_STDCALL=__stdcall
|
||||
CORE_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall
|
||||
SHELL_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_APICALL=__stdcall -DSQLITE_CALLBACK=__stdcall -DSQLITE_SYSAPI=__stdcall
|
||||
!ELSE
|
||||
CORE_CCONV_OPTS =
|
||||
SHELL_CCONV_OPTS =
|
||||
@ -450,8 +496,10 @@ CORE_COMPILE_OPTS = $(CORE_CCONV_OPTS)
|
||||
# when linking.
|
||||
#
|
||||
!IFNDEF CORE_LINK_DEP
|
||||
!IF $(DYNAMIC_SHELL)!=0 || $(FOR_WIN10)!=0
|
||||
!IF $(DYNAMIC_SHELL)!=0
|
||||
CORE_LINK_DEP =
|
||||
!ELSEIF $(FOR_WIN10)==0 || "$(PLATFORM)"=="x86"
|
||||
CORE_LINK_DEP = sqlite3.def
|
||||
!ELSE
|
||||
CORE_LINK_DEP =
|
||||
!ENDIF
|
||||
@ -460,8 +508,10 @@ CORE_LINK_DEP =
|
||||
# These are additional linker options used for the core library.
|
||||
#
|
||||
!IFNDEF CORE_LINK_OPTS
|
||||
!IF $(DYNAMIC_SHELL)!=0 || $(FOR_WIN10)!=0
|
||||
!IF $(DYNAMIC_SHELL)!=0
|
||||
CORE_LINK_OPTS =
|
||||
!ELSEIF $(FOR_WIN10)==0 || "$(PLATFORM)"=="x86"
|
||||
CORE_LINK_OPTS = /DEF:sqlite3.def
|
||||
!ELSE
|
||||
CORE_LINK_OPTS =
|
||||
!ENDIF
|
||||
@ -707,6 +757,10 @@ RCC = $(RCC) -D_DEBUG
|
||||
!IF $(DEBUG)>1 || $(OPTIMIZATIONS)==0
|
||||
TCC = $(TCC) -Od
|
||||
BCC = $(BCC) -Od
|
||||
!IF $(USE_RUNTIME_CHECKS)!=0
|
||||
TCC = $(TCC) -RTC1
|
||||
BCC = $(BCC) -RTC1
|
||||
!ENDIF
|
||||
!ELSEIF $(OPTIMIZATIONS)>=3
|
||||
TCC = $(TCC) -Ox
|
||||
BCC = $(BCC) -Ox
|
||||
@ -863,23 +917,28 @@ SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_SHELL_JSON1 -DSQLITE_ENABLE_
|
||||
# This is the default Makefile target. The objects listed here
|
||||
# are what get build when you type just "make" with no arguments.
|
||||
#
|
||||
all: dll libsqlite3.lib shell
|
||||
all: dll shell
|
||||
|
||||
# Dynamic link library section.
|
||||
#
|
||||
dll: $(SQLITE3DLL)
|
||||
dll: $(SQLITE3DLL)
|
||||
|
||||
# Shell executable.
|
||||
#
|
||||
shell: $(SQLITE3EXE)
|
||||
|
||||
libsqlite3.lib: $(LIBOBJ)
|
||||
$(LTLIB) $(LTLIBOPTS) /OUT:$@ $(LIBOBJ) $(TLIBS)
|
||||
shell: $(SQLITE3EXE)
|
||||
|
||||
|
||||
$(SQLITE3DLL): $(LIBOBJ) $(LIBRESOBJS) $(CORE_LINK_DEP)
|
||||
$(SQLITE3DLL): $(LIBOBJ) $(LIBRESOBJS) $(CORE_LINK_DEP)
|
||||
$(LD) $(LDFLAGS) $(LTLINKOPTS) $(LTLIBPATHS) /DLL $(CORE_LINK_OPTS) /OUT:$@ $(LIBOBJ) $(LIBRESOBJS) $(LTLIBS) $(TLIBS)
|
||||
|
||||
Replace.exe:
|
||||
$(CSC) /target:exe $(TOP)\Replace.cs
|
||||
|
||||
sqlite3.def: Replace.exe $(LIBOBJ)
|
||||
echo EXPORTS > sqlite3.def
|
||||
dumpbin /all $(LIBOBJ) \
|
||||
| .\Replace.exe "^\s+/EXPORT:_?(sqlite3_[^@,]*)(?:@\d+|,DATA)?$$" $$1 true \
|
||||
| sort >> sqlite3.def
|
||||
|
||||
$(SQLITE3EXE): $(TOP)\shell.c $(SHELL_CORE_DEP) $(LIBRESOBJS) $(SHELL_CORE_SRC) $(SQLITE3H)
|
||||
$(LTLINK) $(SHELL_COMPILE_OPTS) $(READLINE_FLAGS) $(TOP)\shell.c $(SHELL_CORE_SRC) \
|
||||
@ -918,4 +977,5 @@ $(LIBRESOBJS): $(TOP)\sqlite3.rc rcver.vc $(SQLITE3H)
|
||||
|
||||
clean:
|
||||
del /Q *.exp *.lo *.ilk *.lib *.obj *.ncb *.pdb *.sdf *.suo 2>NUL
|
||||
del /Q *.bsc *.cod *.da *.bb *.bbg *.vc gmon.out 2>NUL
|
||||
del /Q *.bsc *.def *.cod *.da *.bb *.bbg *.vc gmon.out 2>NUL
|
||||
del /Q $(SQLITE3EXE) $(SQLITE3DLL) Replace.exe 2>NUL
|
||||
|
||||
@ -6,8 +6,8 @@ This package contains:
|
||||
* the shell.c file used to build the sqlite3 command-line shell program
|
||||
* autoconf/automake installation infrastucture for building on POSIX
|
||||
compliant systems
|
||||
* a Makefile.msc and sqlite3.rc for building with Microsoft Visual C++ on
|
||||
Windows
|
||||
* a Makefile.msc, sqlite3.rc, and Replace.cs for building with Microsoft
|
||||
Visual C++ on Windows
|
||||
|
||||
SUMMARY OF HOW TO BUILD
|
||||
=======================
|
||||
|
||||
@ -30,39 +30,52 @@ AC_FUNC_STRERROR_R
|
||||
AC_CONFIG_FILES([Makefile sqlite3.pc])
|
||||
AC_SUBST(BUILD_CFLAGS)
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
#-------------------------------------------------------------------------
|
||||
# Two options to enable readline compatible libraries:
|
||||
#
|
||||
# --enable-editline
|
||||
# --enable-readline
|
||||
#
|
||||
AC_ARG_ENABLE(editline, [AS_HELP_STRING(
|
||||
[--enable-editline],
|
||||
[use BSD libedit])],
|
||||
[], [enable_editline=yes])
|
||||
AC_ARG_ENABLE(readline, [AS_HELP_STRING(
|
||||
[--enable-readline],
|
||||
[use readline])],
|
||||
[], [enable_readline=no])
|
||||
if test x"$enable_editline" != xno ; then
|
||||
sLIBS=$LIBS
|
||||
LIBS=""
|
||||
AC_SEARCH_LIBS([readline],[edit],[enable_readline=no],[enable_editline=no])
|
||||
READLINE_LIBS=$LIBS
|
||||
if test x"$LIBS" != "x"; then
|
||||
AC_DEFINE([HAVE_EDITLINE],1,Define to use BSD editline)
|
||||
else
|
||||
unset ac_cv_search_readline
|
||||
fi
|
||||
LIBS=$sLIBS
|
||||
fi
|
||||
if test x"$enable_readline" != xno ; then
|
||||
sLIBS=$LIBS
|
||||
LIBS=""
|
||||
AC_SEARCH_LIBS(tgetent, curses ncurses ncursesw, [], [])
|
||||
AC_SEARCH_LIBS(readline, readline, [], [enable_readline=no])
|
||||
AC_CHECK_FUNCS(readline, [], [])
|
||||
READLINE_LIBS=$LIBS
|
||||
LIBS=$sLIBS
|
||||
fi
|
||||
# Both are enabled by default. If, after command line processing both are
|
||||
# still enabled, the script searches for editline first and automatically
|
||||
# disables readline if it is found. So, to use readline explicitly, the
|
||||
# user must pass "--disable-editline". To disable command line editing
|
||||
# support altogether, "--disable-editline --disable-readline".
|
||||
#
|
||||
# When searching for either library, check for headers before libraries
|
||||
# as some distros supply packages that contain libraries but not header
|
||||
# files, which come as a separate development package.
|
||||
#
|
||||
AC_ARG_ENABLE(editline, [AS_HELP_STRING([--enable-editline],[use BSD libedit])])
|
||||
AC_ARG_ENABLE(readline, [AS_HELP_STRING([--enable-readline],[use readline])])
|
||||
|
||||
AS_IF([ test x"$enable_editline" != xno ],[
|
||||
AC_CHECK_HEADERS([editline/readline.h],[
|
||||
sLIBS=$LIBS
|
||||
LIBS=""
|
||||
AC_SEARCH_LIBS([readline],[edit],[
|
||||
AC_DEFINE([HAVE_EDITLINE],1,Define to use BSD editline)
|
||||
READLINE_LIBS=$LIBS
|
||||
enable_readline=no
|
||||
])
|
||||
AS_UNSET(ac_cv_search_readline)
|
||||
LIBS=$sLIBS
|
||||
])
|
||||
])
|
||||
|
||||
AS_IF([ test x"$enable_readline" != xno ],[
|
||||
AC_CHECK_HEADERS([readline/readline.h],[
|
||||
sLIBS=$LIBS
|
||||
LIBS=""
|
||||
AC_SEARCH_LIBS(tgetent, termcap curses ncurses ncursesw, [], [])
|
||||
AC_SEARCH_LIBS(readline,[readline edit], [
|
||||
AC_DEFINE([HAVE_READLINE],1,Define to use readline or wrapper)
|
||||
READLINE_LIBS=$LIBS
|
||||
])
|
||||
LIBS=$sLIBS
|
||||
])
|
||||
])
|
||||
|
||||
AC_SUBST(READLINE_LIBS)
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
@ -103,7 +116,7 @@ AC_SUBST(DYNAMIC_EXTENSION_FLAGS)
|
||||
AC_ARG_ENABLE(fts5, [AS_HELP_STRING(
|
||||
[--enable-fts5], [include fts5 support [default=no]])],
|
||||
[], [enable_fts5=no])
|
||||
if test x"$enable_fts5" == "xyes"; then
|
||||
if test x"$enable_fts5" = "xyes"; then
|
||||
AC_SEARCH_LIBS(log, m)
|
||||
FTS5_FLAGS=-DSQLITE_ENABLE_FTS5
|
||||
fi
|
||||
@ -116,12 +129,24 @@ AC_SUBST(FTS5_FLAGS)
|
||||
AC_ARG_ENABLE(json1, [AS_HELP_STRING(
|
||||
[--enable-json1], [include json1 support [default=no]])],
|
||||
[], [enable_json1=no])
|
||||
if test x"$enable_json1" == "xyes"; then
|
||||
if test x"$enable_json1" = "xyes"; then
|
||||
JSON1_FLAGS=-DSQLITE_ENABLE_JSON1
|
||||
fi
|
||||
AC_SUBST(JSON1_FLAGS)
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# --enable-session
|
||||
#
|
||||
AC_ARG_ENABLE(session, [AS_HELP_STRING(
|
||||
[--enable-session], [enable the session extension [default=no]])],
|
||||
[], [enable_session=no])
|
||||
if test x"$enable_session" = "xyes"; then
|
||||
SESSION_FLAGS="-DSQLITE_ENABLE_SESSION -DSQLITE_ENABLE_PREUPDATE_HOOK"
|
||||
fi
|
||||
AC_SUBST(SESSION_FLAGS)
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# --enable-static-shell
|
||||
#
|
||||
@ -129,8 +154,8 @@ AC_ARG_ENABLE(static-shell, [AS_HELP_STRING(
|
||||
[--enable-static-shell],
|
||||
[statically link libsqlite3 into shell tool [default=yes]])],
|
||||
[], [enable_static_shell=yes])
|
||||
if test x"$enable_static_shell" == "xyes"; then
|
||||
EXTRA_SHELL_OBJ=sqlite3.$OBJEXT
|
||||
if test x"$enable_static_shell" = "xyes"; then
|
||||
EXTRA_SHELL_OBJ=sqlite3-sqlite3.$OBJEXT
|
||||
else
|
||||
EXTRA_SHELL_OBJ=libsqlite3.la
|
||||
fi
|
||||
|
||||
@ -78,7 +78,6 @@ TEA_ADD_LIBS([])
|
||||
TEA_ADD_CFLAGS([-DSQLITE_ENABLE_FTS3=1])
|
||||
TEA_ADD_CFLAGS([-DSQLITE_3_SUFFIX_ONLY=1])
|
||||
TEA_ADD_CFLAGS([-DSQLITE_ENABLE_RTREE=1])
|
||||
TEA_ADD_CFLAGS([-DSQLITE_OMIT_DEPRECATED=1])
|
||||
TEA_ADD_STUB_SOURCES([])
|
||||
TEA_ADD_TCL_SOURCES([])
|
||||
|
||||
|
||||
12
config.h.in
12
config.h.in
@ -51,6 +51,18 @@
|
||||
/* Define to 1 if you have the <memory.h> header file. */
|
||||
#undef HAVE_MEMORY_H
|
||||
|
||||
/* Define to 1 if you have the `pread' function. */
|
||||
#undef HAVE_PREAD
|
||||
|
||||
/* Define to 1 if you have the `pread64' function. */
|
||||
#undef HAVE_PREAD64
|
||||
|
||||
/* Define to 1 if you have the `pwrite' function. */
|
||||
#undef HAVE_PWRITE
|
||||
|
||||
/* Define to 1 if you have the `pwrite64' function. */
|
||||
#undef HAVE_PWRITE64
|
||||
|
||||
/* Define to 1 if you have the <stdint.h> header file. */
|
||||
#undef HAVE_STDINT_H
|
||||
|
||||
|
||||
92
configure
vendored
92
configure
vendored
@ -1,6 +1,6 @@
|
||||
#! /bin/sh
|
||||
# Guess values for system-dependent variables and create Makefiles.
|
||||
# Generated by GNU Autoconf 2.69 for sqlcipher 3.11.0.
|
||||
# Generated by GNU Autoconf 2.69 for sqlcipher 3.15.2.
|
||||
#
|
||||
#
|
||||
# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
|
||||
@ -587,8 +587,8 @@ MAKEFLAGS=
|
||||
# Identity of this package.
|
||||
PACKAGE_NAME='sqlcipher'
|
||||
PACKAGE_TARNAME='sqlcipher'
|
||||
PACKAGE_VERSION='3.11.0'
|
||||
PACKAGE_STRING='sqlcipher 3.11.0'
|
||||
PACKAGE_VERSION='3.15.2'
|
||||
PACKAGE_STRING='sqlcipher 3.15.2'
|
||||
PACKAGE_BUGREPORT=''
|
||||
PACKAGE_URL=''
|
||||
|
||||
@ -773,11 +773,14 @@ with_readline_inc
|
||||
enable_debug
|
||||
enable_amalgamation
|
||||
enable_load_extension
|
||||
enable_memsys5
|
||||
enable_memsys3
|
||||
enable_fts3
|
||||
enable_fts4
|
||||
enable_fts5
|
||||
enable_json1
|
||||
enable_rtree
|
||||
enable_session
|
||||
enable_gcov
|
||||
'
|
||||
ac_precious_vars='build_alias
|
||||
@ -1331,7 +1334,7 @@ if test "$ac_init_help" = "long"; then
|
||||
# Omit some internal or obsolete options to make the list less imposing.
|
||||
# This message is too long to be a string in the A/UX 3.1 sh.
|
||||
cat <<_ACEOF
|
||||
\`configure' configures sqlcipher 3.11.0 to adapt to many kinds of systems.
|
||||
\`configure' configures sqlcipher 3.15.2 to adapt to many kinds of systems.
|
||||
|
||||
Usage: $0 [OPTION]... [VAR=VALUE]...
|
||||
|
||||
@ -1396,7 +1399,7 @@ fi
|
||||
|
||||
if test -n "$ac_init_help"; then
|
||||
case $ac_init_help in
|
||||
short | recursive ) echo "Configuration of sqlcipher 3.11.0:";;
|
||||
short | recursive ) echo "Configuration of sqlcipher 3.15.2:";;
|
||||
esac
|
||||
cat <<\_ACEOF
|
||||
|
||||
@ -1424,11 +1427,14 @@ Optional Features:
|
||||
separately
|
||||
--disable-load-extension
|
||||
Disable loading of external extensions
|
||||
--enable-memsys5 Enable MEMSYS5
|
||||
--enable-memsys3 Enable MEMSYS3
|
||||
--enable-fts3 Enable the FTS3 extension
|
||||
--enable-fts4 Enable the FTS4 extension
|
||||
--enable-fts5 Enable the FTS5 extension
|
||||
--enable-json1 Enable the JSON1 extension
|
||||
--enable-rtree Enable the RTREE extension
|
||||
--enable-session Enable the SESSION extension
|
||||
--enable-gcov Enable coverage testing using gcov
|
||||
|
||||
Optional Packages:
|
||||
@ -1527,7 +1533,7 @@ fi
|
||||
test -n "$ac_init_help" && exit $ac_status
|
||||
if $ac_init_version; then
|
||||
cat <<\_ACEOF
|
||||
sqlcipher configure 3.11.0
|
||||
sqlcipher configure 3.15.2
|
||||
generated by GNU Autoconf 2.69
|
||||
|
||||
Copyright (C) 2012 Free Software Foundation, Inc.
|
||||
@ -1946,7 +1952,7 @@ cat >config.log <<_ACEOF
|
||||
This file contains any messages produced by compilers while
|
||||
running configure, to aid debugging if configure makes a mistake.
|
||||
|
||||
It was created by sqlcipher $as_me 3.11.0, which was
|
||||
It was created by sqlcipher $as_me 3.15.2, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
$ $0 $@
|
||||
@ -11642,7 +11648,7 @@ done
|
||||
#########
|
||||
# Figure out whether or not we have these functions
|
||||
#
|
||||
for ac_func in fdatasync gmtime_r isnan localtime_r localtime_s malloc_usable_size strchrnul usleep utime
|
||||
for ac_func in fdatasync gmtime_r isnan localtime_r localtime_s malloc_usable_size strchrnul usleep utime pread pread64 pwrite pwrite64
|
||||
do :
|
||||
as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
|
||||
ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
|
||||
@ -12277,6 +12283,20 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
# Recent versions of Xcode on Macs hid the tclConfig.sh file
|
||||
# in a strange place.
|
||||
if test x"${ac_cv_c_tclconfig}" = x ; then
|
||||
if test x"$cross_compiling" = xno; then
|
||||
for i in /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX*.sdk/usr/lib
|
||||
do
|
||||
if test -f "$i/tclConfig.sh" ; then
|
||||
ac_cv_c_tclconfig="$i"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
# then check for a private Tcl installation
|
||||
if test x"${ac_cv_c_tclconfig}" = x ; then
|
||||
for i in \
|
||||
@ -12845,6 +12865,44 @@ else
|
||||
OPT_FEATURE_FLAGS="-DSQLITE_OMIT_LOAD_EXTENSION=1"
|
||||
fi
|
||||
|
||||
##########
|
||||
# Do we want to support memsys3 and/or memsys5
|
||||
#
|
||||
# Check whether --enable-memsys5 was given.
|
||||
if test "${enable_memsys5+set}" = set; then :
|
||||
enableval=$enable_memsys5; enable_memsys5=yes
|
||||
else
|
||||
enable_memsys5=no
|
||||
fi
|
||||
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to support MEMSYS5" >&5
|
||||
$as_echo_n "checking whether to support MEMSYS5... " >&6; }
|
||||
if test "${enable_memsys5}" = "yes"; then
|
||||
OPT_FEATURE_FLAGS+=" -DSQLITE_ENABLE_MEMSYS5"
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
|
||||
$as_echo "yes" >&6; }
|
||||
else
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
|
||||
$as_echo "no" >&6; }
|
||||
fi
|
||||
# Check whether --enable-memsys3 was given.
|
||||
if test "${enable_memsys3+set}" = set; then :
|
||||
enableval=$enable_memsys3; enable_memsys3=yes
|
||||
else
|
||||
enable_memsys3=no
|
||||
fi
|
||||
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to support MEMSYS3" >&5
|
||||
$as_echo_n "checking whether to support MEMSYS3... " >&6; }
|
||||
if test "${enable_memsys3}" = "yes" -a "${enable_memsys5}" = "no"; then
|
||||
OPT_FEATURE_FLAGS+=" -DSQLITE_ENABLE_MEMSYS3"
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
|
||||
$as_echo "yes" >&6; }
|
||||
else
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
|
||||
$as_echo "no" >&6; }
|
||||
fi
|
||||
|
||||
#########
|
||||
# See whether we should enable Full Text Search extensions
|
||||
# Check whether --enable-fts3 was given.
|
||||
@ -13016,6 +13074,20 @@ if test "${enable_rtree}" = "yes" ; then
|
||||
OPT_FEATURE_FLAGS+=" -DSQLITE_ENABLE_RTREE"
|
||||
fi
|
||||
|
||||
#########
|
||||
# See whether we should enable the SESSION extension
|
||||
# Check whether --enable-session was given.
|
||||
if test "${enable_session+set}" = set; then :
|
||||
enableval=$enable_session; enable_session=yes
|
||||
else
|
||||
enable_session=no
|
||||
fi
|
||||
|
||||
if test "${enable_session}" = "yes" ; then
|
||||
OPT_FEATURE_FLAGS+=" -DSQLITE_ENABLE_SESSION"
|
||||
OPT_FEATURE_FLAGS+=" -DSQLITE_ENABLE_PREUPDATE_HOOK"
|
||||
fi
|
||||
|
||||
#########
|
||||
# attempt to duplicate any OMITS and ENABLES into the $(OPT_FEATURE_FLAGS) parameter
|
||||
for option in $CFLAGS $CPPFLAGS
|
||||
@ -13601,7 +13673,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
|
||||
# report actual input values of CONFIG_FILES etc. instead of their
|
||||
# values after options handling.
|
||||
ac_log="
|
||||
This file was extended by sqlcipher $as_me 3.11.0, which was
|
||||
This file was extended by sqlcipher $as_me 3.15.2, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
CONFIG_FILES = $CONFIG_FILES
|
||||
@ -13667,7 +13739,7 @@ _ACEOF
|
||||
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
|
||||
ac_cs_version="\\
|
||||
sqlcipher config.status 3.11.0
|
||||
sqlcipher config.status 3.15.2
|
||||
configured by $0, generated by GNU Autoconf 2.69,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
|
||||
50
configure.ac
50
configure.ac
@ -108,7 +108,7 @@ AC_CHECK_HEADERS([sys/types.h stdlib.h stdint.h inttypes.h malloc.h])
|
||||
#########
|
||||
# Figure out whether or not we have these functions
|
||||
#
|
||||
AC_CHECK_FUNCS([fdatasync gmtime_r isnan localtime_r localtime_s malloc_usable_size strchrnul usleep utime])
|
||||
AC_CHECK_FUNCS([fdatasync gmtime_r isnan localtime_r localtime_s malloc_usable_size strchrnul usleep utime pread pread64 pwrite pwrite64])
|
||||
|
||||
#########
|
||||
# By default, we use the amalgamation (this may be changed below...)
|
||||
@ -381,6 +381,20 @@ if test "${use_tcl}" = "yes" ; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Recent versions of Xcode on Macs hid the tclConfig.sh file
|
||||
# in a strange place.
|
||||
if test x"${ac_cv_c_tclconfig}" = x ; then
|
||||
if test x"$cross_compiling" = xno; then
|
||||
for i in /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX*.sdk/usr/lib
|
||||
do
|
||||
if test -f "$i/tclConfig.sh" ; then
|
||||
ac_cv_c_tclconfig="$i"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
# then check for a private Tcl installation
|
||||
if test x"${ac_cv_c_tclconfig}" = x ; then
|
||||
for i in \
|
||||
@ -621,6 +635,30 @@ else
|
||||
OPT_FEATURE_FLAGS="-DSQLITE_OMIT_LOAD_EXTENSION=1"
|
||||
fi
|
||||
|
||||
##########
|
||||
# Do we want to support memsys3 and/or memsys5
|
||||
#
|
||||
AC_ARG_ENABLE(memsys5,
|
||||
AC_HELP_STRING([--enable-memsys5],[Enable MEMSYS5]),
|
||||
[enable_memsys5=yes],[enable_memsys5=no])
|
||||
AC_MSG_CHECKING([whether to support MEMSYS5])
|
||||
if test "${enable_memsys5}" = "yes"; then
|
||||
OPT_FEATURE_FLAGS+=" -DSQLITE_ENABLE_MEMSYS5"
|
||||
AC_MSG_RESULT([yes])
|
||||
else
|
||||
AC_MSG_RESULT([no])
|
||||
fi
|
||||
AC_ARG_ENABLE(memsys3,
|
||||
AC_HELP_STRING([--enable-memsys3],[Enable MEMSYS3]),
|
||||
[enable_memsys3=yes],[enable_memsys3=no])
|
||||
AC_MSG_CHECKING([whether to support MEMSYS3])
|
||||
if test "${enable_memsys3}" = "yes" -a "${enable_memsys5}" = "no"; then
|
||||
OPT_FEATURE_FLAGS+=" -DSQLITE_ENABLE_MEMSYS3"
|
||||
AC_MSG_RESULT([yes])
|
||||
else
|
||||
AC_MSG_RESULT([no])
|
||||
fi
|
||||
|
||||
#########
|
||||
# See whether we should enable Full Text Search extensions
|
||||
AC_ARG_ENABLE(fts3, AC_HELP_STRING([--enable-fts3],
|
||||
@ -662,6 +700,16 @@ if test "${enable_rtree}" = "yes" ; then
|
||||
OPT_FEATURE_FLAGS+=" -DSQLITE_ENABLE_RTREE"
|
||||
fi
|
||||
|
||||
#########
|
||||
# See whether we should enable the SESSION extension
|
||||
AC_ARG_ENABLE(session, AC_HELP_STRING([--enable-session],
|
||||
[Enable the SESSION extension]),
|
||||
[enable_session=yes],[enable_session=no])
|
||||
if test "${enable_session}" = "yes" ; then
|
||||
OPT_FEATURE_FLAGS+=" -DSQLITE_ENABLE_SESSION"
|
||||
OPT_FEATURE_FLAGS+=" -DSQLITE_ENABLE_PREUPDATE_HOOK"
|
||||
fi
|
||||
|
||||
#########
|
||||
# attempt to duplicate any OMITS and ENABLES into the $(OPT_FEATURE_FLAGS) parameter
|
||||
for option in $CFLAGS $CPPFLAGS
|
||||
|
||||
291
doc/lemon.html
291
doc/lemon.html
@ -5,14 +5,17 @@
|
||||
<body bgcolor=white>
|
||||
<h1 align=center>The Lemon Parser Generator</h1>
|
||||
|
||||
<p>Lemon is an LALR(1) parser generator for C or C++.
|
||||
It does the same job as ``bison'' and ``yacc''.
|
||||
But lemon is not another bison or yacc clone. It
|
||||
<p>Lemon is an LALR(1) parser generator for C.
|
||||
It does the same job as "bison" and "yacc".
|
||||
But lemon is not a bison or yacc clone. Lemon
|
||||
uses a different grammar syntax which is designed to
|
||||
reduce the number of coding errors. Lemon also uses a more
|
||||
sophisticated parsing engine that is faster than yacc and
|
||||
bison and which is both reentrant and thread-safe.
|
||||
Furthermore, Lemon implements features that can be used
|
||||
reduce the number of coding errors. Lemon also uses a
|
||||
parsing engine that is faster than yacc and
|
||||
bison and which is both reentrant and threadsafe.
|
||||
(Update: Since the previous sentence was written, bison
|
||||
has also been updated so that it too can generate a
|
||||
reentrant and threadsafe parser.)
|
||||
Lemon also implements features that can be used
|
||||
to eliminate resource leaks, making is suitable for use
|
||||
in long-running programs such as graphical user interfaces
|
||||
or embedded controllers.</p>
|
||||
@ -44,18 +47,18 @@ one and three files of outputs.
|
||||
automaton.
|
||||
</ul>
|
||||
By default, all three of these output files are generated.
|
||||
The header file is suppressed if the ``-m'' command-line option is
|
||||
used and the report file is omitted when ``-q'' is selected.</p>
|
||||
The header file is suppressed if the "-m" command-line option is
|
||||
used and the report file is omitted when "-q" is selected.</p>
|
||||
|
||||
<p>The grammar specification file uses a ``.y'' suffix, by convention.
|
||||
<p>The grammar specification file uses a ".y" suffix, by convention.
|
||||
In the examples used in this document, we'll assume the name of the
|
||||
grammar file is ``gram.y''. A typical use of Lemon would be the
|
||||
grammar file is "gram.y". A typical use of Lemon would be the
|
||||
following command:
|
||||
<pre>
|
||||
lemon gram.y
|
||||
</pre>
|
||||
This command will generate three output files named ``gram.c'',
|
||||
``gram.h'' and ``gram.out''.
|
||||
This command will generate three output files named "gram.c",
|
||||
"gram.h" and "gram.out".
|
||||
The first is C code to implement the parser. The second
|
||||
is the header file that defines numerical values for all
|
||||
terminal symbols, and the last is the report that explains
|
||||
@ -71,39 +74,35 @@ with a brief explanation of what each does by typing
|
||||
</pre>
|
||||
As of this writing, the following command-line options are supported:
|
||||
<ul>
|
||||
<li><tt>-b</tt>
|
||||
<li><tt>-c</tt>
|
||||
<li><tt>-g</tt>
|
||||
<li><tt>-m</tt>
|
||||
<li><tt>-q</tt>
|
||||
<li><tt>-s</tt>
|
||||
<li><tt>-x</tt>
|
||||
<li><b>-b</b>
|
||||
Show only the basis for each parser state in the report file.
|
||||
<li><b>-c</b>
|
||||
Do not compress the generated action tables.
|
||||
<li><b>-D<i>name</i></b>
|
||||
Define C preprocessor macro <i>name</i>. This macro is useable by
|
||||
"%ifdef" lines in the grammar file.
|
||||
<li><b>-g</b>
|
||||
Do not generate a parser. Instead write the input grammar to standard
|
||||
output with all comments, actions, and other extraneous text removed.
|
||||
<li><b>-l</b>
|
||||
Omit "#line" directives in the generated parser C code.
|
||||
<li><b>-m</b>
|
||||
Cause the output C source code to be compatible with the "makeheaders"
|
||||
program.
|
||||
<li><b>-p</b>
|
||||
Display all conflicts that are resolved by
|
||||
<a href='#precrules'>precedence rules</a>.
|
||||
<li><b>-q</b>
|
||||
Suppress generation of the report file.
|
||||
<li><b>-r</b>
|
||||
Do not sort or renumber the parser states as part of optimization.
|
||||
<li><b>-s</b>
|
||||
Show parser statistics before existing.
|
||||
<li><b>-T<i>file</i></b>
|
||||
Use <i>file</i> as the template for the generated C-code parser implementation.
|
||||
<li><b>-x</b>
|
||||
Print the Lemon version number.
|
||||
</ul>
|
||||
The ``-b'' option reduces the amount of text in the report file by
|
||||
printing only the basis of each parser state, rather than the full
|
||||
configuration.
|
||||
The ``-c'' option suppresses action table compression. Using -c
|
||||
will make the parser a little larger and slower but it will detect
|
||||
syntax errors sooner.
|
||||
The ``-g'' option causes no output files to be generated at all.
|
||||
Instead, the input grammar file is printed on standard output but
|
||||
with all comments, actions and other extraneous text deleted. This
|
||||
is a useful way to get a quick summary of a grammar.
|
||||
The ``-m'' option causes the output C source file to be compatible
|
||||
with the ``makeheaders'' program.
|
||||
Makeheaders is a program that automatically generates header files
|
||||
from C source code. When the ``-m'' option is used, the header
|
||||
file is not output since the makeheaders program will take care
|
||||
of generated all header files automatically.
|
||||
The ``-q'' option suppresses the report file.
|
||||
Using ``-s'' causes a brief summary of parser statistics to be
|
||||
printed. Like this:
|
||||
<pre>
|
||||
Parser statistics: 74 terminals, 70 nonterminals, 179 rules
|
||||
340 states, 2026 parser table entries, 0 conflicts
|
||||
</pre>
|
||||
Finally, the ``-x'' option causes Lemon to print its version number
|
||||
and then stops without attempting to read the grammar or generate a parser.</p>
|
||||
|
||||
<h3>The Parser Interface</h3>
|
||||
|
||||
@ -121,12 +120,12 @@ A new parser is created as follows:
|
||||
</pre>
|
||||
The ParseAlloc() routine allocates and initializes a new parser and
|
||||
returns a pointer to it.
|
||||
The actual data structure used to represent a parser is opaque --
|
||||
The actual data structure used to represent a parser is opaque —
|
||||
its internal structure is not visible or usable by the calling routine.
|
||||
For this reason, the ParseAlloc() routine returns a pointer to void
|
||||
rather than a pointer to some particular structure.
|
||||
The sole argument to the ParseAlloc() routine is a pointer to the
|
||||
subroutine used to allocate memory. Typically this means ``malloc()''.</p>
|
||||
subroutine used to allocate memory. Typically this means malloc().</p>
|
||||
|
||||
<p>After a program is finished using a parser, it can reclaim all
|
||||
memory allocated by that parser by calling
|
||||
@ -151,18 +150,19 @@ type of the next token in the data stream.
|
||||
There is one token type for each terminal symbol in the grammar.
|
||||
The gram.h file generated by Lemon contains #define statements that
|
||||
map symbolic terminal symbol names into appropriate integer values.
|
||||
(A value of 0 for the second argument is a special flag to the
|
||||
parser to indicate that the end of input has been reached.)
|
||||
A value of 0 for the second argument is a special flag to the
|
||||
parser to indicate that the end of input has been reached.
|
||||
The third argument is the value of the given token. By default,
|
||||
the type of the third argument is integer, but the grammar will
|
||||
usually redefine this type to be some kind of structure.
|
||||
Typically the second argument will be a broad category of tokens
|
||||
such as ``identifier'' or ``number'' and the third argument will
|
||||
such as "identifier" or "number" and the third argument will
|
||||
be the name of the identifier or the value of the number.</p>
|
||||
|
||||
<p>The Parse() function may have either three or four arguments,
|
||||
depending on the grammar. If the grammar specification file request
|
||||
it, the Parse() function will have a fourth parameter that can be
|
||||
depending on the grammar. If the grammar specification file requests
|
||||
it (via the <a href='#extraarg'><tt>extra_argument</tt> directive</a>),
|
||||
the Parse() function will have a fourth parameter that can be
|
||||
of any type chosen by the programmer. The parser doesn't do anything
|
||||
with this argument except to pass it through to action routines.
|
||||
This is a convenient mechanism for passing state information down
|
||||
@ -192,7 +192,7 @@ following:
|
||||
</pre>
|
||||
This example shows a user-written routine that parses a file of
|
||||
text and returns a pointer to the parse tree.
|
||||
(We've omitted all error-handling from this example to keep it
|
||||
(All error-handling code is omitted from this example to keep it
|
||||
simple.)
|
||||
We assume the existence of some kind of tokenizer which is created
|
||||
using TokenizerCreate() on line 8 and deleted by TokenizerFree()
|
||||
@ -263,6 +263,12 @@ with prior yacc and bison experience.
|
||||
But after years of experience using Lemon, I firmly
|
||||
believe that the Lemon way of doing things is better.</p>
|
||||
|
||||
<p><i>Updated as of 2016-02-16:</i>
|
||||
The text above was written in the 1990s.
|
||||
We are told that Bison has lately been enhanced to support the
|
||||
tokenizer-calls-parser paradigm used by Lemon, and to obviate the
|
||||
need for global variables.</p>
|
||||
|
||||
<h2>Input File Syntax</h2>
|
||||
|
||||
<p>The main purpose of the grammar specification file for Lemon is
|
||||
@ -280,7 +286,7 @@ tokens) and it honors the same commenting conventions as C and C++.</p>
|
||||
<h3>Terminals and Nonterminals</h3>
|
||||
|
||||
<p>A terminal symbol (token) is any string of alphanumeric
|
||||
and underscore characters
|
||||
and/or underscore characters
|
||||
that begins with an upper case letter.
|
||||
A terminal can contain lowercase letters after the first character,
|
||||
but the usual convention is to make terminals all upper case.
|
||||
@ -307,7 +313,7 @@ must have alphanumeric names.</p>
|
||||
<p>The main component of a Lemon grammar file is a sequence of grammar
|
||||
rules.
|
||||
Each grammar rule consists of a nonterminal symbol followed by
|
||||
the special symbol ``::='' and then a list of terminals and/or nonterminals.
|
||||
the special symbol "::=" and then a list of terminals and/or nonterminals.
|
||||
The rule is terminated by a period.
|
||||
The list of terminals and nonterminals on the right-hand side of the
|
||||
rule can be empty.
|
||||
@ -323,9 +329,9 @@ A typical sequence of grammar rules might look something like this:
|
||||
</pre>
|
||||
</p>
|
||||
|
||||
<p>There is one non-terminal in this example, ``expr'', and five
|
||||
terminal symbols or tokens: ``PLUS'', ``TIMES'', ``LPAREN'',
|
||||
``RPAREN'' and ``VALUE''.</p>
|
||||
<p>There is one non-terminal in this example, "expr", and five
|
||||
terminal symbols or tokens: "PLUS", "TIMES", "LPAREN",
|
||||
"RPAREN" and "VALUE".</p>
|
||||
|
||||
<p>Like yacc and bison, Lemon allows the grammar to specify a block
|
||||
of C code that will be executed whenever a grammar rule is reduced
|
||||
@ -341,15 +347,15 @@ For example:
|
||||
|
||||
<p>In order to be useful, grammar actions must normally be linked to
|
||||
their associated grammar rules.
|
||||
In yacc and bison, this is accomplished by embedding a ``$$'' in the
|
||||
In yacc and bison, this is accomplished by embedding a "$$" in the
|
||||
action to stand for the value of the left-hand side of the rule and
|
||||
symbols ``$1'', ``$2'', and so forth to stand for the value of
|
||||
symbols "$1", "$2", and so forth to stand for the value of
|
||||
the terminal or nonterminal at position 1, 2 and so forth on the
|
||||
right-hand side of the rule.
|
||||
This idea is very powerful, but it is also very error-prone. The
|
||||
single most common source of errors in a yacc or bison grammar is
|
||||
to miscount the number of symbols on the right-hand side of a grammar
|
||||
rule and say ``$7'' when you really mean ``$8''.</p>
|
||||
rule and say "$7" when you really mean "$8".</p>
|
||||
|
||||
<p>Lemon avoids the need to count grammar symbols by assigning symbolic
|
||||
names to each symbol in a grammar rule and then using those symbolic
|
||||
@ -379,7 +385,7 @@ For example, the rule
|
||||
<pre>
|
||||
expr(A) ::= expr(B) PLUS expr(C). { A = B; }
|
||||
</pre>
|
||||
will generate an error because the linking symbol ``C'' is used
|
||||
will generate an error because the linking symbol "C" is used
|
||||
in the grammar rule but not in the reduce action.</p>
|
||||
|
||||
<p>The Lemon notation for linking grammar rules to reduce actions
|
||||
@ -387,6 +393,7 @@ also facilitates the use of destructors for reclaiming memory
|
||||
allocated by the values of terminals and nonterminals on the
|
||||
right-hand side of a rule.</p>
|
||||
|
||||
<a name='precrules'></a>
|
||||
<h3>Precedence Rules</h3>
|
||||
|
||||
<p>Lemon resolves parsing ambiguities in exactly the same way as
|
||||
@ -398,7 +405,10 @@ whichever rule comes first in the grammar file.</p>
|
||||
yacc and bison, Lemon allows a measure of control
|
||||
over the resolution of paring conflicts using precedence rules.
|
||||
A precedence value can be assigned to any terminal symbol
|
||||
using the %left, %right or %nonassoc directives. Terminal symbols
|
||||
using the
|
||||
<a href='#pleft'>%left</a>,
|
||||
<a href='#pright'>%right</a> or
|
||||
<a href='#pnonassoc'>%nonassoc</a> directives. Terminal symbols
|
||||
mentioned in earlier directives have a lower precedence that
|
||||
terminal symbols mentioned in later directives. For example:</p>
|
||||
|
||||
@ -518,7 +528,11 @@ other than that, the order of directives in Lemon is arbitrary.</p>
|
||||
<li><tt>%default_destructor</tt>
|
||||
<li><tt>%default_type</tt>
|
||||
<li><tt>%destructor</tt>
|
||||
<li><tt>%endif</tt>
|
||||
<li><tt>%extra_argument</tt>
|
||||
<li><tt>%fallback</tt>
|
||||
<li><tt>%ifdef</tt>
|
||||
<li><tt>%ifndef</tt>
|
||||
<li><tt>%include</tt>
|
||||
<li><tt>%left</tt>
|
||||
<li><tt>%name</tt>
|
||||
@ -530,49 +544,57 @@ other than that, the order of directives in Lemon is arbitrary.</p>
|
||||
<li><tt>%stack_size</tt>
|
||||
<li><tt>%start_symbol</tt>
|
||||
<li><tt>%syntax_error</tt>
|
||||
<li><tt>%token_class</tt>
|
||||
<li><tt>%token_destructor</tt>
|
||||
<li><tt>%token_prefix</tt>
|
||||
<li><tt>%token_type</tt>
|
||||
<li><tt>%type</tt>
|
||||
<li><tt>%wildcard</tt>
|
||||
</ul>
|
||||
Each of these directives will be described separately in the
|
||||
following sections:</p>
|
||||
|
||||
<a name='pcode'></a>
|
||||
<h4>The <tt>%code</tt> directive</h4>
|
||||
|
||||
<p>The %code directive is used to specify addition C/C++ code that
|
||||
<p>The %code directive is used to specify addition C code that
|
||||
is added to the end of the main output file. This is similar to
|
||||
the %include directive except that %include is inserted at the
|
||||
beginning of the main output file.</p>
|
||||
the <a href='#pinclude'>%include</a> directive except that %include
|
||||
is inserted at the beginning of the main output file.</p>
|
||||
|
||||
<p>%code is typically used to include some action routines or perhaps
|
||||
a tokenizer as part of the output file.</p>
|
||||
a tokenizer or even the "main()" function
|
||||
as part of the output file.</p>
|
||||
|
||||
<a name='default_destructor'></a>
|
||||
<h4>The <tt>%default_destructor</tt> directive</h4>
|
||||
|
||||
<p>The %default_destructor directive specifies a destructor to
|
||||
use for non-terminals that do not have their own destructor
|
||||
specified by a separate %destructor directive. See the documentation
|
||||
on the %destructor directive below for additional information.</p>
|
||||
on the <a name='#destructor'>%destructor</a> directive below for
|
||||
additional information.</p>
|
||||
|
||||
<p>In some grammers, many different non-terminal symbols have the
|
||||
same datatype and hence the same destructor. This directive is
|
||||
a convenience way to specify the same destructor for all those
|
||||
non-terminals using a single statement.</p>
|
||||
|
||||
<a name='default_type'></a>
|
||||
<h4>The <tt>%default_type</tt> directive</h4>
|
||||
|
||||
<p>The %default_type directive specifies the datatype of non-terminal
|
||||
symbols that do no have their own datatype defined using a separate
|
||||
%type directive. See the documentation on %type below for addition
|
||||
information.</p>
|
||||
<a href='#ptype'>%type</a> directive.
|
||||
</p>
|
||||
|
||||
<a name='destructor'></a>
|
||||
<h4>The <tt>%destructor</tt> directive</h4>
|
||||
|
||||
<p>The %destructor directive is used to specify a destructor for
|
||||
a non-terminal symbol.
|
||||
(See also the %token_destructor directive which is used to
|
||||
specify a destructor for terminal symbols.)</p>
|
||||
(See also the <a href='#token_destructor'>%token_destructor</a>
|
||||
directive which is used to specify a destructor for terminal symbols.)</p>
|
||||
|
||||
<p>A non-terminal's destructor is called to dispose of the
|
||||
non-terminal's value whenever the non-terminal is popped from
|
||||
@ -595,28 +617,28 @@ or other resources held by that non-terminal.</p>
|
||||
</pre>
|
||||
This example is a bit contrived but it serves to illustrate how
|
||||
destructors work. The example shows a non-terminal named
|
||||
``nt'' that holds values of type ``void*''. When the rule for
|
||||
an ``nt'' reduces, it sets the value of the non-terminal to
|
||||
"nt" that holds values of type "void*". When the rule for
|
||||
an "nt" reduces, it sets the value of the non-terminal to
|
||||
space obtained from malloc(). Later, when the nt non-terminal
|
||||
is popped from the stack, the destructor will fire and call
|
||||
free() on this malloced space, thus avoiding a memory leak.
|
||||
(Note that the symbol ``$$'' in the destructor code is replaced
|
||||
(Note that the symbol "$$" in the destructor code is replaced
|
||||
by the value of the non-terminal.)</p>
|
||||
|
||||
<p>It is important to note that the value of a non-terminal is passed
|
||||
to the destructor whenever the non-terminal is removed from the
|
||||
stack, unless the non-terminal is used in a C-code action. If
|
||||
the non-terminal is used by C-code, then it is assumed that the
|
||||
C-code will take care of destroying it if it should really
|
||||
be destroyed. More commonly, the value is used to build some
|
||||
C-code will take care of destroying it.
|
||||
More commonly, the value is used to build some
|
||||
larger structure and we don't want to destroy it, which is why
|
||||
the destructor is not called in this circumstance.</p>
|
||||
|
||||
<p>By appropriate use of destructors, it is possible to
|
||||
build a parser using Lemon that can be used within a long-running
|
||||
program, such as a GUI, that will not leak memory or other resources.
|
||||
<p>Destructors help avoid memory leaks by automatically freeing
|
||||
allocated objects when they go out of scope.
|
||||
To do the same using yacc or bison is much more difficult.</p>
|
||||
|
||||
<a name="extraarg"></a>
|
||||
<h4>The <tt>%extra_argument</tt> directive</h4>
|
||||
|
||||
The %extra_argument directive instructs Lemon to add a 4th parameter
|
||||
@ -630,17 +652,66 @@ and so forth. For example, if the grammar file contains:</p>
|
||||
</pre></p>
|
||||
|
||||
<p>Then the Parse() function generated will have an 4th parameter
|
||||
of type ``MyStruct*'' and all action routines will have access to
|
||||
a variable named ``pAbc'' that is the value of the 4th parameter
|
||||
of type "MyStruct*" and all action routines will have access to
|
||||
a variable named "pAbc" that is the value of the 4th parameter
|
||||
in the most recent call to Parse().</p>
|
||||
|
||||
<a name='pfallback'></a>
|
||||
<h4>The <tt>%fallback</tt> directive</h4>
|
||||
|
||||
<p>The %fallback directive specifies an alternative meaning for one
|
||||
or more tokens. The alternative meaning is tried if the original token
|
||||
would have generated a syntax error.
|
||||
|
||||
<p>The %fallback directive was added to support robust parsing of SQL
|
||||
syntax in <a href="https://www.sqlite.org/">SQLite</a>.
|
||||
The SQL language contains a large assortment of keywords, each of which
|
||||
appears as a different token to the language parser. SQL contains so
|
||||
many keywords, that it can be difficult for programmers to keep up with
|
||||
them all. Programmers will, therefore, sometimes mistakenly use an
|
||||
obscure language keyword for an identifier. The %fallback directive
|
||||
provides a mechanism to tell the parser: "If you are unable to parse
|
||||
this keyword, try treating it as an identifier instead."
|
||||
|
||||
<p>The syntax of %fallback is as follows:
|
||||
|
||||
<blockquote>
|
||||
<tt>%fallback</tt> <i>ID</i> <i>TOKEN...</i> <b>.</b>
|
||||
</blockquote>
|
||||
|
||||
<p>In words, the %fallback directive is followed by a list of token names
|
||||
terminated by a period. The first token name is the fallback token - the
|
||||
token to which all the other tokens fall back to. The second and subsequent
|
||||
arguments are tokens which fall back to the token identified by the first
|
||||
argument.
|
||||
|
||||
<a name='pifdef'></a>
|
||||
<h4>The <tt>%ifdef</tt>, <tt>%ifndef</tt>, and <tt>%endif</tt> directives.</h4>
|
||||
|
||||
<p>The %ifdef, %ifndef, and %endif directives are similar to
|
||||
#ifdef, #ifndef, and #endif in the C-preprocessor, just not as general.
|
||||
Each of these directives must begin at the left margin. No whitespace
|
||||
is allowed between the "%" and the directive name.
|
||||
|
||||
<p>Grammar text in between "%ifdef MACRO" and the next nested "%endif" is
|
||||
ignored unless the "-DMACRO" command-line option is used. Grammar text
|
||||
betwen "%ifndef MACRO" and the next nested "%endif" is included except when
|
||||
the "-DMACRO" command-line option is used.
|
||||
|
||||
<p>Note that the argument to %ifdef and %ifndef must be a single
|
||||
preprocessor symbol name, not a general expression. There is no "%else"
|
||||
directive.
|
||||
|
||||
|
||||
<a name='pinclude'></a>
|
||||
<h4>The <tt>%include</tt> directive</h4>
|
||||
|
||||
<p>The %include directive specifies C code that is included at the
|
||||
top of the generated parser. You can include any text you want --
|
||||
the Lemon parser generator copies it blindly. If you have multiple
|
||||
%include directives in your grammar file the value of the last
|
||||
%include directive overwrites all the others.</p.
|
||||
%include directives in your grammar file, their values are concatenated
|
||||
so that all %include code ultimately appears near the top of the
|
||||
generated parser, in the same order as it appeared in the grammer.</p>
|
||||
|
||||
<p>The %include directive is very handy for getting some extra #include
|
||||
preprocessor statements at the beginning of the generated parser.
|
||||
@ -653,12 +724,13 @@ For example:</p>
|
||||
<p>This might be needed, for example, if some of the C actions in the
|
||||
grammar call functions that are prototyed in unistd.h.</p>
|
||||
|
||||
<a name='pleft'></a>
|
||||
<h4>The <tt>%left</tt> directive</h4>
|
||||
|
||||
The %left directive is used (along with the %right and
|
||||
%nonassoc directives) to declare precedences of terminal
|
||||
symbols. Every terminal symbol whose name appears after
|
||||
a %left directive but before the next period (``.'') is
|
||||
The %left directive is used (along with the <a href='#pright'>%right</a> and
|
||||
<a href='#pnonassoc'>%nonassoc</a> directives) to declare precedences of
|
||||
terminal symbols. Every terminal symbol whose name appears after
|
||||
a %left directive but before the next period (".") is
|
||||
given the same left-associative precedence value. Subsequent
|
||||
%left directives have higher precedence. For example:</p>
|
||||
|
||||
@ -679,10 +751,11 @@ a large amount of stack space if you make heavy use or right-associative
|
||||
operators. For this reason, it is recommended that you use %left
|
||||
rather than %right whenever possible.</p>
|
||||
|
||||
<a name='pname'></a>
|
||||
<h4>The <tt>%name</tt> directive</h4>
|
||||
|
||||
<p>By default, the functions generated by Lemon all begin with the
|
||||
five-character string ``Parse''. You can change this string to something
|
||||
five-character string "Parse". You can change this string to something
|
||||
different using the %name directive. For instance:</p>
|
||||
|
||||
<p><pre>
|
||||
@ -701,16 +774,19 @@ The %name directive allows you to generator two or more different
|
||||
parsers and link them all into the same executable.
|
||||
</p>
|
||||
|
||||
<a name='pnonassoc'></a>
|
||||
<h4>The <tt>%nonassoc</tt> directive</h4>
|
||||
|
||||
<p>This directive is used to assign non-associative precedence to
|
||||
one or more terminal symbols. See the section on precedence rules
|
||||
or on the %left directive for additional information.</p>
|
||||
one or more terminal symbols. See the section on
|
||||
<a href='#precrules'>precedence rules</a>
|
||||
or on the <a href='#pleft'>%left</a> directive for additional information.</p>
|
||||
|
||||
<a name='parse_accept'></a>
|
||||
<h4>The <tt>%parse_accept</tt> directive</h4>
|
||||
|
||||
<p>The %parse_accept directive specifies a block of C code that is
|
||||
executed whenever the parser accepts its input string. To ``accept''
|
||||
executed whenever the parser accepts its input string. To "accept"
|
||||
an input string means that the parser was able to process all tokens
|
||||
without error.</p>
|
||||
|
||||
@ -722,7 +798,7 @@ without error.</p>
|
||||
}
|
||||
</pre></p>
|
||||
|
||||
|
||||
<a name='parse_failure'></a>
|
||||
<h4>The <tt>%parse_failure</tt> directive</h4>
|
||||
|
||||
<p>The %parse_failure directive specifies a block of C code that
|
||||
@ -737,12 +813,15 @@ only invoked when parsing is unable to continue.</p>
|
||||
}
|
||||
</pre></p>
|
||||
|
||||
<a name='pright'></a>
|
||||
<h4>The <tt>%right</tt> directive</h4>
|
||||
|
||||
<p>This directive is used to assign right-associative precedence to
|
||||
one or more terminal symbols. See the section on precedence rules
|
||||
or on the %left directive for additional information.</p>
|
||||
one or more terminal symbols. See the section on
|
||||
<a href='#precrules'>precedence rules</a>
|
||||
or on the <a href='#pleft'>%left</a> directive for additional information.</p>
|
||||
|
||||
<a name='stack_overflow'></a>
|
||||
<h4>The <tt>%stack_overflow</tt> directive</h4>
|
||||
|
||||
<p>The %stack_overflow directive specifies a block of C code that
|
||||
@ -771,6 +850,7 @@ Not like this:
|
||||
list ::= .
|
||||
</pre>
|
||||
|
||||
<a name='stack_size'></a>
|
||||
<h4>The <tt>%stack_size</tt> directive</h4>
|
||||
|
||||
<p>If stack overflow is a problem and you can't resolve the trouble
|
||||
@ -783,6 +863,7 @@ with a stack of the requested size. The default value is 100.</p>
|
||||
%stack_size 2000
|
||||
</pre></p>
|
||||
|
||||
<a name='start_symbol'></a>
|
||||
<h4>The <tt>%start_symbol</tt> directive</h4>
|
||||
|
||||
<p>By default, the start-symbol for the grammar that Lemon generates
|
||||
@ -793,6 +874,7 @@ can choose a different start-symbol using the %start_symbol directive.</p>
|
||||
%start_symbol prog
|
||||
</pre></p>
|
||||
|
||||
<a name='token_destructor'></a>
|
||||
<h4>The <tt>%token_destructor</tt> directive</h4>
|
||||
|
||||
<p>The %destructor directive assigns a destructor to a non-terminal
|
||||
@ -805,6 +887,7 @@ the %token_type directive) and so they use a common destructor. Other
|
||||
than that, the token destructor works just like the non-terminal
|
||||
destructors.</p>
|
||||
|
||||
<a name='token_prefix'></a>
|
||||
<h4>The <tt>%token_prefix</tt> directive</h4>
|
||||
|
||||
<p>Lemon generates #defines that assign small integer constants
|
||||
@ -830,6 +913,7 @@ to cause Lemon to produce these symbols instead:
|
||||
#define TOKEN_PLUS 4
|
||||
</pre>
|
||||
|
||||
<a name='token_type'></a><a name='ptype'></a>
|
||||
<h4>The <tt>%token_type</tt> and <tt>%type</tt> directives</h4>
|
||||
|
||||
<p>These directives are used to specify the data types for values
|
||||
@ -845,7 +929,7 @@ token structure. Like this:</p>
|
||||
</pre></p>
|
||||
|
||||
<p>If the data type of terminals is not specified, the default value
|
||||
is ``int''.</p>
|
||||
is "void*".</p>
|
||||
|
||||
<p>Non-terminal symbols can each have their own data types. Typically
|
||||
the data type of a non-terminal is a pointer to the root of a parse-tree
|
||||
@ -866,6 +950,17 @@ non-terminal whose data type requires 1K of storage, then your 100
|
||||
entry parser stack will require 100K of heap space. If you are willing
|
||||
and able to pay that price, fine. You just need to know.</p>
|
||||
|
||||
<a name='pwildcard'></a>
|
||||
<h4>The <tt>%wildcard</tt> directive</h4>
|
||||
|
||||
<p>The %wildcard directive is followed by a single token name and a
|
||||
period. This directive specifies that the identified token should
|
||||
match any input token.
|
||||
|
||||
<p>When the generated parser has the choice of matching an input against
|
||||
the wildcard token and some other token, the other token is always used.
|
||||
The wildcard token is only matched if there are no other alternatives.
|
||||
|
||||
<h3>Error Processing</h3>
|
||||
|
||||
<p>After extensive experimentation over several years, it has been
|
||||
@ -877,7 +972,7 @@ first invokes the code specified by the %syntax_error directive, if
|
||||
any. It then enters its error recovery strategy. The error recovery
|
||||
strategy is to begin popping the parsers stack until it enters a
|
||||
state where it is permitted to shift a special non-terminal symbol
|
||||
named ``error''. It then shifts this non-terminal and continues
|
||||
named "error". It then shifts this non-terminal and continues
|
||||
parsing. But the %syntax_error routine will not be called again
|
||||
until at least three new tokens have been successfully shifted.</p>
|
||||
|
||||
@ -886,7 +981,7 @@ is unable to shift the error symbol, then the %parse_failed routine
|
||||
is invoked and the parser resets itself to its start state, ready
|
||||
to begin parsing a new file. This is what will happen at the very
|
||||
first syntax error, of course, if there are no instances of the
|
||||
``error'' non-terminal in your grammar.</p>
|
||||
"error" non-terminal in your grammar.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -99,7 +99,11 @@ static void scalarFunc(
|
||||
|
||||
#ifdef SQLITE_TEST
|
||||
|
||||
#include <tcl.h>
|
||||
#if defined(INCLUDE_SQLITE_TCL_H)
|
||||
# include "sqlite_tcl.h"
|
||||
#else
|
||||
# include "tcl.h"
|
||||
#endif
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
|
||||
@ -18,7 +18,14 @@
|
||||
** that the sqlite3_tokenizer_module.xLanguage() method is invoked correctly.
|
||||
*/
|
||||
|
||||
#include <tcl.h>
|
||||
#if defined(INCLUDE_SQLITE_TCL_H)
|
||||
# include "sqlite_tcl.h"
|
||||
#else
|
||||
# include "tcl.h"
|
||||
# ifndef SQLITE_TCLAPI
|
||||
# define SQLITE_TCLAPI
|
||||
# endif
|
||||
#endif
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
@ -143,7 +150,7 @@ static int nm_match_count(
|
||||
/*
|
||||
** Tclcmd: fts3_near_match DOCUMENT EXPR ?OPTIONS?
|
||||
*/
|
||||
static int fts3_near_match_cmd(
|
||||
static int SQLITE_TCLAPI fts3_near_match_cmd(
|
||||
ClientData clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
@ -278,7 +285,7 @@ static int fts3_near_match_cmd(
|
||||
** # Restore initial incr-load settings:
|
||||
** eval fts3_configure_incr_load $cfg
|
||||
*/
|
||||
static int fts3_configure_incr_load_cmd(
|
||||
static int SQLITE_TCLAPI fts3_configure_incr_load_cmd(
|
||||
ClientData clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
@ -458,7 +465,7 @@ static int testTokenizerNext(
|
||||
if( pCsr->iLangid & 0x00000001 ){
|
||||
for(i=0; i<nToken; i++) pCsr->aBuffer[i] = pToken[i];
|
||||
}else{
|
||||
for(i=0; i<nToken; i++) pCsr->aBuffer[i] = testTolower(pToken[i]);
|
||||
for(i=0; i<nToken; i++) pCsr->aBuffer[i] = (char)testTolower(pToken[i]);
|
||||
}
|
||||
pCsr->iToken++;
|
||||
pCsr->iInput = (int)(p - pCsr->aInput);
|
||||
@ -488,7 +495,7 @@ static int testTokenizerLanguage(
|
||||
}
|
||||
#endif
|
||||
|
||||
static int fts3_test_tokenizer_cmd(
|
||||
static int SQLITE_TCLAPI fts3_test_tokenizer_cmd(
|
||||
ClientData clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
@ -517,7 +524,7 @@ static int fts3_test_tokenizer_cmd(
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
static int fts3_test_varint_cmd(
|
||||
static int SQLITE_TCLAPI fts3_test_varint_cmd(
|
||||
ClientData clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
@ -526,7 +533,8 @@ static int fts3_test_varint_cmd(
|
||||
#ifdef SQLITE_ENABLE_FTS3
|
||||
char aBuf[24];
|
||||
int rc;
|
||||
Tcl_WideInt w, w2;
|
||||
Tcl_WideInt w;
|
||||
sqlite3_int64 w2;
|
||||
int nByte, nByte2;
|
||||
|
||||
if( objc!=2 ){
|
||||
|
||||
@ -29,6 +29,18 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
** Return true if the two-argument version of fts3_tokenizer()
|
||||
** has been activated via a prior call to sqlite3_db_config(db,
|
||||
** SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, 1, 0);
|
||||
*/
|
||||
static int fts3TokenizerEnabled(sqlite3_context *context){
|
||||
sqlite3 *db = sqlite3_context_db_handle(context);
|
||||
int isEnabled = 0;
|
||||
sqlite3_db_config(db,SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER,-1,&isEnabled);
|
||||
return isEnabled;
|
||||
}
|
||||
|
||||
/*
|
||||
** Implementation of the SQL scalar function for accessing the underlying
|
||||
** hash table. This function may be called as follows:
|
||||
@ -49,7 +61,7 @@
|
||||
** is a blob containing the pointer stored as the hash data corresponding
|
||||
** to string <key-name> (after the hash-table is updated, if applicable).
|
||||
*/
|
||||
static void scalarFunc(
|
||||
static void fts3TokenizerFunc(
|
||||
sqlite3_context *context,
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
@ -67,27 +79,23 @@ static void scalarFunc(
|
||||
nName = sqlite3_value_bytes(argv[0])+1;
|
||||
|
||||
if( argc==2 ){
|
||||
#ifdef SQLITE_ENABLE_FTS3_TOKENIZER
|
||||
void *pOld;
|
||||
int n = sqlite3_value_bytes(argv[1]);
|
||||
if( zName==0 || n!=sizeof(pPtr) ){
|
||||
sqlite3_result_error(context, "argument type mismatch", -1);
|
||||
if( fts3TokenizerEnabled(context) ){
|
||||
void *pOld;
|
||||
int n = sqlite3_value_bytes(argv[1]);
|
||||
if( zName==0 || n!=sizeof(pPtr) ){
|
||||
sqlite3_result_error(context, "argument type mismatch", -1);
|
||||
return;
|
||||
}
|
||||
pPtr = *(void **)sqlite3_value_blob(argv[1]);
|
||||
pOld = sqlite3Fts3HashInsert(pHash, (void *)zName, nName, pPtr);
|
||||
if( pOld==pPtr ){
|
||||
sqlite3_result_error(context, "out of memory", -1);
|
||||
}
|
||||
}else{
|
||||
sqlite3_result_error(context, "fts3tokenize disabled", -1);
|
||||
return;
|
||||
}
|
||||
pPtr = *(void **)sqlite3_value_blob(argv[1]);
|
||||
pOld = sqlite3Fts3HashInsert(pHash, (void *)zName, nName, pPtr);
|
||||
if( pOld==pPtr ){
|
||||
sqlite3_result_error(context, "out of memory", -1);
|
||||
return;
|
||||
}
|
||||
#else
|
||||
sqlite3_result_error(context, "fts3tokenize: "
|
||||
"disabled - rebuild with -DSQLITE_ENABLE_FTS3_TOKENIZER", -1
|
||||
);
|
||||
return;
|
||||
#endif /* SQLITE_ENABLE_FTS3_TOKENIZER */
|
||||
}else
|
||||
{
|
||||
}else{
|
||||
if( zName ){
|
||||
pPtr = sqlite3Fts3HashFind(pHash, zName, nName);
|
||||
}
|
||||
@ -98,7 +106,6 @@ static void scalarFunc(
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sqlite3_result_blob(context, (void *)&pPtr, sizeof(pPtr), SQLITE_TRANSIENT);
|
||||
}
|
||||
|
||||
@ -217,7 +224,11 @@ int sqlite3Fts3InitTokenizer(
|
||||
|
||||
#ifdef SQLITE_TEST
|
||||
|
||||
#include <tcl.h>
|
||||
#if defined(INCLUDE_SQLITE_TCL_H)
|
||||
# include "sqlite_tcl.h"
|
||||
#else
|
||||
# include "tcl.h"
|
||||
#endif
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
@ -336,7 +347,6 @@ finish:
|
||||
Tcl_DecrRefCount(pRet);
|
||||
}
|
||||
|
||||
#ifdef SQLITE_ENABLE_FTS3_TOKENIZER
|
||||
static
|
||||
int registerTokenizer(
|
||||
sqlite3 *db,
|
||||
@ -358,7 +368,6 @@ int registerTokenizer(
|
||||
|
||||
return sqlite3_finalize(pStmt);
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_FTS3_TOKENIZER */
|
||||
|
||||
|
||||
static
|
||||
@ -431,13 +440,13 @@ static void intTestFunc(
|
||||
assert( 0==strcmp(sqlite3_errmsg(db), "unknown tokenizer: nosuchtokenizer") );
|
||||
|
||||
/* Test the storage function */
|
||||
#ifdef SQLITE_ENABLE_FTS3_TOKENIZER
|
||||
rc = registerTokenizer(db, "nosuchtokenizer", p1);
|
||||
assert( rc==SQLITE_OK );
|
||||
rc = queryTokenizer(db, "nosuchtokenizer", &p2);
|
||||
assert( rc==SQLITE_OK );
|
||||
assert( p2==p1 );
|
||||
#endif
|
||||
if( fts3TokenizerEnabled(context) ){
|
||||
rc = registerTokenizer(db, "nosuchtokenizer", p1);
|
||||
assert( rc==SQLITE_OK );
|
||||
rc = queryTokenizer(db, "nosuchtokenizer", &p2);
|
||||
assert( rc==SQLITE_OK );
|
||||
assert( p2==p1 );
|
||||
}
|
||||
|
||||
sqlite3_result_text(context, "ok", -1, SQLITE_STATIC);
|
||||
}
|
||||
@ -453,7 +462,7 @@ static void intTestFunc(
|
||||
** sqlite3Fts3HashInit(pHash, FTS3_HASH_STRING, 1);
|
||||
**
|
||||
** This function adds a scalar function (see header comment above
|
||||
** scalarFunc() in this file for details) and, if ENABLE_TABLE is
|
||||
** fts3TokenizerFunc() in this file for details) and, if ENABLE_TABLE is
|
||||
** defined at compilation time, a temporary virtual table (see header
|
||||
** comment above struct HashTableVtab) to the database schema. Both
|
||||
** provide read/write access to the contents of *pHash.
|
||||
@ -482,10 +491,10 @@ int sqlite3Fts3InitHashTable(
|
||||
#endif
|
||||
|
||||
if( SQLITE_OK==rc ){
|
||||
rc = sqlite3_create_function(db, zName, 1, any, p, scalarFunc, 0, 0);
|
||||
rc = sqlite3_create_function(db, zName, 1, any, p, fts3TokenizerFunc, 0, 0);
|
||||
}
|
||||
if( SQLITE_OK==rc ){
|
||||
rc = sqlite3_create_function(db, zName, 2, any, p, scalarFunc, 0, 0);
|
||||
rc = sqlite3_create_function(db, zName, 2, any, p, fts3TokenizerFunc, 0, 0);
|
||||
}
|
||||
#ifdef SQLITE_TEST
|
||||
if( SQLITE_OK==rc ){
|
||||
|
||||
@ -333,7 +333,8 @@ static int fts3SqlStmt(
|
||||
** of the oldest level in the db that contains at least ? segments. Or,
|
||||
** if no level in the FTS index contains more than ? segments, the statement
|
||||
** returns zero rows. */
|
||||
/* 28 */ "SELECT level FROM %Q.'%q_segdir' GROUP BY level HAVING count(*)>=?"
|
||||
/* 28 */ "SELECT level, count(*) AS cnt FROM %Q.'%q_segdir' "
|
||||
" GROUP BY level HAVING cnt>=?"
|
||||
" ORDER BY (level %% 1024) ASC LIMIT 1",
|
||||
|
||||
/* Estimate the upper limit on the number of leaf nodes in a new segment
|
||||
@ -3194,7 +3195,7 @@ static int fts3SegmentMerge(
|
||||
** segment. The level of the new segment is equal to the numerically
|
||||
** greatest segment level currently present in the database for this
|
||||
** index. The idx of the new segment is always 0. */
|
||||
if( csr.nSegment==1 ){
|
||||
if( csr.nSegment==1 && 0==fts3SegReaderIsPending(csr.apSegment[0]) ){
|
||||
rc = SQLITE_DONE;
|
||||
goto finished;
|
||||
}
|
||||
@ -4836,10 +4837,11 @@ int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){
|
||||
** set nSeg to -1.
|
||||
*/
|
||||
rc = fts3SqlStmt(p, SQL_FIND_MERGE_LEVEL, &pFindLevel, 0);
|
||||
sqlite3_bind_int(pFindLevel, 1, nMin);
|
||||
sqlite3_bind_int(pFindLevel, 1, MAX(2, nMin));
|
||||
if( sqlite3_step(pFindLevel)==SQLITE_ROW ){
|
||||
iAbsLevel = sqlite3_column_int64(pFindLevel, 0);
|
||||
nSeg = nMin;
|
||||
nSeg = sqlite3_column_int(pFindLevel, 1);
|
||||
assert( nSeg>=2 );
|
||||
}else{
|
||||
nSeg = -1;
|
||||
}
|
||||
|
||||
@ -398,8 +398,8 @@ static void showSegmentStats(sqlite3 *db, const char *zTab){
|
||||
if( sqlite3_step(pStmt)==SQLITE_ROW
|
||||
&& (nLeaf = sqlite3_column_int(pStmt, 0))>0
|
||||
){
|
||||
nIdx = sqlite3_column_int(pStmt, 5);
|
||||
sqlite3_int64 sz;
|
||||
nIdx = sqlite3_column_int(pStmt, 5);
|
||||
printf("For level %d:\n", i);
|
||||
printf(" Number of indexes...................... %9d\n", nIdx);
|
||||
printf(" Number of leaf segments................ %9d\n", nLeaf);
|
||||
|
||||
@ -143,11 +143,13 @@ struct Fts5PhraseIter {
|
||||
** ... FROM ftstable WHERE ftstable MATCH $p ORDER BY rowid
|
||||
**
|
||||
** with $p set to a phrase equivalent to the phrase iPhrase of the
|
||||
** current query is executed. For each row visited, the callback function
|
||||
** passed as the fourth argument is invoked. The context and API objects
|
||||
** passed to the callback function may be used to access the properties of
|
||||
** each matched row. Invoking Api.xUserData() returns a copy of the pointer
|
||||
** passed as the third argument to pUserData.
|
||||
** current query is executed. Any column filter that applies to
|
||||
** phrase iPhrase of the current query is included in $p. For each
|
||||
** row visited, the callback function passed as the fourth argument
|
||||
** is invoked. The context and API objects passed to the callback
|
||||
** function may be used to access the properties of each matched row.
|
||||
** Invoking Api.xUserData() returns a copy of the pointer passed as
|
||||
** the third argument to pUserData.
|
||||
**
|
||||
** If the callback function returns any value other than SQLITE_OK, the
|
||||
** query is abandoned and the xQueryPhrase function returns immediately.
|
||||
@ -316,7 +318,7 @@ struct Fts5ExtensionApi {
|
||||
** behaviour. The structure methods are expected to function as follows:
|
||||
**
|
||||
** xCreate:
|
||||
** This function is used to allocate and inititalize a tokenizer instance.
|
||||
** This function is used to allocate and initialize a tokenizer instance.
|
||||
** A tokenizer instance is required to actually tokenize text.
|
||||
**
|
||||
** The first argument passed to this function is a copy of the (void*)
|
||||
@ -575,4 +577,3 @@ struct fts5_api {
|
||||
#endif
|
||||
|
||||
#endif /* _FTS5_H */
|
||||
|
||||
|
||||
@ -47,6 +47,10 @@ typedef sqlite3_uint64 u64;
|
||||
|
||||
#endif
|
||||
|
||||
/* Truncate very long tokens to this many bytes. Hard limit is
|
||||
** (65536-1-1-4-9)==65521 bytes. The limiting factor is the 16-bit offset
|
||||
** field that occurs at the start of each leaf page (see fts5_index.c). */
|
||||
#define FTS5_MAX_TOKEN_SIZE 32768
|
||||
|
||||
/*
|
||||
** Maximum number of prefix indexes on single FTS5 table. This must be
|
||||
@ -172,6 +176,7 @@ struct Fts5Config {
|
||||
int pgsz; /* Approximate page size used in %_data */
|
||||
int nAutomerge; /* 'automerge' setting */
|
||||
int nCrisisMerge; /* Maximum allowed segments per level */
|
||||
int nUsermerge; /* 'usermerge' setting */
|
||||
int nHashSize; /* Bytes of memory for in-memory hash */
|
||||
char *zRank; /* Name of rank function */
|
||||
char *zRankArgs; /* Arguments to rank function */
|
||||
@ -479,6 +484,7 @@ int sqlite3Fts5IndexReads(Fts5Index *p);
|
||||
int sqlite3Fts5IndexReinit(Fts5Index *p);
|
||||
int sqlite3Fts5IndexOptimize(Fts5Index *p);
|
||||
int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge);
|
||||
int sqlite3Fts5IndexReset(Fts5Index *p);
|
||||
|
||||
int sqlite3Fts5IndexLoadConfig(Fts5Index *p);
|
||||
|
||||
@ -621,6 +627,7 @@ int sqlite3Fts5StorageDeleteAll(Fts5Storage *p);
|
||||
int sqlite3Fts5StorageRebuild(Fts5Storage *p);
|
||||
int sqlite3Fts5StorageOptimize(Fts5Storage *p);
|
||||
int sqlite3Fts5StorageMerge(Fts5Storage *p, int nMerge);
|
||||
int sqlite3Fts5StorageReset(Fts5Storage *p);
|
||||
|
||||
/*
|
||||
** End of interface to code in fts5_storage.c.
|
||||
@ -679,7 +686,6 @@ int sqlite3Fts5ExprPopulatePoslists(
|
||||
Fts5Config*, Fts5Expr*, Fts5PoslistPopulator*, int, const char*, int
|
||||
);
|
||||
void sqlite3Fts5ExprCheckPoslists(Fts5Expr*, i64);
|
||||
void sqlite3Fts5ExprClearEof(Fts5Expr*);
|
||||
|
||||
int sqlite3Fts5ExprClonePhrase(Fts5Expr*, int, Fts5Expr**);
|
||||
|
||||
@ -700,6 +706,12 @@ Fts5ExprNode *sqlite3Fts5ParseNode(
|
||||
Fts5ExprNearset *pNear
|
||||
);
|
||||
|
||||
Fts5ExprNode *sqlite3Fts5ParseImplicitAnd(
|
||||
Fts5Parse *pParse,
|
||||
Fts5ExprNode *pLeft,
|
||||
Fts5ExprNode *pRight
|
||||
);
|
||||
|
||||
Fts5ExprPhrase *sqlite3Fts5ParseTerm(
|
||||
Fts5Parse *pParse,
|
||||
Fts5ExprPhrase *pPhrase,
|
||||
@ -725,6 +737,7 @@ void sqlite3Fts5ParseNodeFree(Fts5ExprNode*);
|
||||
|
||||
void sqlite3Fts5ParseSetDistance(Fts5Parse*, Fts5ExprNearset*, Fts5Token*);
|
||||
void sqlite3Fts5ParseSetColset(Fts5Parse*, Fts5ExprNearset*, Fts5Colset*);
|
||||
Fts5Colset *sqlite3Fts5ParseColsetInvert(Fts5Parse*, Fts5Colset*);
|
||||
void sqlite3Fts5ParseFinished(Fts5Parse *pParse, Fts5ExprNode *p);
|
||||
void sqlite3Fts5ParseNear(Fts5Parse *pParse, Fts5Token*);
|
||||
|
||||
|
||||
@ -189,7 +189,7 @@ static int fts5HighlightCb(
|
||||
if( p->iRangeEnd>0 && iPos==p->iRangeEnd ){
|
||||
fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff);
|
||||
p->iOff = iEndOff;
|
||||
if( iPos<p->iter.iEnd ){
|
||||
if( iPos>=p->iter.iStart && iPos<p->iter.iEnd ){
|
||||
fts5HighlightAppend(&rc, p, p->zClose, -1);
|
||||
}
|
||||
}
|
||||
@ -246,6 +246,118 @@ static void fts5HighlightFunction(
|
||||
** End of highlight() implementation.
|
||||
**************************************************************************/
|
||||
|
||||
/*
|
||||
** Context object passed to the fts5SentenceFinderCb() function.
|
||||
*/
|
||||
typedef struct Fts5SFinder Fts5SFinder;
|
||||
struct Fts5SFinder {
|
||||
int iPos; /* Current token position */
|
||||
int nFirstAlloc; /* Allocated size of aFirst[] */
|
||||
int nFirst; /* Number of entries in aFirst[] */
|
||||
int *aFirst; /* Array of first token in each sentence */
|
||||
const char *zDoc; /* Document being tokenized */
|
||||
};
|
||||
|
||||
/*
|
||||
** Add an entry to the Fts5SFinder.aFirst[] array. Grow the array if
|
||||
** necessary. Return SQLITE_OK if successful, or SQLITE_NOMEM if an
|
||||
** error occurs.
|
||||
*/
|
||||
static int fts5SentenceFinderAdd(Fts5SFinder *p, int iAdd){
|
||||
if( p->nFirstAlloc==p->nFirst ){
|
||||
int nNew = p->nFirstAlloc ? p->nFirstAlloc*2 : 64;
|
||||
int *aNew;
|
||||
|
||||
aNew = (int*)sqlite3_realloc(p->aFirst, nNew*sizeof(int));
|
||||
if( aNew==0 ) return SQLITE_NOMEM;
|
||||
p->aFirst = aNew;
|
||||
p->nFirstAlloc = nNew;
|
||||
}
|
||||
p->aFirst[p->nFirst++] = iAdd;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is an xTokenize() callback used by the auxiliary snippet()
|
||||
** function. Its job is to identify tokens that are the first in a sentence.
|
||||
** For each such token, an entry is added to the SFinder.aFirst[] array.
|
||||
*/
|
||||
static int fts5SentenceFinderCb(
|
||||
void *pContext, /* Pointer to HighlightContext object */
|
||||
int tflags, /* Mask of FTS5_TOKEN_* flags */
|
||||
const char *pToken, /* Buffer containing token */
|
||||
int nToken, /* Size of token in bytes */
|
||||
int iStartOff, /* Start offset of token */
|
||||
int iEndOff /* End offset of token */
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
UNUSED_PARAM2(pToken, nToken);
|
||||
UNUSED_PARAM(iEndOff);
|
||||
|
||||
if( (tflags & FTS5_TOKEN_COLOCATED)==0 ){
|
||||
Fts5SFinder *p = (Fts5SFinder*)pContext;
|
||||
if( p->iPos>0 ){
|
||||
int i;
|
||||
char c = 0;
|
||||
for(i=iStartOff-1; i>=0; i--){
|
||||
c = p->zDoc[i];
|
||||
if( c!=' ' && c!='\t' && c!='\n' && c!='\r' ) break;
|
||||
}
|
||||
if( i!=iStartOff-1 && (c=='.' || c==':') ){
|
||||
rc = fts5SentenceFinderAdd(p, p->iPos);
|
||||
}
|
||||
}else{
|
||||
rc = fts5SentenceFinderAdd(p, 0);
|
||||
}
|
||||
p->iPos++;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int fts5SnippetScore(
|
||||
const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
|
||||
Fts5Context *pFts, /* First arg to pass to pApi functions */
|
||||
int nDocsize, /* Size of column in tokens */
|
||||
unsigned char *aSeen, /* Array with one element per query phrase */
|
||||
int iCol, /* Column to score */
|
||||
int iPos, /* Starting offset to score */
|
||||
int nToken, /* Max tokens per snippet */
|
||||
int *pnScore, /* OUT: Score */
|
||||
int *piPos /* OUT: Adjusted offset */
|
||||
){
|
||||
int rc;
|
||||
int i;
|
||||
int ip = 0;
|
||||
int ic = 0;
|
||||
int iOff = 0;
|
||||
int iFirst = -1;
|
||||
int nInst;
|
||||
int nScore = 0;
|
||||
int iLast = 0;
|
||||
|
||||
rc = pApi->xInstCount(pFts, &nInst);
|
||||
for(i=0; i<nInst && rc==SQLITE_OK; i++){
|
||||
rc = pApi->xInst(pFts, i, &ip, &ic, &iOff);
|
||||
if( rc==SQLITE_OK && ic==iCol && iOff>=iPos && iOff<(iPos+nToken) ){
|
||||
nScore += (aSeen[ip] ? 1 : 1000);
|
||||
aSeen[ip] = 1;
|
||||
if( iFirst<0 ) iFirst = iOff;
|
||||
iLast = iOff + pApi->xPhraseSize(pFts, ip);
|
||||
}
|
||||
}
|
||||
|
||||
*pnScore = nScore;
|
||||
if( piPos ){
|
||||
int iAdj = iFirst - (nToken - (iLast-iFirst)) / 2;
|
||||
if( (iAdj+nToken)>nDocsize ) iAdj = nDocsize - nToken;
|
||||
if( iAdj<0 ) iAdj = 0;
|
||||
*piPos = iAdj;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Implementation of snippet() function.
|
||||
*/
|
||||
@ -267,9 +379,10 @@ static void fts5SnippetFunction(
|
||||
unsigned char *aSeen; /* Array of "seen instance" flags */
|
||||
int iBestCol; /* Column containing best snippet */
|
||||
int iBestStart = 0; /* First token of best snippet */
|
||||
int iBestLast; /* Last token of best snippet */
|
||||
int nBestScore = 0; /* Score of best snippet */
|
||||
int nColSize = 0; /* Total size of iBestCol in tokens */
|
||||
Fts5SFinder sFinder; /* Used to find the beginnings of sentences */
|
||||
int nCol;
|
||||
|
||||
if( nVal!=5 ){
|
||||
const char *zErr = "wrong number of arguments to function snippet()";
|
||||
@ -277,13 +390,13 @@ static void fts5SnippetFunction(
|
||||
return;
|
||||
}
|
||||
|
||||
nCol = pApi->xColumnCount(pFts);
|
||||
memset(&ctx, 0, sizeof(HighlightContext));
|
||||
iCol = sqlite3_value_int(apVal[0]);
|
||||
ctx.zOpen = (const char*)sqlite3_value_text(apVal[1]);
|
||||
ctx.zClose = (const char*)sqlite3_value_text(apVal[2]);
|
||||
zEllips = (const char*)sqlite3_value_text(apVal[3]);
|
||||
nToken = sqlite3_value_int(apVal[4]);
|
||||
iBestLast = nToken-1;
|
||||
|
||||
iBestCol = (iCol>=0 ? iCol : 0);
|
||||
nPhrase = pApi->xPhraseCount(pFts);
|
||||
@ -291,65 +404,94 @@ static void fts5SnippetFunction(
|
||||
if( aSeen==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = pApi->xInstCount(pFts, &nInst);
|
||||
}
|
||||
for(i=0; rc==SQLITE_OK && i<nInst; i++){
|
||||
int ip, iSnippetCol, iStart;
|
||||
memset(aSeen, 0, nPhrase);
|
||||
rc = pApi->xInst(pFts, i, &ip, &iSnippetCol, &iStart);
|
||||
if( rc==SQLITE_OK && (iCol<0 || iSnippetCol==iCol) ){
|
||||
int nScore = 1000;
|
||||
int iLast = iStart - 1 + pApi->xPhraseSize(pFts, ip);
|
||||
int j;
|
||||
aSeen[ip] = 1;
|
||||
|
||||
for(j=i+1; rc==SQLITE_OK && j<nInst; j++){
|
||||
int ic; int io; int iFinal;
|
||||
rc = pApi->xInst(pFts, j, &ip, &ic, &io);
|
||||
iFinal = io + pApi->xPhraseSize(pFts, ip) - 1;
|
||||
if( rc==SQLITE_OK && ic==iSnippetCol && iLast<iStart+nToken ){
|
||||
nScore += aSeen[ip] ? 1000 : 1;
|
||||
aSeen[ip] = 1;
|
||||
if( iFinal>iLast ) iLast = iFinal;
|
||||
memset(&sFinder, 0, sizeof(Fts5SFinder));
|
||||
for(i=0; i<nCol; i++){
|
||||
if( iCol<0 || iCol==i ){
|
||||
int nDoc;
|
||||
int nDocsize;
|
||||
int ii;
|
||||
sFinder.iPos = 0;
|
||||
sFinder.nFirst = 0;
|
||||
rc = pApi->xColumnText(pFts, i, &sFinder.zDoc, &nDoc);
|
||||
if( rc!=SQLITE_OK ) break;
|
||||
rc = pApi->xTokenize(pFts,
|
||||
sFinder.zDoc, nDoc, (void*)&sFinder,fts5SentenceFinderCb
|
||||
);
|
||||
if( rc!=SQLITE_OK ) break;
|
||||
rc = pApi->xColumnSize(pFts, i, &nDocsize);
|
||||
if( rc!=SQLITE_OK ) break;
|
||||
|
||||
for(ii=0; rc==SQLITE_OK && ii<nInst; ii++){
|
||||
int ip, ic, io;
|
||||
int iAdj;
|
||||
int nScore;
|
||||
int jj;
|
||||
|
||||
rc = pApi->xInst(pFts, ii, &ip, &ic, &io);
|
||||
if( ic!=i || rc!=SQLITE_OK ) continue;
|
||||
memset(aSeen, 0, nPhrase);
|
||||
rc = fts5SnippetScore(pApi, pFts, nDocsize, aSeen, i,
|
||||
io, nToken, &nScore, &iAdj
|
||||
);
|
||||
if( rc==SQLITE_OK && nScore>nBestScore ){
|
||||
nBestScore = nScore;
|
||||
iBestCol = i;
|
||||
iBestStart = iAdj;
|
||||
nColSize = nDocsize;
|
||||
}
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK && nScore>nBestScore ){
|
||||
iBestCol = iSnippetCol;
|
||||
iBestStart = iStart;
|
||||
iBestLast = iLast;
|
||||
nBestScore = nScore;
|
||||
if( rc==SQLITE_OK && sFinder.nFirst && nDocsize>nToken ){
|
||||
for(jj=0; jj<(sFinder.nFirst-1); jj++){
|
||||
if( sFinder.aFirst[jj+1]>io ) break;
|
||||
}
|
||||
|
||||
if( sFinder.aFirst[jj]<io ){
|
||||
memset(aSeen, 0, nPhrase);
|
||||
rc = fts5SnippetScore(pApi, pFts, nDocsize, aSeen, i,
|
||||
sFinder.aFirst[jj], nToken, &nScore, 0
|
||||
);
|
||||
|
||||
nScore += (sFinder.aFirst[jj]==0 ? 120 : 100);
|
||||
if( rc==SQLITE_OK && nScore>nBestScore ){
|
||||
nBestScore = nScore;
|
||||
iBestCol = i;
|
||||
iBestStart = sFinder.aFirst[jj];
|
||||
nColSize = nDocsize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = pApi->xColumnSize(pFts, iBestCol, &nColSize);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = pApi->xColumnText(pFts, iBestCol, &ctx.zIn, &ctx.nIn);
|
||||
}
|
||||
if( rc==SQLITE_OK && nColSize==0 ){
|
||||
rc = pApi->xColumnSize(pFts, iBestCol, &nColSize);
|
||||
}
|
||||
if( ctx.zIn ){
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = fts5CInstIterInit(pApi, pFts, iBestCol, &ctx.iter);
|
||||
}
|
||||
|
||||
if( (iBestStart+nToken-1)>iBestLast ){
|
||||
iBestStart -= (iBestStart+nToken-1-iBestLast) / 2;
|
||||
}
|
||||
if( iBestStart+nToken>nColSize ){
|
||||
iBestStart = nColSize - nToken;
|
||||
}
|
||||
if( iBestStart<0 ) iBestStart = 0;
|
||||
|
||||
ctx.iRangeStart = iBestStart;
|
||||
ctx.iRangeEnd = iBestStart + nToken - 1;
|
||||
|
||||
if( iBestStart>0 ){
|
||||
fts5HighlightAppend(&rc, &ctx, zEllips, -1);
|
||||
}
|
||||
|
||||
/* Advance iterator ctx.iter so that it points to the first coalesced
|
||||
** phrase instance at or following position iBestStart. */
|
||||
while( ctx.iter.iStart>=0 && ctx.iter.iStart<iBestStart && rc==SQLITE_OK ){
|
||||
rc = fts5CInstIterNext(&ctx.iter);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = pApi->xTokenize(pFts, ctx.zIn, ctx.nIn, (void*)&ctx,fts5HighlightCb);
|
||||
}
|
||||
@ -358,15 +500,15 @@ static void fts5SnippetFunction(
|
||||
}else{
|
||||
fts5HighlightAppend(&rc, &ctx, zEllips, -1);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3_result_text(pCtx, (const char*)ctx.zOut, -1, SQLITE_TRANSIENT);
|
||||
}else{
|
||||
sqlite3_result_error_code(pCtx, rc);
|
||||
}
|
||||
sqlite3_free(ctx.zOut);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3_result_text(pCtx, (const char*)ctx.zOut, -1, SQLITE_TRANSIENT);
|
||||
}else{
|
||||
sqlite3_result_error_code(pCtx, rc);
|
||||
}
|
||||
sqlite3_free(ctx.zOut);
|
||||
sqlite3_free(aSeen);
|
||||
sqlite3_free(sFinder.aFirst);
|
||||
}
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
|
||||
#define FTS5_DEFAULT_PAGE_SIZE 4050
|
||||
#define FTS5_DEFAULT_AUTOMERGE 4
|
||||
#define FTS5_DEFAULT_USERMERGE 4
|
||||
#define FTS5_DEFAULT_CRISISMERGE 16
|
||||
#define FTS5_DEFAULT_HASHSIZE (1024*1024)
|
||||
|
||||
@ -441,7 +442,9 @@ static const char *fts5ConfigGobbleWord(
|
||||
*pbQuoted = 1;
|
||||
}else{
|
||||
zRet = fts5ConfigSkipBareword(zIn);
|
||||
zOut[zRet-zIn] = '\0';
|
||||
if( zRet ){
|
||||
zOut[zRet-zIn] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -857,6 +860,18 @@ int sqlite3Fts5ConfigSetValue(
|
||||
}
|
||||
}
|
||||
|
||||
else if( 0==sqlite3_stricmp(zKey, "usermerge") ){
|
||||
int nUsermerge = -1;
|
||||
if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
|
||||
nUsermerge = sqlite3_value_int(pVal);
|
||||
}
|
||||
if( nUsermerge<2 || nUsermerge>16 ){
|
||||
*pbBadkey = 1;
|
||||
}else{
|
||||
pConfig->nUsermerge = nUsermerge;
|
||||
}
|
||||
}
|
||||
|
||||
else if( 0==sqlite3_stricmp(zKey, "crisismerge") ){
|
||||
int nCrisisMerge = -1;
|
||||
if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
|
||||
@ -903,6 +918,7 @@ int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){
|
||||
/* Set default values */
|
||||
pConfig->pgsz = FTS5_DEFAULT_PAGE_SIZE;
|
||||
pConfig->nAutomerge = FTS5_DEFAULT_AUTOMERGE;
|
||||
pConfig->nUsermerge = FTS5_DEFAULT_USERMERGE;
|
||||
pConfig->nCrisisMerge = FTS5_DEFAULT_CRISISMERGE;
|
||||
pConfig->nHashSize = FTS5_DEFAULT_HASHSIZE;
|
||||
|
||||
|
||||
@ -167,6 +167,7 @@ static int fts5ExprGetToken(
|
||||
case ',': tok = FTS5_COMMA; break;
|
||||
case '+': tok = FTS5_PLUS; break;
|
||||
case '*': tok = FTS5_STAR; break;
|
||||
case '-': tok = FTS5_MINUS; break;
|
||||
case '\0': tok = FTS5_EOF; break;
|
||||
|
||||
case '"': {
|
||||
@ -258,6 +259,8 @@ int sqlite3Fts5ExprNew(
|
||||
pNew->nPhrase = sParse.nPhrase;
|
||||
sParse.apPhrase = 0;
|
||||
}
|
||||
}else{
|
||||
sqlite3Fts5ParseNodeFree(sParse.pExpr);
|
||||
}
|
||||
|
||||
sqlite3_free(sParse.apPhrase);
|
||||
@ -751,6 +754,7 @@ static int fts5ExprNearInitAll(
|
||||
Fts5ExprNearset *pNear = pNode->pNear;
|
||||
int i, j;
|
||||
int rc = SQLITE_OK;
|
||||
int bEof = 1;
|
||||
|
||||
assert( pNode->bNomatch==0 );
|
||||
for(i=0; rc==SQLITE_OK && i<pNear->nPhrase; i++){
|
||||
@ -758,7 +762,6 @@ static int fts5ExprNearInitAll(
|
||||
for(j=0; j<pPhrase->nTerm; j++){
|
||||
Fts5ExprTerm *pTerm = &pPhrase->aTerm[j];
|
||||
Fts5ExprTerm *p;
|
||||
int bEof = 1;
|
||||
|
||||
for(p=pTerm; p && rc==SQLITE_OK; p=p->pSynonym){
|
||||
if( p->pIter ){
|
||||
@ -778,13 +781,12 @@ static int fts5ExprNearInitAll(
|
||||
}
|
||||
}
|
||||
|
||||
if( bEof ){
|
||||
pNode->bEof = 1;
|
||||
return rc;
|
||||
}
|
||||
if( bEof ) break;
|
||||
}
|
||||
if( bEof ) break;
|
||||
}
|
||||
|
||||
pNode->bEof = bEof;
|
||||
return rc;
|
||||
}
|
||||
|
||||
@ -918,7 +920,7 @@ static int fts5ExprNodeTest_STRING(
|
||||
}
|
||||
}else{
|
||||
Fts5IndexIter *pIter = pPhrase->aTerm[j].pIter;
|
||||
if( pIter->iRowid==iLast ) continue;
|
||||
if( pIter->iRowid==iLast || pIter->bEof ) continue;
|
||||
bMatch = 0;
|
||||
if( fts5ExprAdvanceto(pIter, bDesc, &iLast, &rc, &pNode->bEof) ){
|
||||
return rc;
|
||||
@ -1268,6 +1270,8 @@ static int fts5ExprNodeFirst(Fts5Expr *pExpr, Fts5ExprNode *pNode){
|
||||
if( Fts5NodeIsString(pNode) ){
|
||||
/* Initialize all term iterators in the NEAR object. */
|
||||
rc = fts5ExprNearInitAll(pExpr, pNode);
|
||||
}else if( pNode->xNext==0 ){
|
||||
pNode->bEof = 1;
|
||||
}else{
|
||||
int i;
|
||||
int nEof = 0;
|
||||
@ -1319,23 +1323,22 @@ static int fts5ExprNodeFirst(Fts5Expr *pExpr, Fts5ExprNode *pNode){
|
||||
*/
|
||||
int sqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, i64 iFirst, int bDesc){
|
||||
Fts5ExprNode *pRoot = p->pRoot;
|
||||
int rc = SQLITE_OK;
|
||||
if( pRoot->xNext ){
|
||||
p->pIndex = pIdx;
|
||||
p->bDesc = bDesc;
|
||||
rc = fts5ExprNodeFirst(p, pRoot);
|
||||
int rc; /* Return code */
|
||||
|
||||
/* If not at EOF but the current rowid occurs earlier than iFirst in
|
||||
** the iteration order, move to document iFirst or later. */
|
||||
if( pRoot->bEof==0 && fts5RowidCmp(p, pRoot->iRowid, iFirst)<0 ){
|
||||
rc = fts5ExprNodeNext(p, pRoot, 1, iFirst);
|
||||
}
|
||||
p->pIndex = pIdx;
|
||||
p->bDesc = bDesc;
|
||||
rc = fts5ExprNodeFirst(p, pRoot);
|
||||
|
||||
/* If the iterator is not at a real match, skip forward until it is. */
|
||||
while( pRoot->bNomatch ){
|
||||
assert( pRoot->bEof==0 && rc==SQLITE_OK );
|
||||
rc = fts5ExprNodeNext(p, pRoot, 0, 0);
|
||||
}
|
||||
/* If not at EOF but the current rowid occurs earlier than iFirst in
|
||||
** the iteration order, move to document iFirst or later. */
|
||||
if( pRoot->bEof==0 && fts5RowidCmp(p, pRoot->iRowid, iFirst)<0 ){
|
||||
rc = fts5ExprNodeNext(p, pRoot, 1, iFirst);
|
||||
}
|
||||
|
||||
/* If the iterator is not at a real match, skip forward until it is. */
|
||||
while( pRoot->bNomatch ){
|
||||
assert( pRoot->bEof==0 && rc==SQLITE_OK );
|
||||
rc = fts5ExprNodeNext(p, pRoot, 0, 0);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
@ -1444,6 +1447,21 @@ Fts5ExprNearset *sqlite3Fts5ParseNearset(
|
||||
sqlite3Fts5ParseNearsetFree(pNear);
|
||||
sqlite3Fts5ParsePhraseFree(pPhrase);
|
||||
}else{
|
||||
if( pRet->nPhrase>0 ){
|
||||
Fts5ExprPhrase *pLast = pRet->apPhrase[pRet->nPhrase-1];
|
||||
assert( pLast==pParse->apPhrase[pParse->nPhrase-2] );
|
||||
if( pPhrase->nTerm==0 ){
|
||||
fts5ExprPhraseFree(pPhrase);
|
||||
pRet->nPhrase--;
|
||||
pParse->nPhrase--;
|
||||
pPhrase = pLast;
|
||||
}else if( pLast->nTerm==0 ){
|
||||
fts5ExprPhraseFree(pLast);
|
||||
pParse->apPhrase[pParse->nPhrase-2] = pPhrase;
|
||||
pParse->nPhrase--;
|
||||
pRet->nPhrase--;
|
||||
}
|
||||
}
|
||||
pRet->apPhrase[pRet->nPhrase++] = pPhrase;
|
||||
}
|
||||
return pRet;
|
||||
@ -1475,9 +1493,9 @@ static int fts5ParseTokenize(
|
||||
|
||||
/* If an error has already occurred, this is a no-op */
|
||||
if( pCtx->rc!=SQLITE_OK ) return pCtx->rc;
|
||||
if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE;
|
||||
|
||||
assert( pPhrase==0 || pPhrase->nTerm>0 );
|
||||
if( pPhrase && (tflags & FTS5_TOKEN_COLOCATED) ){
|
||||
if( pPhrase && pPhrase->nTerm>0 && (tflags & FTS5_TOKEN_COLOCATED) ){
|
||||
Fts5ExprTerm *pSyn;
|
||||
int nByte = sizeof(Fts5ExprTerm) + sizeof(Fts5Buffer) + nToken+1;
|
||||
pSyn = (Fts5ExprTerm*)sqlite3_malloc(nByte);
|
||||
@ -1578,7 +1596,7 @@ Fts5ExprPhrase *sqlite3Fts5ParseTerm(
|
||||
pParse->rc = rc;
|
||||
fts5ExprPhraseFree(sCtx.pPhrase);
|
||||
sCtx.pPhrase = 0;
|
||||
}else if( sCtx.pPhrase ){
|
||||
}else{
|
||||
|
||||
if( pAppend==0 ){
|
||||
if( (pParse->nPhrase % 8)==0 ){
|
||||
@ -1595,9 +1613,14 @@ Fts5ExprPhrase *sqlite3Fts5ParseTerm(
|
||||
pParse->nPhrase++;
|
||||
}
|
||||
|
||||
if( sCtx.pPhrase==0 ){
|
||||
/* This happens when parsing a token or quoted phrase that contains
|
||||
** no token characters at all. (e.g ... MATCH '""'). */
|
||||
sCtx.pPhrase = sqlite3Fts5MallocZero(&pParse->rc, sizeof(Fts5ExprPhrase));
|
||||
}else if( sCtx.pPhrase->nTerm ){
|
||||
sCtx.pPhrase->aTerm[sCtx.pPhrase->nTerm-1].bPrefix = bPrefix;
|
||||
}
|
||||
pParse->apPhrase[pParse->nPhrase-1] = sCtx.pPhrase;
|
||||
assert( sCtx.pPhrase->nTerm>0 );
|
||||
sCtx.pPhrase->aTerm[sCtx.pPhrase->nTerm-1].bPrefix = bPrefix;
|
||||
}
|
||||
|
||||
return sCtx.pPhrase;
|
||||
@ -1614,7 +1637,6 @@ int sqlite3Fts5ExprClonePhrase(
|
||||
){
|
||||
int rc = SQLITE_OK; /* Return code */
|
||||
Fts5ExprPhrase *pOrig; /* The phrase extracted from pExpr */
|
||||
int i; /* Used to iterate through phrase terms */
|
||||
Fts5Expr *pNew = 0; /* Expression to return via *ppNew */
|
||||
TokenCtx sCtx = {0,0}; /* Context object for fts5ParseTokenize */
|
||||
|
||||
@ -1632,19 +1654,37 @@ int sqlite3Fts5ExprClonePhrase(
|
||||
pNew->pRoot->pNear = (Fts5ExprNearset*)sqlite3Fts5MallocZero(&rc,
|
||||
sizeof(Fts5ExprNearset) + sizeof(Fts5ExprPhrase*));
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
Fts5Colset *pColsetOrig = pOrig->pNode->pNear->pColset;
|
||||
if( pColsetOrig ){
|
||||
int nByte = sizeof(Fts5Colset) + (pColsetOrig->nCol-1) * sizeof(int);
|
||||
Fts5Colset *pColset = (Fts5Colset*)sqlite3Fts5MallocZero(&rc, nByte);
|
||||
if( pColset ){
|
||||
memcpy(pColset, pColsetOrig, nByte);
|
||||
}
|
||||
pNew->pRoot->pNear->pColset = pColset;
|
||||
}
|
||||
}
|
||||
|
||||
for(i=0; rc==SQLITE_OK && i<pOrig->nTerm; i++){
|
||||
int tflags = 0;
|
||||
Fts5ExprTerm *p;
|
||||
for(p=&pOrig->aTerm[i]; p && rc==SQLITE_OK; p=p->pSynonym){
|
||||
const char *zTerm = p->zTerm;
|
||||
rc = fts5ParseTokenize((void*)&sCtx, tflags, zTerm, (int)strlen(zTerm),
|
||||
0, 0);
|
||||
tflags = FTS5_TOKEN_COLOCATED;
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
sCtx.pPhrase->aTerm[i].bPrefix = pOrig->aTerm[i].bPrefix;
|
||||
if( pOrig->nTerm ){
|
||||
int i; /* Used to iterate through phrase terms */
|
||||
for(i=0; rc==SQLITE_OK && i<pOrig->nTerm; i++){
|
||||
int tflags = 0;
|
||||
Fts5ExprTerm *p;
|
||||
for(p=&pOrig->aTerm[i]; p && rc==SQLITE_OK; p=p->pSynonym){
|
||||
const char *zTerm = p->zTerm;
|
||||
rc = fts5ParseTokenize((void*)&sCtx, tflags, zTerm, (int)strlen(zTerm),
|
||||
0, 0);
|
||||
tflags = FTS5_TOKEN_COLOCATED;
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
sCtx.pPhrase->aTerm[i].bPrefix = pOrig->aTerm[i].bPrefix;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
/* This happens when parsing a token or quoted phrase that contains
|
||||
** no token characters at all. (e.g ... MATCH '""'). */
|
||||
sCtx.pPhrase = sqlite3Fts5MallocZero(&rc, sizeof(Fts5ExprPhrase));
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
@ -1693,23 +1733,25 @@ void sqlite3Fts5ParseSetDistance(
|
||||
Fts5ExprNearset *pNear,
|
||||
Fts5Token *p
|
||||
){
|
||||
int nNear = 0;
|
||||
int i;
|
||||
if( p->n ){
|
||||
for(i=0; i<p->n; i++){
|
||||
char c = (char)p->p[i];
|
||||
if( c<'0' || c>'9' ){
|
||||
sqlite3Fts5ParseError(
|
||||
pParse, "expected integer, got \"%.*s\"", p->n, p->p
|
||||
);
|
||||
return;
|
||||
if( pNear ){
|
||||
int nNear = 0;
|
||||
int i;
|
||||
if( p->n ){
|
||||
for(i=0; i<p->n; i++){
|
||||
char c = (char)p->p[i];
|
||||
if( c<'0' || c>'9' ){
|
||||
sqlite3Fts5ParseError(
|
||||
pParse, "expected integer, got \"%.*s\"", p->n, p->p
|
||||
);
|
||||
return;
|
||||
}
|
||||
nNear = nNear * 10 + (p->p[i] - '0');
|
||||
}
|
||||
nNear = nNear * 10 + (p->p[i] - '0');
|
||||
}else{
|
||||
nNear = FTS5_DEFAULT_NEARDIST;
|
||||
}
|
||||
}else{
|
||||
nNear = FTS5_DEFAULT_NEARDIST;
|
||||
pNear->nNear = nNear;
|
||||
}
|
||||
pNear->nNear = nNear;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1757,6 +1799,34 @@ static Fts5Colset *fts5ParseColset(
|
||||
return pNew;
|
||||
}
|
||||
|
||||
/*
|
||||
** Allocate and return an Fts5Colset object specifying the inverse of
|
||||
** the colset passed as the second argument. Free the colset passed
|
||||
** as the second argument before returning.
|
||||
*/
|
||||
Fts5Colset *sqlite3Fts5ParseColsetInvert(Fts5Parse *pParse, Fts5Colset *p){
|
||||
Fts5Colset *pRet;
|
||||
int nCol = pParse->pConfig->nCol;
|
||||
|
||||
pRet = (Fts5Colset*)sqlite3Fts5MallocZero(&pParse->rc,
|
||||
sizeof(Fts5Colset) + sizeof(int)*nCol
|
||||
);
|
||||
if( pRet ){
|
||||
int i;
|
||||
int iOld = 0;
|
||||
for(i=0; i<nCol; i++){
|
||||
if( iOld>=p->nCol || p->aiCol[iOld]!=i ){
|
||||
pRet->aiCol[pRet->nCol++] = i;
|
||||
}else{
|
||||
iOld++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sqlite3_free(p);
|
||||
return pRet;
|
||||
}
|
||||
|
||||
Fts5Colset *sqlite3Fts5ParseColset(
|
||||
Fts5Parse *pParse, /* Store SQLITE_NOMEM here if required */
|
||||
Fts5Colset *pColset, /* Existing colset object */
|
||||
@ -1896,10 +1966,14 @@ Fts5ExprNode *sqlite3Fts5ParseNode(
|
||||
int iPhrase;
|
||||
for(iPhrase=0; iPhrase<pNear->nPhrase; iPhrase++){
|
||||
pNear->apPhrase[iPhrase]->pNode = pRet;
|
||||
if( pNear->apPhrase[iPhrase]->nTerm==0 ){
|
||||
pRet->xNext = 0;
|
||||
pRet->eType = FTS5_EOF;
|
||||
}
|
||||
}
|
||||
|
||||
if( pParse->pConfig->eDetail!=FTS5_DETAIL_FULL
|
||||
&& (pNear->nPhrase!=1 || pNear->apPhrase[0]->nTerm!=1)
|
||||
&& (pNear->nPhrase!=1 || pNear->apPhrase[0]->nTerm>1)
|
||||
){
|
||||
assert( pParse->rc==SQLITE_OK );
|
||||
pParse->rc = SQLITE_ERROR;
|
||||
@ -1928,6 +2002,70 @@ Fts5ExprNode *sqlite3Fts5ParseNode(
|
||||
return pRet;
|
||||
}
|
||||
|
||||
Fts5ExprNode *sqlite3Fts5ParseImplicitAnd(
|
||||
Fts5Parse *pParse, /* Parse context */
|
||||
Fts5ExprNode *pLeft, /* Left hand child expression */
|
||||
Fts5ExprNode *pRight /* Right hand child expression */
|
||||
){
|
||||
Fts5ExprNode *pRet = 0;
|
||||
Fts5ExprNode *pPrev;
|
||||
|
||||
if( pParse->rc ){
|
||||
sqlite3Fts5ParseNodeFree(pLeft);
|
||||
sqlite3Fts5ParseNodeFree(pRight);
|
||||
}else{
|
||||
|
||||
assert( pLeft->eType==FTS5_STRING
|
||||
|| pLeft->eType==FTS5_TERM
|
||||
|| pLeft->eType==FTS5_EOF
|
||||
|| pLeft->eType==FTS5_AND
|
||||
);
|
||||
assert( pRight->eType==FTS5_STRING
|
||||
|| pRight->eType==FTS5_TERM
|
||||
|| pRight->eType==FTS5_EOF
|
||||
);
|
||||
|
||||
if( pLeft->eType==FTS5_AND ){
|
||||
pPrev = pLeft->apChild[pLeft->nChild-1];
|
||||
}else{
|
||||
pPrev = pLeft;
|
||||
}
|
||||
assert( pPrev->eType==FTS5_STRING
|
||||
|| pPrev->eType==FTS5_TERM
|
||||
|| pPrev->eType==FTS5_EOF
|
||||
);
|
||||
|
||||
if( pRight->eType==FTS5_EOF ){
|
||||
assert( pParse->apPhrase[pParse->nPhrase-1]==pRight->pNear->apPhrase[0] );
|
||||
sqlite3Fts5ParseNodeFree(pRight);
|
||||
pRet = pLeft;
|
||||
pParse->nPhrase--;
|
||||
}
|
||||
else if( pPrev->eType==FTS5_EOF ){
|
||||
Fts5ExprPhrase **ap;
|
||||
|
||||
if( pPrev==pLeft ){
|
||||
pRet = pRight;
|
||||
}else{
|
||||
pLeft->apChild[pLeft->nChild-1] = pRight;
|
||||
pRet = pLeft;
|
||||
}
|
||||
|
||||
ap = &pParse->apPhrase[pParse->nPhrase-1-pRight->pNear->nPhrase];
|
||||
assert( ap[0]==pPrev->pNear->apPhrase[0] );
|
||||
memmove(ap, &ap[1], sizeof(Fts5ExprPhrase*)*pRight->pNear->nPhrase);
|
||||
pParse->nPhrase--;
|
||||
|
||||
sqlite3Fts5ParseNodeFree(pPrev);
|
||||
}
|
||||
else{
|
||||
pRet = sqlite3Fts5ParseNode(pParse, FTS5_AND, pLeft, pRight, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return pRet;
|
||||
}
|
||||
|
||||
static char *fts5ExprTermPrint(Fts5ExprTerm *pTerm){
|
||||
int nByte = 0;
|
||||
Fts5ExprTerm *p;
|
||||
@ -2062,6 +2200,9 @@ static char *fts5ExprPrintTcl(
|
||||
|
||||
static char *fts5ExprPrint(Fts5Config *pConfig, Fts5ExprNode *pExpr){
|
||||
char *zRet = 0;
|
||||
if( pExpr->eType==0 ){
|
||||
return sqlite3_mprintf("\"\"");
|
||||
}else
|
||||
if( pExpr->eType==FTS5_STRING || pExpr->eType==FTS5_TERM ){
|
||||
Fts5ExprNearset *pNear = pExpr->pNear;
|
||||
int i;
|
||||
@ -2122,7 +2263,7 @@ static char *fts5ExprPrint(Fts5Config *pConfig, Fts5ExprNode *pExpr){
|
||||
zRet = 0;
|
||||
}else{
|
||||
int e = pExpr->apChild[i]->eType;
|
||||
int b = (e!=FTS5_STRING && e!=FTS5_TERM);
|
||||
int b = (e!=FTS5_STRING && e!=FTS5_TERM && e!=FTS5_EOF);
|
||||
zRet = fts5PrintfAppend(zRet, "%s%s%z%s",
|
||||
(i==0 ? "" : zOp),
|
||||
(b?"(":""), z, (b?")":"")
|
||||
@ -2400,12 +2541,13 @@ static int fts5ExprPopulatePoslistsCb(
|
||||
|
||||
UNUSED_PARAM2(iUnused1, iUnused2);
|
||||
|
||||
if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE;
|
||||
if( (tflags & FTS5_TOKEN_COLOCATED)==0 ) p->iOff++;
|
||||
for(i=0; i<pExpr->nPhrase; i++){
|
||||
Fts5ExprTerm *pTerm;
|
||||
if( p->aPopulator[i].bOk==0 ) continue;
|
||||
for(pTerm=&pExpr->apExprPhrase[i]->aTerm[0]; pTerm; pTerm=pTerm->pSynonym){
|
||||
int nTerm = strlen(pTerm->zTerm);
|
||||
int nTerm = (int)strlen(pTerm->zTerm);
|
||||
if( (nTerm==nToken || (nTerm<nToken && pTerm->bPrefix))
|
||||
&& memcmp(pTerm->zTerm, pToken, nTerm)==0
|
||||
){
|
||||
@ -2509,17 +2651,6 @@ void sqlite3Fts5ExprCheckPoslists(Fts5Expr *pExpr, i64 iRowid){
|
||||
fts5ExprCheckPoslists(pExpr->pRoot, iRowid);
|
||||
}
|
||||
|
||||
static void fts5ExprClearEof(Fts5ExprNode *pNode){
|
||||
int i;
|
||||
for(i=0; i<pNode->nChild; i++){
|
||||
fts5ExprClearEof(pNode->apChild[i]);
|
||||
}
|
||||
pNode->bEof = 0;
|
||||
}
|
||||
void sqlite3Fts5ExprClearEof(Fts5Expr *pExpr){
|
||||
fts5ExprClearEof(pExpr->pRoot);
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is only called for detail=columns tables.
|
||||
*/
|
||||
|
||||
@ -345,11 +345,11 @@ int sqlite3Fts5HashWrite(
|
||||
if( pHash->eDetail==FTS5_DETAIL_FULL ){
|
||||
pPtr[p->nData++] = 0x01;
|
||||
p->nData += sqlite3Fts5PutVarint(&pPtr[p->nData], iCol);
|
||||
p->iCol = iCol;
|
||||
p->iCol = (i16)iCol;
|
||||
p->iPos = 0;
|
||||
}else{
|
||||
bNew = 1;
|
||||
p->iCol = iPos = iCol;
|
||||
p->iCol = (i16)(iPos = iCol);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -304,6 +304,10 @@ struct Fts5Index {
|
||||
sqlite3_stmt *pIdxDeleter; /* "DELETE FROM %_idx WHERE segid=? */
|
||||
sqlite3_stmt *pIdxSelect;
|
||||
int nRead; /* Total number of blocks read */
|
||||
|
||||
sqlite3_stmt *pDataVersion;
|
||||
i64 iStructVersion; /* data_version when pStruct read */
|
||||
Fts5Structure *pStruct; /* Current db structure (or NULL) */
|
||||
};
|
||||
|
||||
struct Fts5DoclistIter {
|
||||
@ -706,6 +710,18 @@ static void fts5DataRelease(Fts5Data *pData){
|
||||
sqlite3_free(pData);
|
||||
}
|
||||
|
||||
static Fts5Data *fts5LeafRead(Fts5Index *p, i64 iRowid){
|
||||
Fts5Data *pRet = fts5DataRead(p, iRowid);
|
||||
if( pRet ){
|
||||
if( pRet->szLeaf>pRet->nn ){
|
||||
p->rc = FTS5_CORRUPT;
|
||||
fts5DataRelease(pRet);
|
||||
pRet = 0;
|
||||
}
|
||||
}
|
||||
return pRet;
|
||||
}
|
||||
|
||||
static int fts5IndexPrepareStmt(
|
||||
Fts5Index *p,
|
||||
sqlite3_stmt **ppStmt,
|
||||
@ -865,7 +881,7 @@ static int fts5StructureDecode(
|
||||
|
||||
for(iLvl=0; rc==SQLITE_OK && iLvl<nLevel; iLvl++){
|
||||
Fts5StructureLevel *pLvl = &pRet->aLevel[iLvl];
|
||||
int nTotal;
|
||||
int nTotal = 0;
|
||||
int iSeg;
|
||||
|
||||
if( i>=nData ){
|
||||
@ -958,6 +974,50 @@ static void fts5StructureExtendLevel(
|
||||
}
|
||||
}
|
||||
|
||||
static Fts5Structure *fts5StructureReadUncached(Fts5Index *p){
|
||||
Fts5Structure *pRet = 0;
|
||||
Fts5Config *pConfig = p->pConfig;
|
||||
int iCookie; /* Configuration cookie */
|
||||
Fts5Data *pData;
|
||||
|
||||
pData = fts5DataRead(p, FTS5_STRUCTURE_ROWID);
|
||||
if( p->rc==SQLITE_OK ){
|
||||
/* TODO: Do we need this if the leaf-index is appended? Probably... */
|
||||
memset(&pData->p[pData->nn], 0, FTS5_DATA_PADDING);
|
||||
p->rc = fts5StructureDecode(pData->p, pData->nn, &iCookie, &pRet);
|
||||
if( p->rc==SQLITE_OK && pConfig->iCookie!=iCookie ){
|
||||
p->rc = sqlite3Fts5ConfigLoad(pConfig, iCookie);
|
||||
}
|
||||
fts5DataRelease(pData);
|
||||
if( p->rc!=SQLITE_OK ){
|
||||
fts5StructureRelease(pRet);
|
||||
pRet = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return pRet;
|
||||
}
|
||||
|
||||
static i64 fts5IndexDataVersion(Fts5Index *p){
|
||||
i64 iVersion = 0;
|
||||
|
||||
if( p->rc==SQLITE_OK ){
|
||||
if( p->pDataVersion==0 ){
|
||||
p->rc = fts5IndexPrepareStmt(p, &p->pDataVersion,
|
||||
sqlite3_mprintf("PRAGMA %Q.data_version", p->pConfig->zDb)
|
||||
);
|
||||
if( p->rc ) return 0;
|
||||
}
|
||||
|
||||
if( SQLITE_ROW==sqlite3_step(p->pDataVersion) ){
|
||||
iVersion = sqlite3_column_int64(p->pDataVersion, 0);
|
||||
}
|
||||
p->rc = sqlite3_reset(p->pDataVersion);
|
||||
}
|
||||
|
||||
return iVersion;
|
||||
}
|
||||
|
||||
/*
|
||||
** Read, deserialize and return the structure record.
|
||||
**
|
||||
@ -970,26 +1030,49 @@ static void fts5StructureExtendLevel(
|
||||
** is called, it is a no-op.
|
||||
*/
|
||||
static Fts5Structure *fts5StructureRead(Fts5Index *p){
|
||||
Fts5Config *pConfig = p->pConfig;
|
||||
Fts5Structure *pRet = 0; /* Object to return */
|
||||
int iCookie; /* Configuration cookie */
|
||||
Fts5Data *pData;
|
||||
|
||||
pData = fts5DataRead(p, FTS5_STRUCTURE_ROWID);
|
||||
if( p->rc ) return 0;
|
||||
/* TODO: Do we need this if the leaf-index is appended? Probably... */
|
||||
memset(&pData->p[pData->nn], 0, FTS5_DATA_PADDING);
|
||||
p->rc = fts5StructureDecode(pData->p, pData->nn, &iCookie, &pRet);
|
||||
if( p->rc==SQLITE_OK && pConfig->iCookie!=iCookie ){
|
||||
p->rc = sqlite3Fts5ConfigLoad(pConfig, iCookie);
|
||||
if( p->pStruct==0 ){
|
||||
p->iStructVersion = fts5IndexDataVersion(p);
|
||||
if( p->rc==SQLITE_OK ){
|
||||
p->pStruct = fts5StructureReadUncached(p);
|
||||
}
|
||||
}
|
||||
|
||||
fts5DataRelease(pData);
|
||||
if( p->rc!=SQLITE_OK ){
|
||||
fts5StructureRelease(pRet);
|
||||
pRet = 0;
|
||||
#if 0
|
||||
else{
|
||||
Fts5Structure *pTest = fts5StructureReadUncached(p);
|
||||
if( pTest ){
|
||||
int i, j;
|
||||
assert_nc( p->pStruct->nSegment==pTest->nSegment );
|
||||
assert_nc( p->pStruct->nLevel==pTest->nLevel );
|
||||
for(i=0; i<pTest->nLevel; i++){
|
||||
assert_nc( p->pStruct->aLevel[i].nMerge==pTest->aLevel[i].nMerge );
|
||||
assert_nc( p->pStruct->aLevel[i].nSeg==pTest->aLevel[i].nSeg );
|
||||
for(j=0; j<pTest->aLevel[i].nSeg; j++){
|
||||
Fts5StructureSegment *p1 = &pTest->aLevel[i].aSeg[j];
|
||||
Fts5StructureSegment *p2 = &p->pStruct->aLevel[i].aSeg[j];
|
||||
assert_nc( p1->iSegid==p2->iSegid );
|
||||
assert_nc( p1->pgnoFirst==p2->pgnoFirst );
|
||||
assert_nc( p1->pgnoLast==p2->pgnoLast );
|
||||
}
|
||||
}
|
||||
fts5StructureRelease(pTest);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if( p->rc!=SQLITE_OK ) return 0;
|
||||
assert( p->iStructVersion!=0 );
|
||||
assert( p->pStruct!=0 );
|
||||
fts5StructureRef(p->pStruct);
|
||||
return p->pStruct;
|
||||
}
|
||||
|
||||
static void fts5StructureInvalidate(Fts5Index *p){
|
||||
if( p->pStruct ){
|
||||
fts5StructureRelease(p->pStruct);
|
||||
p->pStruct = 0;
|
||||
}
|
||||
return pRet;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1447,7 +1530,7 @@ static void fts5SegIterNextPage(
|
||||
pIter->pLeaf = pIter->pNextLeaf;
|
||||
pIter->pNextLeaf = 0;
|
||||
}else if( pIter->iLeafPgno<=pSeg->pgnoLast ){
|
||||
pIter->pLeaf = fts5DataRead(p,
|
||||
pIter->pLeaf = fts5LeafRead(p,
|
||||
FTS5_SEGMENT_ROWID(pSeg->iSegid, pIter->iLeafPgno)
|
||||
);
|
||||
}else{
|
||||
@ -1950,9 +2033,8 @@ static void fts5SegIterNext(
|
||||
if( pLeaf->nn>pLeaf->szLeaf ){
|
||||
pIter->iPgidxOff = pLeaf->szLeaf + fts5GetVarint32(
|
||||
&pLeaf->p[pLeaf->szLeaf], pIter->iEndofDoclist
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
else if( pLeaf->nn>pLeaf->szLeaf ){
|
||||
pIter->iPgidxOff = pLeaf->szLeaf + fts5GetVarint32(
|
||||
@ -2154,6 +2236,10 @@ static void fts5LeafSeek(
|
||||
iPgidx = szLeaf;
|
||||
iPgidx += fts5GetVarint32(&a[iPgidx], iTermOff);
|
||||
iOff = iTermOff;
|
||||
if( iOff>n ){
|
||||
p->rc = FTS5_CORRUPT;
|
||||
return;
|
||||
}
|
||||
|
||||
while( 1 ){
|
||||
|
||||
@ -2193,6 +2279,11 @@ static void fts5LeafSeek(
|
||||
iTermOff += nKeep;
|
||||
iOff = iTermOff;
|
||||
|
||||
if( iOff>=n ){
|
||||
p->rc = FTS5_CORRUPT;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Read the nKeep field of the next term. */
|
||||
fts5FastGetVarint32(a, iOff, nKeep);
|
||||
}
|
||||
@ -2245,6 +2336,18 @@ static void fts5LeafSeek(
|
||||
fts5SegIterLoadNPos(p, pIter);
|
||||
}
|
||||
|
||||
static sqlite3_stmt *fts5IdxSelectStmt(Fts5Index *p){
|
||||
if( p->pIdxSelect==0 ){
|
||||
Fts5Config *pConfig = p->pConfig;
|
||||
fts5IndexPrepareStmt(p, &p->pIdxSelect, sqlite3_mprintf(
|
||||
"SELECT pgno FROM '%q'.'%q_idx' WHERE "
|
||||
"segid=? AND term<=? ORDER BY term DESC LIMIT 1",
|
||||
pConfig->zDb, pConfig->zName
|
||||
));
|
||||
}
|
||||
return p->pIdxSelect;
|
||||
}
|
||||
|
||||
/*
|
||||
** Initialize the object pIter to point to term pTerm/nTerm within segment
|
||||
** pSeg. If there is no such term in the index, the iterator is set to EOF.
|
||||
@ -2262,6 +2365,7 @@ static void fts5SegIterSeekInit(
|
||||
int iPg = 1;
|
||||
int bGe = (flags & FTS5INDEX_QUERY_SCAN);
|
||||
int bDlidx = 0; /* True if there is a doclist-index */
|
||||
sqlite3_stmt *pIdxSelect = 0;
|
||||
|
||||
assert( bGe==0 || (flags & FTS5INDEX_QUERY_DESC)==0 );
|
||||
assert( pTerm && nTerm );
|
||||
@ -2270,23 +2374,16 @@ static void fts5SegIterSeekInit(
|
||||
|
||||
/* This block sets stack variable iPg to the leaf page number that may
|
||||
** contain term (pTerm/nTerm), if it is present in the segment. */
|
||||
if( p->pIdxSelect==0 ){
|
||||
Fts5Config *pConfig = p->pConfig;
|
||||
fts5IndexPrepareStmt(p, &p->pIdxSelect, sqlite3_mprintf(
|
||||
"SELECT pgno FROM '%q'.'%q_idx' WHERE "
|
||||
"segid=? AND term<=? ORDER BY term DESC LIMIT 1",
|
||||
pConfig->zDb, pConfig->zName
|
||||
));
|
||||
}
|
||||
pIdxSelect = fts5IdxSelectStmt(p);
|
||||
if( p->rc ) return;
|
||||
sqlite3_bind_int(p->pIdxSelect, 1, pSeg->iSegid);
|
||||
sqlite3_bind_blob(p->pIdxSelect, 2, pTerm, nTerm, SQLITE_STATIC);
|
||||
if( SQLITE_ROW==sqlite3_step(p->pIdxSelect) ){
|
||||
i64 val = sqlite3_column_int(p->pIdxSelect, 0);
|
||||
sqlite3_bind_int(pIdxSelect, 1, pSeg->iSegid);
|
||||
sqlite3_bind_blob(pIdxSelect, 2, pTerm, nTerm, SQLITE_STATIC);
|
||||
if( SQLITE_ROW==sqlite3_step(pIdxSelect) ){
|
||||
i64 val = sqlite3_column_int(pIdxSelect, 0);
|
||||
iPg = (int)(val>>1);
|
||||
bDlidx = (val & 0x0001);
|
||||
}
|
||||
p->rc = sqlite3_reset(p->pIdxSelect);
|
||||
p->rc = sqlite3_reset(pIdxSelect);
|
||||
|
||||
if( iPg<pSeg->pgnoFirst ){
|
||||
iPg = pSeg->pgnoFirst;
|
||||
@ -2743,6 +2840,7 @@ static void fts5MultiIterNext(
|
||||
i64 iFrom /* Advance at least as far as this */
|
||||
){
|
||||
int bUseFrom = bFrom;
|
||||
assert( pIter->base.bEof==0 );
|
||||
while( p->rc==SQLITE_OK ){
|
||||
int iFirst = pIter->aFirst[1].iFirst;
|
||||
int bNewTerm = 0;
|
||||
@ -3113,6 +3211,15 @@ static void fts5IterSetOutputs_Nocolset(Fts5Iter *pIter, Fts5SegIter *pSeg){
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** xSetOutputs callback used when the Fts5Colset object has nCol==0 (match
|
||||
** against no columns at all).
|
||||
*/
|
||||
static void fts5IterSetOutputs_ZeroColset(Fts5Iter *pIter, Fts5SegIter *pSeg){
|
||||
UNUSED_PARAM(pSeg);
|
||||
pIter->base.nData = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** xSetOutputs callback used by detail=col when there is a column filter
|
||||
** and there are 100 or more columns. Also called as a fallback from
|
||||
@ -3162,7 +3269,7 @@ static void fts5IterSetOutputs_Col100(Fts5Iter *pIter, Fts5SegIter *pSeg){
|
||||
if( aiCol==aiColEnd ) goto setoutputs_col_out;
|
||||
}
|
||||
if( *aiCol==iPrev ){
|
||||
*aOut++ = (iPrev - iPrevOut) + 2;
|
||||
*aOut++ = (u8)((iPrev - iPrevOut) + 2);
|
||||
iPrevOut = iPrev;
|
||||
}
|
||||
}
|
||||
@ -3218,6 +3325,10 @@ static void fts5IterSetOutputCb(int *pRc, Fts5Iter *pIter){
|
||||
pIter->xSetOutputs = fts5IterSetOutputs_Nocolset;
|
||||
}
|
||||
|
||||
else if( pIter->pColset->nCol==0 ){
|
||||
pIter->xSetOutputs = fts5IterSetOutputs_ZeroColset;
|
||||
}
|
||||
|
||||
else if( pConfig->eDetail==FTS5_DETAIL_FULL ){
|
||||
pIter->xSetOutputs = fts5IterSetOutputs_Full;
|
||||
}
|
||||
@ -3448,18 +3559,46 @@ static int fts5AllocateSegid(Fts5Index *p, Fts5Structure *pStruct){
|
||||
if( pStruct->nSegment>=FTS5_MAX_SEGMENT ){
|
||||
p->rc = SQLITE_FULL;
|
||||
}else{
|
||||
while( iSegid==0 ){
|
||||
int iLvl, iSeg;
|
||||
sqlite3_randomness(sizeof(u32), (void*)&iSegid);
|
||||
iSegid = iSegid & ((1 << FTS5_DATA_ID_B)-1);
|
||||
for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){
|
||||
for(iSeg=0; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){
|
||||
if( iSegid==pStruct->aLevel[iLvl].aSeg[iSeg].iSegid ){
|
||||
iSegid = 0;
|
||||
}
|
||||
/* FTS5_MAX_SEGMENT is currently defined as 2000. So the following
|
||||
** array is 63 elements, or 252 bytes, in size. */
|
||||
u32 aUsed[(FTS5_MAX_SEGMENT+31) / 32];
|
||||
int iLvl, iSeg;
|
||||
int i;
|
||||
u32 mask;
|
||||
memset(aUsed, 0, sizeof(aUsed));
|
||||
for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){
|
||||
for(iSeg=0; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){
|
||||
int iId = pStruct->aLevel[iLvl].aSeg[iSeg].iSegid;
|
||||
if( iId<=FTS5_MAX_SEGMENT ){
|
||||
aUsed[(iId-1) / 32] |= 1 << ((iId-1) % 32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(i=0; aUsed[i]==0xFFFFFFFF; i++);
|
||||
mask = aUsed[i];
|
||||
for(iSegid=0; mask & (1 << iSegid); iSegid++);
|
||||
iSegid += 1 + i*32;
|
||||
|
||||
#ifdef SQLITE_DEBUG
|
||||
for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){
|
||||
for(iSeg=0; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){
|
||||
assert( iSegid!=pStruct->aLevel[iLvl].aSeg[iSeg].iSegid );
|
||||
}
|
||||
}
|
||||
assert( iSegid>0 && iSegid<=FTS5_MAX_SEGMENT );
|
||||
|
||||
{
|
||||
sqlite3_stmt *pIdxSelect = fts5IdxSelectStmt(p);
|
||||
if( p->rc==SQLITE_OK ){
|
||||
u8 aBlob[2] = {0xff, 0xff};
|
||||
sqlite3_bind_int(pIdxSelect, 1, iSegid);
|
||||
sqlite3_bind_blob(pIdxSelect, 2, aBlob, 2, SQLITE_STATIC);
|
||||
assert( sqlite3_step(pIdxSelect)!=SQLITE_ROW );
|
||||
p->rc = sqlite3_reset(pIdxSelect);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@ -3705,6 +3844,9 @@ static void fts5WriteFlushLeaf(Fts5Index *p, Fts5SegWriter *pWriter){
|
||||
Fts5PageWriter *pPage = &pWriter->writer;
|
||||
i64 iRowid;
|
||||
|
||||
static int nCall = 0;
|
||||
nCall++;
|
||||
|
||||
assert( (pPage->pgidx.n==0)==(pWriter->bFirstTermInPage) );
|
||||
|
||||
/* Set the szLeaf header field. */
|
||||
@ -3904,7 +4046,9 @@ static void fts5WriteFinish(
|
||||
fts5WriteFlushLeaf(p, pWriter);
|
||||
}
|
||||
*pnLeaf = pLeaf->pgno-1;
|
||||
fts5WriteFlushBtree(p, pWriter);
|
||||
if( pLeaf->pgno>1 ){
|
||||
fts5WriteFlushBtree(p, pWriter);
|
||||
}
|
||||
}
|
||||
fts5BufferFree(&pLeaf->term);
|
||||
fts5BufferFree(&pLeaf->buf);
|
||||
@ -4174,13 +4318,17 @@ static void fts5IndexMergeLevel(
|
||||
|
||||
/*
|
||||
** Do up to nPg pages of automerge work on the index.
|
||||
**
|
||||
** Return true if any changes were actually made, or false otherwise.
|
||||
*/
|
||||
static void fts5IndexMerge(
|
||||
static int fts5IndexMerge(
|
||||
Fts5Index *p, /* FTS5 backend object */
|
||||
Fts5Structure **ppStruct, /* IN/OUT: Current structure of index */
|
||||
int nPg /* Pages of work to do */
|
||||
int nPg, /* Pages of work to do */
|
||||
int nMin /* Minimum number of segments to merge */
|
||||
){
|
||||
int nRem = nPg;
|
||||
int bRet = 0;
|
||||
Fts5Structure *pStruct = *ppStruct;
|
||||
while( nRem>0 && p->rc==SQLITE_OK ){
|
||||
int iLvl; /* To iterate through levels */
|
||||
@ -4211,17 +4359,17 @@ static void fts5IndexMerge(
|
||||
}
|
||||
#endif
|
||||
|
||||
if( nBest<p->pConfig->nAutomerge
|
||||
&& pStruct->aLevel[iBestLvl].nMerge==0
|
||||
){
|
||||
if( nBest<nMin && pStruct->aLevel[iBestLvl].nMerge==0 ){
|
||||
break;
|
||||
}
|
||||
bRet = 1;
|
||||
fts5IndexMergeLevel(p, &pStruct, iBestLvl, &nRem);
|
||||
if( p->rc==SQLITE_OK && pStruct->aLevel[iBestLvl].nMerge==0 ){
|
||||
fts5StructurePromote(p, iBestLvl+1, pStruct);
|
||||
}
|
||||
}
|
||||
*ppStruct = pStruct;
|
||||
return bRet;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -4249,7 +4397,7 @@ static void fts5IndexAutomerge(
|
||||
pStruct->nWriteCounter += nLeaf;
|
||||
nRem = (int)(p->nWorkUnit * nWork * pStruct->nLevel);
|
||||
|
||||
fts5IndexMerge(p, ppStruct, nRem);
|
||||
fts5IndexMerge(p, ppStruct, nRem, p->pConfig->nAutomerge);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4319,6 +4467,7 @@ static void fts5FlushOneHash(Fts5Index *p){
|
||||
** for the new level-0 segment. */
|
||||
pStruct = fts5StructureRead(p);
|
||||
iSegid = fts5AllocateSegid(p, pStruct);
|
||||
fts5StructureInvalidate(p);
|
||||
|
||||
if( iSegid ){
|
||||
const int pgsz = p->pConfig->pgsz;
|
||||
@ -4469,28 +4618,41 @@ static void fts5IndexFlush(Fts5Index *p){
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int sqlite3Fts5IndexOptimize(Fts5Index *p){
|
||||
Fts5Structure *pStruct;
|
||||
static Fts5Structure *fts5IndexOptimizeStruct(
|
||||
Fts5Index *p,
|
||||
Fts5Structure *pStruct
|
||||
){
|
||||
Fts5Structure *pNew = 0;
|
||||
int nSeg = 0;
|
||||
int nByte = sizeof(Fts5Structure);
|
||||
int nSeg = pStruct->nSegment;
|
||||
int i;
|
||||
|
||||
assert( p->rc==SQLITE_OK );
|
||||
fts5IndexFlush(p);
|
||||
pStruct = fts5StructureRead(p);
|
||||
|
||||
if( pStruct ){
|
||||
assert( pStruct->nSegment==fts5StructureCountSegments(pStruct) );
|
||||
nSeg = pStruct->nSegment;
|
||||
if( nSeg>1 ){
|
||||
int nByte = sizeof(Fts5Structure);
|
||||
nByte += (pStruct->nLevel+1) * sizeof(Fts5StructureLevel);
|
||||
pNew = (Fts5Structure*)sqlite3Fts5MallocZero(&p->rc, nByte);
|
||||
/* Figure out if this structure requires optimization. A structure does
|
||||
** not require optimization if either:
|
||||
**
|
||||
** + it consists of fewer than two segments, or
|
||||
** + all segments are on the same level, or
|
||||
** + all segments except one are currently inputs to a merge operation.
|
||||
**
|
||||
** In the first case, return NULL. In the second, increment the ref-count
|
||||
** on *pStruct and return a copy of the pointer to it.
|
||||
*/
|
||||
if( nSeg<2 ) return 0;
|
||||
for(i=0; i<pStruct->nLevel; i++){
|
||||
int nThis = pStruct->aLevel[i].nSeg;
|
||||
if( nThis==nSeg || (nThis==nSeg-1 && pStruct->aLevel[i].nMerge==nThis) ){
|
||||
fts5StructureRef(pStruct);
|
||||
return pStruct;
|
||||
}
|
||||
assert( pStruct->aLevel[i].nMerge<=nThis );
|
||||
}
|
||||
|
||||
nByte += (pStruct->nLevel+1) * sizeof(Fts5StructureLevel);
|
||||
pNew = (Fts5Structure*)sqlite3Fts5MallocZero(&p->rc, nByte);
|
||||
|
||||
if( pNew ){
|
||||
Fts5StructureLevel *pLvl;
|
||||
int nByte = nSeg * sizeof(Fts5StructureSegment);
|
||||
nByte = nSeg * sizeof(Fts5StructureSegment);
|
||||
pNew->nLevel = pStruct->nLevel+1;
|
||||
pNew->nRef = 1;
|
||||
pNew->nWriteCounter = pStruct->nWriteCounter;
|
||||
@ -4499,7 +4661,10 @@ int sqlite3Fts5IndexOptimize(Fts5Index *p){
|
||||
if( pLvl->aSeg ){
|
||||
int iLvl, iSeg;
|
||||
int iSegOut = 0;
|
||||
for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){
|
||||
/* Iterate through all segments, from oldest to newest. Add them to
|
||||
** the new Fts5Level object so that pLvl->aSeg[0] is the oldest
|
||||
** segment in the data structure. */
|
||||
for(iLvl=pStruct->nLevel-1; iLvl>=0; iLvl--){
|
||||
for(iSeg=0; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){
|
||||
pLvl->aSeg[iSegOut] = pStruct->aLevel[iLvl].aSeg[iSeg];
|
||||
iSegOut++;
|
||||
@ -4512,8 +4677,27 @@ int sqlite3Fts5IndexOptimize(Fts5Index *p){
|
||||
}
|
||||
}
|
||||
|
||||
return pNew;
|
||||
}
|
||||
|
||||
int sqlite3Fts5IndexOptimize(Fts5Index *p){
|
||||
Fts5Structure *pStruct;
|
||||
Fts5Structure *pNew = 0;
|
||||
|
||||
assert( p->rc==SQLITE_OK );
|
||||
fts5IndexFlush(p);
|
||||
pStruct = fts5StructureRead(p);
|
||||
fts5StructureInvalidate(p);
|
||||
|
||||
if( pStruct ){
|
||||
pNew = fts5IndexOptimizeStruct(p, pStruct);
|
||||
}
|
||||
fts5StructureRelease(pStruct);
|
||||
|
||||
assert( pNew==0 || pNew->nSegment>0 );
|
||||
if( pNew ){
|
||||
int iLvl = pNew->nLevel-1;
|
||||
int iLvl;
|
||||
for(iLvl=0; pNew->aLevel[iLvl].nSeg==0; iLvl++){}
|
||||
while( p->rc==SQLITE_OK && pNew->aLevel[iLvl].nSeg>0 ){
|
||||
int nRem = FTS5_OPT_WORK_UNIT;
|
||||
fts5IndexMergeLevel(p, &pNew, iLvl, &nRem);
|
||||
@ -4523,20 +4707,32 @@ int sqlite3Fts5IndexOptimize(Fts5Index *p){
|
||||
fts5StructureRelease(pNew);
|
||||
}
|
||||
|
||||
fts5StructureRelease(pStruct);
|
||||
return fts5IndexReturn(p);
|
||||
}
|
||||
|
||||
/*
|
||||
** This is called to implement the special "VALUES('merge', $nMerge)"
|
||||
** INSERT command.
|
||||
*/
|
||||
int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){
|
||||
Fts5Structure *pStruct;
|
||||
|
||||
pStruct = fts5StructureRead(p);
|
||||
if( pStruct && pStruct->nLevel ){
|
||||
fts5IndexMerge(p, &pStruct, nMerge);
|
||||
fts5StructureWrite(p, pStruct);
|
||||
Fts5Structure *pStruct = fts5StructureRead(p);
|
||||
if( pStruct ){
|
||||
int nMin = p->pConfig->nUsermerge;
|
||||
fts5StructureInvalidate(p);
|
||||
if( nMerge<0 ){
|
||||
Fts5Structure *pNew = fts5IndexOptimizeStruct(p, pStruct);
|
||||
fts5StructureRelease(pStruct);
|
||||
pStruct = pNew;
|
||||
nMin = 2;
|
||||
nMerge = nMerge*-1;
|
||||
}
|
||||
if( pStruct && pStruct->nLevel ){
|
||||
if( fts5IndexMerge(p, &pStruct, nMerge, nMin) ){
|
||||
fts5StructureWrite(p, pStruct);
|
||||
}
|
||||
}
|
||||
fts5StructureRelease(pStruct);
|
||||
}
|
||||
fts5StructureRelease(pStruct);
|
||||
|
||||
return fts5IndexReturn(p);
|
||||
}
|
||||
|
||||
@ -4950,6 +5146,7 @@ int sqlite3Fts5IndexSync(Fts5Index *p, int bCommit){
|
||||
int sqlite3Fts5IndexRollback(Fts5Index *p){
|
||||
fts5CloseReader(p);
|
||||
fts5IndexDiscardData(p);
|
||||
fts5StructureInvalidate(p);
|
||||
/* assert( p->rc==SQLITE_OK ); */
|
||||
return SQLITE_OK;
|
||||
}
|
||||
@ -4961,6 +5158,7 @@ int sqlite3Fts5IndexRollback(Fts5Index *p){
|
||||
*/
|
||||
int sqlite3Fts5IndexReinit(Fts5Index *p){
|
||||
Fts5Structure s;
|
||||
fts5StructureInvalidate(p);
|
||||
memset(&s, 0, sizeof(Fts5Structure));
|
||||
fts5DataWrite(p, FTS5_AVERAGES_ROWID, (const u8*)"", 0);
|
||||
fts5StructureWrite(p, &s);
|
||||
@ -5019,11 +5217,13 @@ int sqlite3Fts5IndexClose(Fts5Index *p){
|
||||
int rc = SQLITE_OK;
|
||||
if( p ){
|
||||
assert( p->pReader==0 );
|
||||
fts5StructureInvalidate(p);
|
||||
sqlite3_finalize(p->pWriter);
|
||||
sqlite3_finalize(p->pDeleter);
|
||||
sqlite3_finalize(p->pIdxWriter);
|
||||
sqlite3_finalize(p->pIdxDeleter);
|
||||
sqlite3_finalize(p->pIdxSelect);
|
||||
sqlite3_finalize(p->pDataVersion);
|
||||
sqlite3Fts5HashFree(p->pHash);
|
||||
sqlite3_free(p->zDataTbl);
|
||||
sqlite3_free(p);
|
||||
@ -6279,3 +6479,12 @@ int sqlite3Fts5IndexInit(sqlite3 *db){
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
int sqlite3Fts5IndexReset(Fts5Index *p){
|
||||
assert( p->pStruct==0 || p->iStructVersion!=0 );
|
||||
if( fts5IndexDataVersion(p)!=p->iStructVersion ){
|
||||
fts5StructureInvalidate(p);
|
||||
}
|
||||
return fts5IndexReturn(p);
|
||||
}
|
||||
|
||||
@ -597,27 +597,38 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
static int fts5NewTransaction(Fts5Table *pTab){
|
||||
Fts5Cursor *pCsr;
|
||||
for(pCsr=pTab->pGlobal->pCsr; pCsr; pCsr=pCsr->pNext){
|
||||
if( pCsr->base.pVtab==(sqlite3_vtab*)pTab ) return SQLITE_OK;
|
||||
}
|
||||
return sqlite3Fts5StorageReset(pTab->pStorage);
|
||||
}
|
||||
|
||||
/*
|
||||
** Implementation of xOpen method.
|
||||
*/
|
||||
static int fts5OpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){
|
||||
Fts5Table *pTab = (Fts5Table*)pVTab;
|
||||
Fts5Config *pConfig = pTab->pConfig;
|
||||
Fts5Cursor *pCsr; /* New cursor object */
|
||||
Fts5Cursor *pCsr = 0; /* New cursor object */
|
||||
int nByte; /* Bytes of space to allocate */
|
||||
int rc = SQLITE_OK; /* Return code */
|
||||
int rc; /* Return code */
|
||||
|
||||
nByte = sizeof(Fts5Cursor) + pConfig->nCol * sizeof(int);
|
||||
pCsr = (Fts5Cursor*)sqlite3_malloc(nByte);
|
||||
if( pCsr ){
|
||||
Fts5Global *pGlobal = pTab->pGlobal;
|
||||
memset(pCsr, 0, nByte);
|
||||
pCsr->aColumnSize = (int*)&pCsr[1];
|
||||
pCsr->pNext = pGlobal->pCsr;
|
||||
pGlobal->pCsr = pCsr;
|
||||
pCsr->iCsrId = ++pGlobal->iNextId;
|
||||
}else{
|
||||
rc = SQLITE_NOMEM;
|
||||
rc = fts5NewTransaction(pTab);
|
||||
if( rc==SQLITE_OK ){
|
||||
nByte = sizeof(Fts5Cursor) + pConfig->nCol * sizeof(int);
|
||||
pCsr = (Fts5Cursor*)sqlite3_malloc(nByte);
|
||||
if( pCsr ){
|
||||
Fts5Global *pGlobal = pTab->pGlobal;
|
||||
memset(pCsr, 0, nByte);
|
||||
pCsr->aColumnSize = (int*)&pCsr[1];
|
||||
pCsr->pNext = pGlobal->pCsr;
|
||||
pGlobal->pCsr = pCsr;
|
||||
pCsr->iCsrId = ++pGlobal->iNextId;
|
||||
}else{
|
||||
rc = SQLITE_NOMEM;
|
||||
}
|
||||
}
|
||||
*ppCsr = (sqlite3_vtab_cursor*)pCsr;
|
||||
return rc;
|
||||
@ -1175,7 +1186,6 @@ static int fts5FilterMethod(
|
||||
pCsr->ePlan = FTS5_PLAN_SOURCE;
|
||||
pCsr->pExpr = pTab->pSortCsr->pExpr;
|
||||
rc = fts5CursorFirst(pTab, pCsr, bDesc);
|
||||
sqlite3Fts5ExprClearEof(pCsr->pExpr);
|
||||
}else if( pMatch ){
|
||||
const char *zExpr = (const char*)sqlite3_value_text(apVal[0]);
|
||||
if( zExpr==0 ) zExpr = "";
|
||||
@ -1511,13 +1521,13 @@ static int fts5UpdateMethod(
|
||||
rc = SQLITE_ERROR;
|
||||
}
|
||||
|
||||
/* Case 1: DELETE */
|
||||
/* DELETE */
|
||||
else if( nArg==1 ){
|
||||
i64 iDel = sqlite3_value_int64(apVal[0]); /* Rowid to delete */
|
||||
rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, 0);
|
||||
}
|
||||
|
||||
/* Case 2: INSERT */
|
||||
/* INSERT */
|
||||
else if( eType0!=SQLITE_INTEGER ){
|
||||
/* If this is a REPLACE, first remove the current entry (if any) */
|
||||
if( eConflict==SQLITE_REPLACE
|
||||
@ -1529,7 +1539,7 @@ static int fts5UpdateMethod(
|
||||
fts5StorageInsert(&rc, pTab, apVal, pRowid);
|
||||
}
|
||||
|
||||
/* Case 2: UPDATE */
|
||||
/* UPDATE */
|
||||
else{
|
||||
i64 iOld = sqlite3_value_int64(apVal[0]); /* Old rowid */
|
||||
i64 iNew = sqlite3_value_int64(apVal[1]); /* New rowid */
|
||||
@ -1578,8 +1588,8 @@ static int fts5SyncMethod(sqlite3_vtab *pVtab){
|
||||
** Implementation of xBegin() method.
|
||||
*/
|
||||
static int fts5BeginMethod(sqlite3_vtab *pVtab){
|
||||
UNUSED_PARAM(pVtab); /* Call below is a no-op for NDEBUG builds */
|
||||
fts5CheckTransactionState((Fts5Table*)pVtab, FTS5_BEGIN, 0);
|
||||
fts5NewTransaction((Fts5Table*)pVtab);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
@ -2665,6 +2675,17 @@ static int fts5Init(sqlite3 *db){
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* If SQLITE_FTS5_ENABLE_TEST_MI is defined, assume that the file
|
||||
** fts5_test_mi.c is compiled and linked into the executable. And call
|
||||
** its entry point to enable the matchinfo() demo. */
|
||||
#ifdef SQLITE_FTS5_ENABLE_TEST_MI
|
||||
if( rc==SQLITE_OK ){
|
||||
extern int sqlite3Fts5TestRegisterMatchinfo(sqlite3*);
|
||||
rc = sqlite3Fts5TestRegisterMatchinfo(db);
|
||||
}
|
||||
#endif
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
@ -145,6 +145,7 @@ static int fts5StorageGetStmt(
|
||||
}
|
||||
|
||||
*ppStmt = p->aStmt[eStmt];
|
||||
sqlite3_reset(*ppStmt);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@ -246,7 +247,11 @@ int sqlite3Fts5CreateTable(
|
||||
char *zErr = 0;
|
||||
|
||||
rc = fts5ExecPrintf(pConfig->db, &zErr, "CREATE TABLE %Q.'%q_%q'(%s)%s",
|
||||
pConfig->zDb, pConfig->zName, zPost, zDefn, bWithout?" WITHOUT ROWID":""
|
||||
pConfig->zDb, pConfig->zName, zPost, zDefn,
|
||||
#ifndef SQLITE_FTS5_NO_WITHOUT_ROWID
|
||||
bWithout?" WITHOUT ROWID":
|
||||
#endif
|
||||
""
|
||||
);
|
||||
if( zErr ){
|
||||
*pzErr = sqlite3_mprintf(
|
||||
@ -368,6 +373,7 @@ static int fts5StorageInsertCallback(
|
||||
Fts5InsertCtx *pCtx = (Fts5InsertCtx*)pContext;
|
||||
Fts5Index *pIdx = pCtx->pStorage->pIndex;
|
||||
UNUSED_PARAM2(iUnused1, iUnused2);
|
||||
if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE;
|
||||
if( (tflags & FTS5_TOKEN_COLOCATED)==0 || pCtx->szCol==0 ){
|
||||
pCtx->szCol++;
|
||||
}
|
||||
@ -639,6 +645,10 @@ int sqlite3Fts5StorageMerge(Fts5Storage *p, int nMerge){
|
||||
return sqlite3Fts5IndexMerge(p->pIndex, nMerge);
|
||||
}
|
||||
|
||||
int sqlite3Fts5StorageReset(Fts5Storage *p){
|
||||
return sqlite3Fts5IndexReset(p->pIndex);
|
||||
}
|
||||
|
||||
/*
|
||||
** Allocate a new rowid. This is used for "external content" tables when
|
||||
** a NULL value is inserted into the rowid column. The new rowid is allocated
|
||||
@ -810,6 +820,7 @@ static int fts5StorageIntegrityCallback(
|
||||
int iCol;
|
||||
|
||||
UNUSED_PARAM2(iUnused1, iUnused2);
|
||||
if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE;
|
||||
|
||||
if( (tflags & FTS5_TOKEN_COLOCATED)==0 || pCtx->szCol==0 ){
|
||||
pCtx->szCol++;
|
||||
@ -1121,5 +1132,3 @@ int sqlite3Fts5StorageConfigValue(
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -14,7 +14,14 @@
|
||||
|
||||
|
||||
#ifdef SQLITE_TEST
|
||||
#include <tcl.h>
|
||||
#if defined(INCLUDE_SQLITE_TCL_H)
|
||||
# include "sqlite_tcl.h"
|
||||
#else
|
||||
# include "tcl.h"
|
||||
# ifndef SQLITE_TCLAPI
|
||||
# define SQLITE_TCLAPI
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_ENABLE_FTS5
|
||||
|
||||
@ -78,7 +85,7 @@ static int f5tResultToErrorCode(const char *zRes){
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
|
||||
static int f5tDbAndApi(
|
||||
static int SQLITE_TCLAPI f5tDbAndApi(
|
||||
Tcl_Interp *interp,
|
||||
Tcl_Obj *pObj,
|
||||
sqlite3 **ppDb,
|
||||
@ -164,7 +171,7 @@ static int xTokenizeCb(
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int xF5tApi(void*, Tcl_Interp*, int, Tcl_Obj *CONST []);
|
||||
static int SQLITE_TCLAPI xF5tApi(void*, Tcl_Interp*, int, Tcl_Obj *CONST []);
|
||||
|
||||
static int xQueryPhraseCb(
|
||||
const Fts5ExtensionApi *pApi,
|
||||
@ -209,7 +216,7 @@ static void xSetAuxdataDestructor(void *p){
|
||||
**
|
||||
** Description...
|
||||
*/
|
||||
static int xF5tApi(
|
||||
static int SQLITE_TCLAPI xF5tApi(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
@ -602,7 +609,7 @@ static void xF5tDestroy(void *pCtx){
|
||||
**
|
||||
** Description...
|
||||
*/
|
||||
static int f5tCreateFunction(
|
||||
static int SQLITE_TCLAPI f5tCreateFunction(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
@ -672,7 +679,7 @@ static int xTokenizeCb2(
|
||||
**
|
||||
** Description...
|
||||
*/
|
||||
static int f5tTokenize(
|
||||
static int SQLITE_TCLAPI f5tTokenize(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
@ -878,7 +885,7 @@ static int f5tTokenizerTokenize(
|
||||
/*
|
||||
** sqlite3_fts5_token ?-colocated? TEXT START END
|
||||
*/
|
||||
static int f5tTokenizerReturn(
|
||||
static int SQLITE_TCLAPI f5tTokenizerReturn(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
@ -949,7 +956,7 @@ static void f5tDelTokenizer(void *pCtx){
|
||||
** SCRIPT2 should invoke the [sqlite3_fts5_token] command once for each
|
||||
** token within the tokenized text.
|
||||
*/
|
||||
static int f5tCreateTokenizer(
|
||||
static int SQLITE_TCLAPI f5tCreateTokenizer(
|
||||
ClientData clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
@ -992,7 +999,7 @@ static int f5tCreateTokenizer(
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
static void xF5tFree(ClientData clientData){
|
||||
static void SQLITE_TCLAPI xF5tFree(ClientData clientData){
|
||||
ckfree(clientData);
|
||||
}
|
||||
|
||||
@ -1001,7 +1008,7 @@ static void xF5tFree(ClientData clientData){
|
||||
**
|
||||
** Set or clear the global "may-be-corrupt" flag. Return the old value.
|
||||
*/
|
||||
static int f5tMayBeCorrupt(
|
||||
static int SQLITE_TCLAPI f5tMayBeCorrupt(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
@ -1033,7 +1040,7 @@ static unsigned int f5t_fts5HashKey(int nSlot, const char *p, int n){
|
||||
return (h % nSlot);
|
||||
}
|
||||
|
||||
static int f5tTokenHash(
|
||||
static int SQLITE_TCLAPI f5tTokenHash(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
@ -1058,7 +1065,7 @@ static int f5tTokenHash(
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
static int f5tRegisterMatchinfo(
|
||||
static int SQLITE_TCLAPI f5tRegisterMatchinfo(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
@ -1083,7 +1090,7 @@ static int f5tRegisterMatchinfo(
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
static int f5tRegisterTok(
|
||||
static int SQLITE_TCLAPI f5tRegisterTok(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
|
||||
@ -41,16 +41,17 @@
|
||||
*/
|
||||
|
||||
|
||||
#ifdef SQLITE_TEST
|
||||
#ifdef SQLITE_ENABLE_FTS5
|
||||
|
||||
#include "fts5.h"
|
||||
#include <tcl.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct Fts5MatchinfoCtx Fts5MatchinfoCtx;
|
||||
|
||||
#ifndef SQLITE_AMALGAMATION
|
||||
typedef unsigned int u32;
|
||||
#endif
|
||||
|
||||
struct Fts5MatchinfoCtx {
|
||||
int nCol; /* Number of cols in FTS5 table */
|
||||
@ -67,18 +68,22 @@ struct Fts5MatchinfoCtx {
|
||||
** If an error occurs, return NULL and leave an error in the database
|
||||
** handle (accessible using sqlite3_errcode()/errmsg()).
|
||||
*/
|
||||
static fts5_api *fts5_api_from_db(sqlite3 *db){
|
||||
fts5_api *pRet = 0;
|
||||
static int fts5_api_from_db(sqlite3 *db, fts5_api **ppApi){
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
int rc;
|
||||
|
||||
if( SQLITE_OK==sqlite3_prepare(db, "SELECT fts5()", -1, &pStmt, 0)
|
||||
&& SQLITE_ROW==sqlite3_step(pStmt)
|
||||
&& sizeof(pRet)==sqlite3_column_bytes(pStmt, 0)
|
||||
){
|
||||
memcpy(&pRet, sqlite3_column_blob(pStmt, 0), sizeof(pRet));
|
||||
*ppApi = 0;
|
||||
rc = sqlite3_prepare(db, "SELECT fts5()", -1, &pStmt, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
if( SQLITE_ROW==sqlite3_step(pStmt)
|
||||
&& sizeof(fts5_api*)==sqlite3_column_bytes(pStmt, 0)
|
||||
){
|
||||
memcpy(ppApi, sqlite3_column_blob(pStmt, 0), sizeof(fts5_api*));
|
||||
}
|
||||
rc = sqlite3_finalize(pStmt);
|
||||
}
|
||||
sqlite3_finalize(pStmt);
|
||||
return pRet;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
@ -244,11 +249,7 @@ static int fts5MatchinfoLocalCb(
|
||||
iOff>=0;
|
||||
pApi->xPhraseNext(pFts, &iter, &iCol, &iOff)
|
||||
){
|
||||
if( f=='b' ){
|
||||
aOut[iPhrase * ((p->nCol+31)/32) + iCol/32] |= ((u32)1 << iCol%32);
|
||||
}else{
|
||||
aOut[nMul * (iCol + iPhrase * p->nCol)]++;
|
||||
}
|
||||
aOut[nMul * (iCol + iPhrase * p->nCol)]++;
|
||||
}
|
||||
}
|
||||
|
||||
@ -402,14 +403,15 @@ int sqlite3Fts5TestRegisterMatchinfo(sqlite3 *db){
|
||||
/* Extract the FTS5 API pointer from the database handle. The
|
||||
** fts5_api_from_db() function above is copied verbatim from the
|
||||
** FTS5 documentation. Refer there for details. */
|
||||
pApi = fts5_api_from_db(db);
|
||||
rc = fts5_api_from_db(db, &pApi);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
|
||||
/* If fts5_api_from_db() returns NULL, then either FTS5 is not registered
|
||||
** with this database handle, or an error (OOM perhaps?) has occurred.
|
||||
**
|
||||
** Also check that the fts5_api object is version 2 or newer.
|
||||
*/
|
||||
if( pApi==0 || pApi->iVersion<1 ){
|
||||
if( pApi==0 || pApi->iVersion<2 ){
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
|
||||
@ -420,5 +422,4 @@ int sqlite3Fts5TestRegisterMatchinfo(sqlite3 *db){
|
||||
}
|
||||
|
||||
#endif /* SQLITE_ENABLE_FTS5 */
|
||||
#endif /* SQLITE_TEST */
|
||||
|
||||
|
||||
@ -279,8 +279,19 @@ static int fts5VocabBestIndexMethod(
|
||||
}
|
||||
}
|
||||
|
||||
pInfo->idxNum = idxNum;
|
||||
/* This virtual table always delivers results in ascending order of
|
||||
** the "term" column (column 0). So if the user has requested this
|
||||
** specifically - "ORDER BY term" or "ORDER BY term ASC" - set the
|
||||
** sqlite3_index_info.orderByConsumed flag to tell the core the results
|
||||
** are already in sorted order. */
|
||||
if( pInfo->nOrderBy==1
|
||||
&& pInfo->aOrderBy[0].iColumn==0
|
||||
&& pInfo->aOrderBy[0].desc==0
|
||||
){
|
||||
pInfo->orderByConsumed = 1;
|
||||
}
|
||||
|
||||
pInfo->idxNum = idxNum;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
|
||||
@ -34,7 +34,6 @@
|
||||
);
|
||||
}
|
||||
%stack_overflow {
|
||||
UNUSED_PARAM(yypMinor); /* Silence a compiler warning */
|
||||
sqlite3Fts5ParseError(pParse, "fts5: parser stack overflow");
|
||||
}
|
||||
|
||||
@ -105,7 +104,7 @@ expr(A) ::= exprlist(X). {A = X;}
|
||||
|
||||
exprlist(A) ::= cnearset(X). {A = X;}
|
||||
exprlist(A) ::= exprlist(X) cnearset(Y). {
|
||||
A = sqlite3Fts5ParseNode(pParse, FTS5_AND, X, Y, 0);
|
||||
A = sqlite3Fts5ParseImplicitAnd(pParse, X, Y);
|
||||
}
|
||||
|
||||
cnearset(A) ::= nearset(X). {
|
||||
@ -121,10 +120,17 @@ cnearset(A) ::= colset(X) COLON nearset(Y). {
|
||||
%type colsetlist {Fts5Colset*}
|
||||
%destructor colsetlist { sqlite3_free($$); }
|
||||
|
||||
colset(A) ::= MINUS LCP colsetlist(X) RCP. {
|
||||
A = sqlite3Fts5ParseColsetInvert(pParse, X);
|
||||
}
|
||||
colset(A) ::= LCP colsetlist(X) RCP. { A = X; }
|
||||
colset(A) ::= STRING(X). {
|
||||
A = sqlite3Fts5ParseColset(pParse, 0, &X);
|
||||
}
|
||||
colset(A) ::= MINUS STRING(X). {
|
||||
A = sqlite3Fts5ParseColset(pParse, 0, &X);
|
||||
A = sqlite3Fts5ParseColsetInvert(pParse, A);
|
||||
}
|
||||
|
||||
colsetlist(A) ::= colsetlist(Y) STRING(X). {
|
||||
A = sqlite3Fts5ParseColset(pParse, Y, &X); }
|
||||
@ -132,7 +138,6 @@ colsetlist(A) ::= STRING(X). {
|
||||
A = sqlite3Fts5ParseColset(pParse, 0, &X);
|
||||
}
|
||||
|
||||
|
||||
%type nearset {Fts5ExprNearset*}
|
||||
%type nearphrases {Fts5ExprNearset*}
|
||||
%destructor nearset { sqlite3Fts5ParseNearsetFree($$); }
|
||||
|
||||
@ -16,8 +16,13 @@ if {![info exists testdir]} {
|
||||
source $testdir/tester.tcl
|
||||
|
||||
ifcapable !fts5 {
|
||||
finish_test
|
||||
proc return_if_no_fts5 {} {
|
||||
finish_test
|
||||
return -code return
|
||||
}
|
||||
return
|
||||
} else {
|
||||
proc return_if_no_fts5 {} {}
|
||||
}
|
||||
|
||||
catch {
|
||||
@ -25,12 +30,6 @@ catch {
|
||||
reset_db
|
||||
}
|
||||
|
||||
# If SQLITE_ENABLE_FTS5 is not defined, skip this test.
|
||||
ifcapable !fts5 {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
proc fts5_test_poslist {cmd} {
|
||||
set res [list]
|
||||
for {set i 0} {$i < [$cmd xInstCount]} {incr i} {
|
||||
@ -160,6 +159,12 @@ proc fts5_aux_test_functions {db} {
|
||||
}
|
||||
}
|
||||
|
||||
proc fts5_segcount {tbl} {
|
||||
set N 0
|
||||
foreach n [fts5_level_segs $tbl] { incr N $n }
|
||||
set N
|
||||
}
|
||||
|
||||
proc fts5_level_segs {tbl} {
|
||||
set sql "SELECT fts5_decode(rowid,block) aS r FROM ${tbl}_data WHERE rowid=10"
|
||||
set ret [list]
|
||||
|
||||
@ -432,9 +432,16 @@ proc funk {} {
|
||||
}
|
||||
db func funk funk
|
||||
|
||||
# This test case corrupts the structure record within the first invocation
|
||||
# of function funk(). Which used to cause the bm25() function to throw an
|
||||
# exception. But since bm25() can now used the cached structure record,
|
||||
# it never sees the corruption introduced by funk() and so the following
|
||||
# statement no longer fails.
|
||||
#
|
||||
do_catchsql_test 16.2 {
|
||||
SELECT funk(), bm25(n1), funk() FROM n1 WHERE n1 MATCH 'a+b+c+d'
|
||||
} {1 {SQL logic error or missing database}}
|
||||
} {0 {{} -1e-06 {}}}
|
||||
# {1 {SQL logic error or missing database}}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
|
||||
@ -72,42 +72,56 @@ foreach {tn doc res} {
|
||||
2.2 {o X o o o o o o} {o [X] o o o o o...}
|
||||
2.3 {o o X o o o o o} {o o [X] o o o o...}
|
||||
2.4 {o o o X o o o o} {o o o [X] o o o...}
|
||||
2.5 {o o o o X o o o} {...o o o [X] o o o}
|
||||
2.6 {o o o o o X o o} {...o o o o [X] o o}
|
||||
2.7 {o o o o o o X o} {...o o o o o [X] o}
|
||||
2.5 {o o o o X o o o} {o o o o [X] o o...}
|
||||
2.6 {o o o o o X o o} {o o o o o [X] o...}
|
||||
2.7 {o o o o o o X o} {o o o o o o [X]...}
|
||||
2.8 {o o o o o o o X} {...o o o o o o [X]}
|
||||
|
||||
2.9 {o o o o o o o X o} {...o o o o o [X] o}
|
||||
2.10 {o o o o o o o X o o} {...o o o o [X] o o}
|
||||
2.11 {o o o o o o o X o o o} {...o o o [X] o o o}
|
||||
2.12 {o o o o o o o X o o o o} {...o o o [X] o o o...}
|
||||
|
||||
|
||||
3.1 {X o o o o o o o o} {[X] o o o o o o...}
|
||||
3.2 {o X o o o o o o o} {o [X] o o o o o...}
|
||||
3.3 {o o X o o o o o o} {o o [X] o o o o...}
|
||||
3.4 {o o o X o o o o o} {o o o [X] o o o...}
|
||||
3.5 {o o o o X o o o o} {...o o o [X] o o o...}
|
||||
3.6 {o o o o o X o o o} {...o o o [X] o o o}
|
||||
3.7 {o o o o o o X o o} {...o o o o [X] o o}
|
||||
3.8 {o o o o o o o X o} {...o o o o o [X] o}
|
||||
3.9 {o o o o o o o o X} {...o o o o o o [X]}
|
||||
|
||||
3.5 {o o o o o o o X o o o o} {...o o o [X] o o o...}
|
||||
3.6 {o o o o o o o o X o o o} {...o o o [X] o o o}
|
||||
3.7 {o o o o o o o o o X o o} {...o o o o [X] o o}
|
||||
3.8 {o o o o o o o o o o X o} {...o o o o o [X] o}
|
||||
3.9 {o o o o o o o o o o o X} {...o o o o o o [X]}
|
||||
|
||||
4.1 {X o o o o o X o o} {[X] o o o o o [X]...}
|
||||
4.2 {o X o o o o o X o} {...[X] o o o o o [X]...}
|
||||
4.3 {o o X o o o o o X} {...[X] o o o o o [X]}
|
||||
4.2 {o o o o o o o X o o o o o X o} {...[X] o o o o o [X]...}
|
||||
4.3 {o o o o o o o o X o o o o o X} {...[X] o o o o o [X]}
|
||||
|
||||
5.1 {X o o o o X o o o} {[X] o o o o [X] o...}
|
||||
5.2 {o X o o o o X o o} {...[X] o o o o [X] o...}
|
||||
5.3 {o o X o o o o X o} {...[X] o o o o [X] o}
|
||||
5.4 {o o o X o o o o X} {...o [X] o o o o [X]}
|
||||
5.2 {o o o o o o o X o o o o X o o} {...[X] o o o o [X] o...}
|
||||
5.3 {o o o o o o o o X o o o o X o} {...[X] o o o o [X] o}
|
||||
5.4 {o o o o o o o o o X o o o o X} {...o [X] o o o o [X]}
|
||||
|
||||
6.1 {X o o o X o o o} {[X] o o o [X] o o...}
|
||||
6.2 {o X o o o X o o o} {o [X] o o o [X] o...}
|
||||
6.3 {o o X o o o X o o} {...o [X] o o o [X] o...}
|
||||
6.4 {o o o X o o o X o} {...o [X] o o o [X] o}
|
||||
6.5 {o o o o X o o o X} {...o o [X] o o o [X]}
|
||||
6.3 {o o o o o o o X o o o X o o} {...o [X] o o o [X] o...}
|
||||
6.4 {o o o o o o o o X o o o X o} {...o [X] o o o [X] o}
|
||||
6.5 {o o o o o o o o o X o o o X} {...o o [X] o o o [X]}
|
||||
|
||||
7.1 {X o o X o o o o o} {[X] o o [X] o o o...}
|
||||
7.2 {o X o o X o o o o} {o [X] o o [X] o o...}
|
||||
7.3 {o o X o o X o o o} {...o [X] o o [X] o o...}
|
||||
7.4 {o o o X o o X o o} {...o [X] o o [X] o o}
|
||||
7.5 {o o o o X o o X o} {...o o [X] o o [X] o}
|
||||
7.6 {o o o o o X o o X} {...o o o [X] o o [X]}
|
||||
7.3 {o o o o o o o X o o X o o o} {...o [X] o o [X] o o...}
|
||||
7.4 {o o o o o o o o X o o X o o} {...o [X] o o [X] o o}
|
||||
7.5 {o o o o o o o o o X o o X o} {...o o [X] o o [X] o}
|
||||
7.6 {o o o o o o o o o o X o o X} {...o o o [X] o o [X]}
|
||||
|
||||
8.1 {o o o o o o o o o X o o o o o o o o o o o o o o o o X X X o o o}
|
||||
{...o o [X] [X] [X] o o...}
|
||||
8.2 {o o o o o o o. o o X o o o o o o o o o o o o o o o o X X X o o o}
|
||||
{...o o [X] o o o o...}
|
||||
8.3 {o o o o X o o o o o o o o o o o o o o o o o o o o o X X X o o o}
|
||||
{o o o o [X] o o...}
|
||||
} {
|
||||
do_snippet_test 1.$tn $doc X $res
|
||||
}
|
||||
@ -124,24 +138,43 @@ if {[detail_is_full]} {
|
||||
2.1 {X Y o o o o o o} {[X Y] o o o o o...}
|
||||
2.2 {o X Y o o o o o} {o [X Y] o o o o...}
|
||||
2.3 {o o X Y o o o o} {o o [X Y] o o o...}
|
||||
2.4 {o o o X Y o o o} {...o o [X Y] o o o}
|
||||
2.5 {o o o o X Y o o} {...o o o [X Y] o o}
|
||||
2.6 {o o o o o X Y o} {...o o o o [X Y] o}
|
||||
2.7 {o o o o o o X Y} {...o o o o o [X Y]}
|
||||
2.4 {o o o o o o o X Y o o o} {...o o [X Y] o o o}
|
||||
2.5 {o o o o o o o o X Y o o} {...o o o [X Y] o o}
|
||||
2.6 {o o o o o o o o o X Y o} {...o o o o [X Y] o}
|
||||
2.7 {o o o o o o o o o o X Y} {...o o o o o [X Y]}
|
||||
|
||||
3.1 {X Y o o o o o o o} {[X Y] o o o o o...}
|
||||
3.2 {o X Y o o o o o o} {o [X Y] o o o o...}
|
||||
3.3 {o o X Y o o o o o} {o o [X Y] o o o...}
|
||||
3.4 {o o o X Y o o o o} {...o o [X Y] o o o...}
|
||||
3.5 {o o o o X Y o o o} {...o o [X Y] o o o}
|
||||
3.6 {o o o o o X Y o o} {...o o o [X Y] o o}
|
||||
3.7 {o o o o o o X Y o} {...o o o o [X Y] o}
|
||||
3.8 {o o o o o o o X Y} {...o o o o o [X Y]}
|
||||
3.4 {o o o o o o o X Y o o o o} {...o o [X Y] o o o...}
|
||||
3.5 {o o o o o o o o X Y o o o} {...o o [X Y] o o o}
|
||||
3.6 {o o o o o o o o o X Y o o} {...o o o [X Y] o o}
|
||||
3.7 {o o o o o o o o o o X Y o} {...o o o o [X Y] o}
|
||||
3.8 {o o o o o o o o o o o X Y} {...o o o o o [X Y]}
|
||||
} {
|
||||
do_snippet_test 2.$tn $doc "X + Y" $res
|
||||
}
|
||||
}
|
||||
|
||||
do_execsql_test 4.0 {
|
||||
CREATE VIRTUAL TABLE x1 USING fts5(a, b);
|
||||
INSERT INTO x1 VALUES('xyz', '1 2 3 4 5 6 7 8 9 10 11 12 13');
|
||||
SELECT snippet(x1, 1, '[', ']', '...', 5) FROM x1('xyz');
|
||||
} {
|
||||
{1 2 3 4 5...}
|
||||
}
|
||||
|
||||
do_execsql_test 5.0 {
|
||||
CREATE VIRTUAL TABLE p1 USING fts5(a, b);
|
||||
INSERT INTO p1 VALUES(
|
||||
'x a a a a a a a a a a',
|
||||
'a a a a a a a a a a a a a a a a a a a x'
|
||||
);
|
||||
}
|
||||
do_execsql_test 5.1 {
|
||||
SELECT snippet(p1, 0, '[', ']', '...', 6) FROM p1('x');
|
||||
} {{[x] a a a a a...}}
|
||||
|
||||
} ;# foreach_detail_mode
|
||||
|
||||
finish_test
|
||||
|
||||
@ -246,5 +246,37 @@ foreach {tn lRow res} {
|
||||
} $res
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test the built-in bm25() demo.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 9.1 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(a, b);
|
||||
INSERT INTO t1 VALUES('a', NULL); -- 1
|
||||
INSERT INTO t1 VALUES('a', NULL); -- 2
|
||||
INSERT INTO t1 VALUES('a', NULL); -- 3
|
||||
INSERT INTO t1 VALUES('a', NULL); -- 4
|
||||
INSERT INTO t1 VALUES('a', NULL); -- 5
|
||||
INSERT INTO t1 VALUES('a', NULL); -- 6
|
||||
INSERT INTO t1 VALUES('a', NULL); -- 7
|
||||
INSERT INTO t1 VALUES('a', NULL); -- 8
|
||||
INSERT INTO t1 VALUES(NULL, 'a a b'); -- 9
|
||||
INSERT INTO t1 VALUES(NULL, 'b b a'); -- 10
|
||||
}
|
||||
|
||||
do_execsql_test 9.2 {
|
||||
SELECT rowid FROM t1('a AND b') ORDER BY rank;
|
||||
} {
|
||||
10 9
|
||||
}
|
||||
|
||||
do_execsql_test 9.3 {
|
||||
SELECT rowid FROM t1('b:a AND b:b') ORDER BY rank;
|
||||
} {
|
||||
9 10
|
||||
}
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
set testprefix fts5bigtok
|
||||
return_if_no_fts5
|
||||
|
||||
proc rndterm {} {
|
||||
set L [list a b c d e f g h i j k l m n o p q r s t u v w x y z]
|
||||
|
||||
59
ext/fts5/test/fts5colset.test
Normal file
59
ext/fts5/test/fts5colset.test
Normal file
@ -0,0 +1,59 @@
|
||||
# 2016 August 10
|
||||
#
|
||||
# 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. The
|
||||
# focus of this script is testing the FTS5 module.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
set testprefix fts5colset
|
||||
|
||||
# If SQLITE_ENABLE_FTS5 is not defined, omit this file.
|
||||
ifcapable !fts5 {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
foreach_detail_mode $::testprefix {
|
||||
if {[detail_is_none]} continue
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(a, b, c, d, detail=%DETAIL%);
|
||||
INSERT INTO t1 VALUES('a', 'b', 'c', 'd'); -- 1
|
||||
INSERT INTO t1 VALUES('d', 'a', 'b', 'c'); -- 2
|
||||
INSERT INTO t1 VALUES('c', 'd', 'a', 'b'); -- 3
|
||||
INSERT INTO t1 VALUES('b', 'c', 'd', 'a'); -- 4
|
||||
}
|
||||
|
||||
foreach {tn q res} {
|
||||
1 "a" {1 2 3 4}
|
||||
2 "{a} : a" {1}
|
||||
3 "-{a} : a" {2 3 4}
|
||||
4 "- {a c} : a" {2 4}
|
||||
5 " - {d d c} : a" {1 2}
|
||||
6 "- {d c b a} : a" {}
|
||||
7 "-{\"a\"} : b" {1 2 3}
|
||||
8 "- c : a" {1 2 4}
|
||||
9 "-c : a" {1 2 4}
|
||||
10 "-\"c\" : a" {1 2 4}
|
||||
} {
|
||||
breakpoint
|
||||
do_execsql_test 1.$tn {
|
||||
SELECT rowid FROM t1($q)
|
||||
} $res
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@ -247,5 +247,21 @@ do_catchsql_test 12.1 {
|
||||
INSERT INTO t1(t1, rank) VALUES('rank', NULL);;
|
||||
} {1 {SQL logic error or missing database}}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# errors in the 'usermerge' option
|
||||
#
|
||||
do_execsql_test 13.0 {
|
||||
CREATE VIRTUAL TABLE tt USING fts5(ttt);
|
||||
}
|
||||
foreach {tn val} {
|
||||
1 -1
|
||||
2 4.2
|
||||
3 17
|
||||
4 1
|
||||
} {
|
||||
set sql "INSERT INTO tt(tt, rank) VALUES('usermerge', $val)"
|
||||
do_catchsql_test 13.$tn $sql {1 {SQL logic error or missing database}}
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@ -37,7 +37,7 @@ do_execsql_test 1.0 {
|
||||
}
|
||||
set mask [expr 31 << 31]
|
||||
|
||||
if 1 {
|
||||
if 0 {
|
||||
|
||||
# Test 1:
|
||||
#
|
||||
@ -84,6 +84,8 @@ foreach {tno stmt} {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Using the same database as the 1.* tests.
|
||||
#
|
||||
# Run N-1 tests, where N is the number of bytes in the rightmost leaf page
|
||||
@ -212,8 +214,6 @@ foreach {tn nCut} {
|
||||
# do_test 4.$tn.x { expr $nCorrupt>0 } 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
set doc [string repeat "A B C " 1000]
|
||||
do_execsql_test 5.0 {
|
||||
CREATE VIRTUAL TABLE x5 USING fts5(tt);
|
||||
|
||||
@ -179,6 +179,10 @@ for {set i 1} {1} {incr i} {
|
||||
if {$end<=$i} break
|
||||
lset var end [expr $end - $i]
|
||||
set struct [binary format c* $var]
|
||||
|
||||
db close
|
||||
sqlite3 db test.db
|
||||
|
||||
db eval {
|
||||
BEGIN;
|
||||
UPDATE t1_data SET block = $struct WHERE id=10;
|
||||
|
||||
67
ext/fts5/test/fts5determin.test
Normal file
67
ext/fts5/test/fts5determin.test
Normal file
@ -0,0 +1,67 @@
|
||||
# 2016 March 21
|
||||
#
|
||||
# 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. The
|
||||
# focus of this script is testing the FTS5 module.
|
||||
#
|
||||
# Specifically, that the fts5 module is deterministic. At one point, when
|
||||
# segment ids were allocated using sqlite3_randomness(), this was not the
|
||||
# case.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
set testprefix fts5aa
|
||||
return_if_no_fts5
|
||||
|
||||
proc do_determin_test {tn} {
|
||||
uplevel [list
|
||||
do_execsql_test $tn {
|
||||
SELECT (SELECT md5sum(id, block) FROM t1_data)==
|
||||
(SELECT md5sum(id, block) FROM t2_data),
|
||||
(SELECT md5sum(id, block) FROM t1_data)==
|
||||
(SELECT md5sum(id, block) FROM t3_data)
|
||||
} {1 1}
|
||||
]
|
||||
}
|
||||
|
||||
foreach_detail_mode $::testprefix {
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(a, b, prefix="1 2", detail=%DETAIL%);
|
||||
CREATE VIRTUAL TABLE t2 USING fts5(a, b, prefix="1 2", detail=%DETAIL%);
|
||||
CREATE VIRTUAL TABLE t3 USING fts5(a, b, prefix="1 2", detail=%DETAIL%);
|
||||
}
|
||||
|
||||
do_test 1.1 {
|
||||
foreach t {t1 t2 t3} {
|
||||
execsql [string map [list TBL $t] {
|
||||
INSERT INTO TBL VALUES('a b c', 'd e f');
|
||||
INSERT INTO TBL VALUES('c1 c2 c3', 'c1 c2 c3');
|
||||
INSERT INTO TBL VALUES('xyzxyzxyz', 'xyzxyzxyz');
|
||||
}]
|
||||
}
|
||||
} {}
|
||||
|
||||
do_determin_test 1.2
|
||||
|
||||
do_test 1.3 {
|
||||
foreach t {t1 t2 t3} {
|
||||
execsql [string map [list TBL $t] {
|
||||
INSERT INTO TBL(TBL) VALUES('optimize');
|
||||
}]
|
||||
}
|
||||
} {}
|
||||
|
||||
do_determin_test 1.4
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@ -178,7 +178,7 @@ do_execsql_test 3.2 {
|
||||
ORDER BY rowid DESC;
|
||||
} {16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1}
|
||||
|
||||
do_execsql_test 3.2 {
|
||||
do_execsql_test 3.3 {
|
||||
INSERT INTO abc(abc) VALUES('integrity-check');
|
||||
INSERT INTO abc(abc) VALUES('optimize');
|
||||
INSERT INTO abc(abc) VALUES('integrity-check');
|
||||
@ -187,7 +187,7 @@ do_execsql_test 3.2 {
|
||||
set v [lindex $vocab 0]
|
||||
set i 0
|
||||
foreach v $vocab {
|
||||
do_execsql_test 3.3.[incr i] {
|
||||
do_execsql_test 3.4.[incr i] {
|
||||
SELECT rowid FROM abc WHERE abc MATCH $v
|
||||
} {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16}
|
||||
}
|
||||
|
||||
@ -33,12 +33,12 @@ foreach {tn expr res} {
|
||||
1 {abc} {"abc"}
|
||||
2 {abc ""} {"abc"}
|
||||
3 {""} {}
|
||||
4 {abc OR ""} {"abc"}
|
||||
5 {abc NOT ""} {"abc"}
|
||||
6 {abc AND ""} {"abc"}
|
||||
7 {"" OR abc} {"abc"}
|
||||
8 {"" NOT abc} {"abc"}
|
||||
9 {"" AND abc} {"abc"}
|
||||
4 {abc OR ""} {"abc" OR ""}
|
||||
5 {abc NOT ""} {"abc" NOT ""}
|
||||
6 {abc AND ""} {"abc" AND ""}
|
||||
7 {"" OR abc} {"" OR "abc"}
|
||||
8 {"" NOT abc} {"" NOT "abc"}
|
||||
9 {"" AND abc} {"" AND "abc"}
|
||||
10 {abc + "" + def} {"abc" + "def"}
|
||||
11 {abc "" def} {"abc" AND "def"}
|
||||
12 {r+e OR w} {"r" + "e" OR "w"}
|
||||
@ -63,6 +63,23 @@ do_catchsql_test 2.1 {
|
||||
SELECT fts5_expr_tcl()
|
||||
} {1 {wrong number of arguments to function fts5_expr_tcl}}
|
||||
|
||||
|
||||
do_execsql_test 3.0 {
|
||||
CREATE VIRTUAL TABLE e1 USING fts5(text, tokenize = 'porter unicode61');
|
||||
INSERT INTO e1 VALUES ("just a few words with a / inside");
|
||||
}
|
||||
do_execsql_test 3.1 {
|
||||
SELECT rowid, bm25(e1) FROM e1 WHERE e1 MATCH '"just"' ORDER BY rank;
|
||||
} {1 -1e-06}
|
||||
do_execsql_test 3.2 {
|
||||
SELECT rowid FROM e1 WHERE e1 MATCH '"/" OR "just"'
|
||||
} 1
|
||||
do_execsql_test 3.3 {
|
||||
SELECT rowid, bm25(e1) FROM e1 WHERE e1 MATCH '"/" OR "just"' ORDER BY rank;
|
||||
} {1 -1e-06}
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
|
||||
@ -86,7 +86,7 @@ set ::res [db eval {SELECT rowid, x1 FROM x1 WHERE x1 MATCH '*reads'}]
|
||||
do_faultsim_test 4 -faults oom-* -body {
|
||||
db eval {SELECT rowid, x, x1 FROM x1 WHERE x1 MATCH '*reads'}
|
||||
} -test {
|
||||
faultsim_test_result {0 {0 {} 4}}
|
||||
faultsim_test_result {0 {0 {} 3}}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
@ -54,7 +54,32 @@ foreach_detail_mode $testprefix {
|
||||
faultsim_test_result {0 {1 3}} {1 SQLITE_NOMEM}
|
||||
}
|
||||
}
|
||||
|
||||
} ;# foreach_detail_mode...
|
||||
|
||||
|
||||
do_execsql_test 4.0 {
|
||||
CREATE VIRTUAL TABLE x2 USING fts5(a);
|
||||
INSERT INTO x2(x2, rank) VALUES('crisismerge', 2);
|
||||
INSERT INTO x2(x2, rank) VALUES('pgsz', 32);
|
||||
INSERT INTO x2 VALUES('a b c d');
|
||||
INSERT INTO x2 VALUES('e f g h');
|
||||
INSERT INTO x2 VALUES('i j k l');
|
||||
INSERT INTO x2 VALUES('m n o p');
|
||||
INSERT INTO x2 VALUES('q r s t');
|
||||
INSERT INTO x2 VALUES('u v w x');
|
||||
INSERT INTO x2 VALUES('y z a b');
|
||||
}
|
||||
faultsim_save_and_close
|
||||
|
||||
do_faultsim_test 4 -faults oom-* -prep {
|
||||
faultsim_restore_and_reopen
|
||||
} -body {
|
||||
execsql { INSERT INTO x2(x2) VALUES('optimize') }
|
||||
} -test {
|
||||
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
83
ext/fts5/test/fts5faultB.test
Normal file
83
ext/fts5/test/fts5faultB.test
Normal file
@ -0,0 +1,83 @@
|
||||
# 2016 February 17
|
||||
#
|
||||
# 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 is focused on OOM errors.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
source $testdir/malloc_common.tcl
|
||||
set testprefix fts5faultB
|
||||
|
||||
# If SQLITE_ENABLE_FTS3 is defined, omit this file.
|
||||
ifcapable !fts5 {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
proc mit {blob} {
|
||||
set scan(littleEndian) i*
|
||||
set scan(bigEndian) I*
|
||||
binary scan $blob $scan($::tcl_platform(byteOrder)) r
|
||||
return $r
|
||||
}
|
||||
db func mit mit
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Errors while registering the matchinfo() demo function.
|
||||
#
|
||||
do_faultsim_test 1 -faults oom* -prep {
|
||||
sqlite3 db test.db
|
||||
} -body {
|
||||
sqlite3_fts5_register_matchinfo db
|
||||
} -test {
|
||||
faultsim_test_result {0 {}} {1 SQLITE_ERROR} {1 SQLITE_NOMEM}
|
||||
}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Errors while executing the matchinfo() demo function.
|
||||
#
|
||||
reset_db
|
||||
sqlite3_fts5_register_matchinfo db
|
||||
db func mit mit
|
||||
do_execsql_test 2 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(a, b);
|
||||
INSERT INTO t1 VALUES('x y z', '1 2 3');
|
||||
INSERT INTO t1 VALUES('x', '1 2 3 4 5 6 7');
|
||||
}
|
||||
|
||||
do_faultsim_test 2.1 -faults oom* -body {
|
||||
execsql { SELECT mit(matchinfo(t1, 'a')) FROM t1('x') }
|
||||
} -test {
|
||||
faultsim_test_result {0 {{2 5} {2 5}}}
|
||||
}
|
||||
|
||||
do_faultsim_test 2.2 -faults oom* -body {
|
||||
execsql { SELECT mit(matchinfo(t1, 'l')) FROM t1('x') }
|
||||
} -test {
|
||||
faultsim_test_result {0 {{3 3} {1 7}}}
|
||||
}
|
||||
|
||||
do_execsql_test 2.3 {
|
||||
INSERT INTO t1 VALUES('a b c d e f', 'a b d e f c');
|
||||
INSERT INTO t1 VALUES('l m b c a', 'n o a b c z');
|
||||
}
|
||||
|
||||
do_faultsim_test 2.4 -faults oom* -body {
|
||||
execsql { SELECT mit(matchinfo(t1, 's')) FROM t1('a b c') }
|
||||
} -test {
|
||||
faultsim_test_result {0 {{3 2} {2 3}}}
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
93
ext/fts5/test/fts5fuzz1.test
Normal file
93
ext/fts5/test/fts5fuzz1.test
Normal file
@ -0,0 +1,93 @@
|
||||
# 2014 June 17
|
||||
#
|
||||
# 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. The
|
||||
# focus of this script is testing the FTS5 module.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
return_if_no_fts5
|
||||
set testprefix fts5fuzz1
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_catchsql_test 1.1 {
|
||||
CREATE VIRTUAL TABLE f1 USING fts5(a b);
|
||||
} {/1 {parse error in.*}/}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 2.1 {
|
||||
CREATE VIRTUAL TABLE f1 USING fts5(a, b);
|
||||
INSERT INTO f1 VALUES('a b', 'c d');
|
||||
INSERT INTO f1 VALUES('e f', 'a b');
|
||||
}
|
||||
|
||||
do_execsql_test 2.2.1 {
|
||||
SELECT rowid FROM f1('""');
|
||||
} {}
|
||||
|
||||
do_execsql_test 2.2.2 {
|
||||
SELECT rowid FROM f1('"" AND a');
|
||||
} {}
|
||||
|
||||
|
||||
do_execsql_test 2.2.3 {
|
||||
SELECT rowid FROM f1('"" a');
|
||||
} {1 2}
|
||||
|
||||
do_execsql_test 2.2.4 {
|
||||
SELECT rowid FROM f1('"" OR a');
|
||||
} {1 2}
|
||||
|
||||
do_execsql_test 2.3 {
|
||||
SELECT a, b FROM f1('NEAR("")');
|
||||
} {}
|
||||
|
||||
do_execsql_test 2.4 {
|
||||
SELECT a, b FROM f1('NEAR("", 5)');
|
||||
} {}
|
||||
|
||||
do_execsql_test 2.5 {
|
||||
SELECT a, b FROM f1('NEAR("" c, 5)');
|
||||
} {{a b} {c d}}
|
||||
|
||||
do_execsql_test 2.6 {
|
||||
SELECT a, b FROM f1('NEAR("" c d, 5)');
|
||||
} {{a b} {c d}}
|
||||
|
||||
do_execsql_test 2.7 {
|
||||
SELECT a, b FROM f1('NEAR(c d, 5)');
|
||||
} {{a b} {c d}}
|
||||
|
||||
do_execsql_test 2.8 {
|
||||
SELECT rowid FROM f1('NEAR("a" "b", 5)');
|
||||
} {1 2}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 3.2 {
|
||||
CREATE VIRTUAL TABLE f2 USING fts5(o, t, tokenize="ascii separators abc");
|
||||
SELECT * FROM f2('a+4');
|
||||
} {}
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_catchsql_test 4.1 {
|
||||
CREATE VIRTUAL TABLE f2 USING fts5(o, t);
|
||||
SELECT * FROM f2('(8 AND 9)`AND 10');
|
||||
} {1 {fts5: syntax error near "`"}}
|
||||
|
||||
finish_test
|
||||
|
||||
@ -467,5 +467,29 @@ do_execsql_test 12.1 {
|
||||
|
||||
} ;# foreach_detail_mode
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that a bad fts5() return is detected
|
||||
#
|
||||
reset_db
|
||||
proc xyz {} {}
|
||||
db func fts5 -argcount 0 xyz
|
||||
do_test 13.1 {
|
||||
list [catch { sqlite3_fts5_register_matchinfo db } msg] $msg
|
||||
} {1 SQLITE_ERROR}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that an invalid matchinfo() flag is detected
|
||||
#
|
||||
reset_db
|
||||
sqlite3_fts5_register_matchinfo db
|
||||
do_execsql_test 14.1 {
|
||||
CREATE VIRTUAL TABLE x1 USING fts5(z);
|
||||
INSERT INTO x1 VALUES('a b c a b c a b c');
|
||||
} {}
|
||||
|
||||
do_catchsql_test 14.2 {
|
||||
SELECT matchinfo(x1, 'd') FROM x1('a b c');
|
||||
} {1 {unrecognized matchinfo flag: d}}
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@ -45,7 +45,7 @@ proc do_merge1_test {testname nRowPerSeg} {
|
||||
WITH ii(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM ii WHERE i<$::nRowPerSeg)
|
||||
INSERT INTO x8 SELECT repeat('x y ', i % 16) FROM ii;
|
||||
|
||||
INSERT INTO x8(x8, rank) VALUES('automerge', 2);
|
||||
INSERT INTO x8(x8, rank) VALUES('usermerge', 2);
|
||||
}
|
||||
|
||||
for {set tn 1} {[lindex [fts5_level_segs x8] 0]>0} {incr tn} {
|
||||
@ -84,9 +84,9 @@ proc do_merge2_test {testname nRow} {
|
||||
execsql { INSERT INTO x8 VALUES( rnddoc(($i%16) + 5) ) }
|
||||
while {[not_merged x8]} {
|
||||
execsql {
|
||||
INSERT INTO x8(x8, rank) VALUES('automerge', 2);
|
||||
INSERT INTO x8(x8, rank) VALUES('usermerge', 2);
|
||||
INSERT INTO x8(x8, rank) VALUES('merge', 1);
|
||||
INSERT INTO x8(x8, rank) VALUES('automerge', 16);
|
||||
INSERT INTO x8(x8, rank) VALUES('usermerge', 16);
|
||||
INSERT INTO x8(x8) VALUES('integrity-check');
|
||||
}
|
||||
}
|
||||
@ -104,9 +104,9 @@ do_merge2_test 2.2 10
|
||||
do_merge2_test 2.3 20
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that an auto-merge will complete any merge that has already been
|
||||
# Test that a merge will complete any merge that has already been
|
||||
# started, even if the number of input segments is less than the current
|
||||
# value of the 'automerge' configuration parameter.
|
||||
# value of the 'usermerge' configuration parameter.
|
||||
#
|
||||
db func rnddoc fts5_rnddoc
|
||||
|
||||
@ -119,7 +119,7 @@ do_execsql_test 3.1 {
|
||||
}
|
||||
do_test 3.2 {
|
||||
execsql {
|
||||
INSERT INTO x8(x8, rank) VALUES('automerge', 4);
|
||||
INSERT INTO x8(x8, rank) VALUES('usermerge', 4);
|
||||
INSERT INTO x8(x8, rank) VALUES('merge', 1);
|
||||
}
|
||||
fts5_level_segs x8
|
||||
@ -127,14 +127,14 @@ do_test 3.2 {
|
||||
|
||||
do_test 3.3 {
|
||||
execsql {
|
||||
INSERT INTO x8(x8, rank) VALUES('automerge', 2);
|
||||
INSERT INTO x8(x8, rank) VALUES('usermerge', 2);
|
||||
INSERT INTO x8(x8, rank) VALUES('merge', 1);
|
||||
}
|
||||
fts5_level_segs x8
|
||||
} {2 1}
|
||||
|
||||
do_test 3.4 {
|
||||
execsql { INSERT INTO x8(x8, rank) VALUES('automerge', 4) }
|
||||
execsql { INSERT INTO x8(x8, rank) VALUES('usermerge', 4) }
|
||||
while {[not_merged x8]} {
|
||||
execsql { INSERT INTO x8(x8, rank) VALUES('merge', 1) }
|
||||
}
|
||||
@ -176,7 +176,7 @@ foreach {tn pgsz} {
|
||||
INSERT INTO x8 SELECT mydoc() FROM ii;
|
||||
WITH ii(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM ii WHERE i<100)
|
||||
INSERT INTO x8 SELECT mydoc() FROM ii;
|
||||
INSERT INTO x8(x8, rank) VALUES('automerge', 2);
|
||||
INSERT INTO x8(x8, rank) VALUES('usermerge', 2);
|
||||
}
|
||||
|
||||
set expect [mycount]
|
||||
@ -190,5 +190,55 @@ foreach {tn pgsz} {
|
||||
# db eval {SELECT fts5_decode(rowid, block) AS r FROM x8_data} { puts $r }
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that the 'merge' command does not modify the database if there is
|
||||
# no work to do.
|
||||
|
||||
do_execsql_test 5.1 {
|
||||
CREATE VIRTUAL TABLE x9 USING fts5(one, two);
|
||||
INSERT INTO x9(x9, rank) VALUES('pgsz', 32);
|
||||
INSERT INTO x9(x9, rank) VALUES('automerge', 2);
|
||||
INSERT INTO x9(x9, rank) VALUES('usermerge', 2);
|
||||
INSERT INTO x9 VALUES(rnddoc(100), rnddoc(100));
|
||||
INSERT INTO x9 VALUES(rnddoc(100), rnddoc(100));
|
||||
INSERT INTO x9 VALUES(rnddoc(100), rnddoc(100));
|
||||
INSERT INTO x9 VALUES(rnddoc(100), rnddoc(100));
|
||||
INSERT INTO x9 VALUES(rnddoc(100), rnddoc(100));
|
||||
INSERT INTO x9 VALUES(rnddoc(100), rnddoc(100));
|
||||
INSERT INTO x9 VALUES(rnddoc(100), rnddoc(100));
|
||||
INSERT INTO x9 VALUES(rnddoc(100), rnddoc(100));
|
||||
}
|
||||
|
||||
do_test 5.2 {
|
||||
while 1 {
|
||||
set nChange [db total_changes]
|
||||
execsql { INSERT INTO x9(x9, rank) VALUES('merge', 1); }
|
||||
set nChange [expr [db total_changes] - $nChange]
|
||||
#puts $nChange
|
||||
if {$nChange<2} break
|
||||
}
|
||||
} {}
|
||||
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# Test that running 'merge' on an empty database does not cause a
|
||||
# problem.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 6.0 {
|
||||
CREATE VIRTUAL TABLE g1 USING fts5(a, b);
|
||||
}
|
||||
do_execsql_test 6.1 {
|
||||
INSERT INTO g1(g1, rank) VALUES('merge', 10);
|
||||
}
|
||||
do_execsql_test 6.2 {
|
||||
INSERT INTO g1(g1, rank) VALUES('merge', -10);
|
||||
}
|
||||
do_execsql_test 6.3 {
|
||||
INSERT INTO g1(g1) VALUES('integrity-check');
|
||||
}
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@ -13,7 +13,8 @@
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
set testprefix fts5merge
|
||||
set testprefix fts5merge2
|
||||
return_if_no_fts5
|
||||
|
||||
proc dump_structure {} {
|
||||
db eval {SELECT fts5_decode(id, block) AS t FROM t1_data WHERE id=10} {
|
||||
@ -26,8 +27,6 @@ proc dump_structure {} {
|
||||
|
||||
foreach_detail_mode $testprefix {
|
||||
|
||||
if {[detail_is_none]==0} continue
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x, detail=%DETAIL%);
|
||||
INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
|
||||
|
||||
48
ext/fts5/test/fts5multiclient.test
Normal file
48
ext/fts5/test/fts5multiclient.test
Normal file
@ -0,0 +1,48 @@
|
||||
# 2016 March 17
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#*************************************************************************
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
source $testdir/lock_common.tcl
|
||||
|
||||
set testprefix fts5multiclient
|
||||
return_if_no_fts5
|
||||
|
||||
foreach_detail_mode $testprefix {
|
||||
|
||||
do_multiclient_test tn {
|
||||
|
||||
do_test 1.$tn.1 {
|
||||
sql1 { CREATE VIRTUAL TABLE t1 USING fts5(x, detail=%DETAIL%) }
|
||||
sql1 { INSERT INTO t1 VALUES('a b c') }
|
||||
sql2 { SELECT rowid FROM t1('b') }
|
||||
} {1}
|
||||
|
||||
do_test 1.$tn.2 {
|
||||
sql2 { INSERT INTO t1 VALUES('a b c') }
|
||||
sql1 { SELECT rowid FROM t1('b') }
|
||||
} {1 2}
|
||||
|
||||
do_test 1.$tn.3 {
|
||||
sql2 { INSERT INTO t1 VALUES('a b c') }
|
||||
sql1 { SELECT rowid FROM t1('b') }
|
||||
} {1 2 3}
|
||||
|
||||
do_test 1.$tn.4 {
|
||||
sql2 { INSERT INTO t1 VALUES('a b c') }
|
||||
sql1 { INSERT INTO t1 VALUES('a b c') }
|
||||
sql3 { INSERT INTO t1(t1) VALUES('integrity-check') }
|
||||
} {}
|
||||
|
||||
};# do_multiclient_test
|
||||
};# foreach_detail_mode
|
||||
finish_test
|
||||
|
||||
@ -20,6 +20,12 @@ ifcapable !fts5 {
|
||||
return
|
||||
}
|
||||
|
||||
#
|
||||
# 1.* - Warm body tests for index optimization using ('optimize')
|
||||
#
|
||||
# 2.* - Warm body tests for index optimization using ('merge', -1)
|
||||
#
|
||||
|
||||
proc rnddoc {nWord} {
|
||||
set vocab {a b c d e f g h i j k l m n o p q r s t u v w x y z}
|
||||
set nVocab [llength $vocab]
|
||||
@ -30,14 +36,12 @@ proc rnddoc {nWord} {
|
||||
return $ret
|
||||
}
|
||||
|
||||
|
||||
foreach {tn nStep} {
|
||||
1 2
|
||||
2 10
|
||||
3 50
|
||||
4 500
|
||||
} {
|
||||
if {$tn!=4} continue
|
||||
reset_db
|
||||
db func rnddoc rnddoc
|
||||
do_execsql_test 1.$tn.1 {
|
||||
@ -60,7 +64,46 @@ if {$tn!=4} continue
|
||||
do_execsql_test 1.$tn.5 {
|
||||
INSERT INTO t1(t1) VALUES('integrity-check');
|
||||
}
|
||||
|
||||
do_test 1.$tn.6 { fts5_segcount t1 } 1
|
||||
}
|
||||
|
||||
foreach {tn nStep} {
|
||||
1 2
|
||||
2 10
|
||||
3 50
|
||||
4 500
|
||||
} {
|
||||
reset_db
|
||||
db func rnddoc rnddoc
|
||||
do_execsql_test 1.$tn.1 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x, y);
|
||||
}
|
||||
do_test 2.$tn.2 {
|
||||
for {set i 0} {$i < $nStep} {incr i} {
|
||||
execsql { INSERT INTO t1 VALUES( rnddoc(5), rnddoc(5) ) }
|
||||
}
|
||||
} {}
|
||||
|
||||
do_execsql_test 2.$tn.3 {
|
||||
INSERT INTO t1(t1) VALUES('integrity-check');
|
||||
}
|
||||
|
||||
do_test 2.$tn.4 {
|
||||
execsql { INSERT INTO t1(t1, rank) VALUES('merge', -1) }
|
||||
while 1 {
|
||||
set c [db total_changes]
|
||||
execsql { INSERT INTO t1(t1, rank) VALUES('merge', 1) }
|
||||
set c [expr [db total_changes]-$c]
|
||||
if {$c<2} break
|
||||
}
|
||||
} {}
|
||||
|
||||
do_execsql_test 2.$tn.5 {
|
||||
INSERT INTO t1(t1) VALUES('integrity-check');
|
||||
}
|
||||
|
||||
do_test 2.$tn.6 { fts5_segcount t1 } 1
|
||||
}
|
||||
finish_test
|
||||
|
||||
|
||||
@ -91,7 +91,61 @@ do_test 2.7 {
|
||||
} {1 3 2}
|
||||
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# At one point there was a problem with queries such as:
|
||||
#
|
||||
# ... MATCH 'x OR y' ORDER BY rank;
|
||||
#
|
||||
# if there were zero occurrences of token 'y' in the dataset. The
|
||||
# following tests verify that that problem has been addressed.
|
||||
#
|
||||
foreach_detail_mode $::testprefix {
|
||||
do_execsql_test 3.1.0 {
|
||||
CREATE VIRTUAL TABLE y1 USING fts5(z, detail=%DETAIL%);
|
||||
INSERT INTO y1 VALUES('test xyz');
|
||||
INSERT INTO y1 VALUES('test test xyz test');
|
||||
INSERT INTO y1 VALUES('test test xyz');
|
||||
}
|
||||
|
||||
do_execsql_test 3.1.1 {
|
||||
SELECT rowid FROM y1('test OR tset');
|
||||
} {1 2 3}
|
||||
|
||||
do_execsql_test 3.1.2 {
|
||||
SELECT rowid FROM y1('test OR tset') ORDER BY bm25(y1)
|
||||
} {2 3 1}
|
||||
|
||||
do_execsql_test 3.1.3 {
|
||||
SELECT rowid FROM y1('test OR tset') ORDER BY +rank
|
||||
} {2 3 1}
|
||||
|
||||
do_execsql_test 3.1.4 {
|
||||
SELECT rowid FROM y1('test OR tset') ORDER BY rank
|
||||
} {2 3 1}
|
||||
|
||||
do_execsql_test 3.1.5 {
|
||||
SELECT rowid FROM y1('test OR xyz') ORDER BY rank
|
||||
} {3 2 1}
|
||||
|
||||
|
||||
do_execsql_test 3.2.1 {
|
||||
CREATE VIRTUAL TABLE z1 USING fts5(a, detail=%DETAIL%);
|
||||
INSERT INTO z1 VALUES('wrinkle in time');
|
||||
SELECT * FROM z1 WHERE z1 MATCH 'wrinkle in time OR a wrinkle in time';
|
||||
} {{wrinkle in time}}
|
||||
}
|
||||
|
||||
do_execsql_test 4.1 {
|
||||
DROP TABLE IF EXISTS VTest;
|
||||
CREATE virtual TABLE VTest USING FTS5(
|
||||
Title, AUthor, tokenize ='porter unicode61 remove_diacritics 1',
|
||||
columnsize='1', detail=full
|
||||
);
|
||||
INSERT INTO VTest (Title, Author) VALUES ('wrinkle in time', 'Bill Smith');
|
||||
|
||||
SELECT * FROM VTest WHERE
|
||||
VTest MATCH 'wrinkle in time OR a wrinkle in time' ORDER BY rank;
|
||||
} {{wrinkle in time} {Bill Smith}}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -265,7 +265,7 @@ do_test 11.0 {
|
||||
execsql "
|
||||
INSERT INTO t4 VALUES('a b c \x1A');
|
||||
INSERT INTO t4 VALUES('a b c d\x1A');
|
||||
INSERT INTO t4 VALUES('a b c \x1Ad');
|
||||
INSERT INTO t4 VALUES('a b c \x1Ag');
|
||||
INSERT INTO t4 VALUES('a b c d');
|
||||
"
|
||||
} {}
|
||||
@ -340,7 +340,7 @@ do_test 14.2 {
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
db func rnddoc fts5_rnddoc
|
||||
do_execsql_test 4.0 {
|
||||
do_execsql_test 14.3 {
|
||||
CREATE VIRTUAL TABLE x1 USING fts5(x);
|
||||
INSERT INTO x1(x1, rank) VALUES('pgsz', 32);
|
||||
|
||||
@ -348,9 +348,9 @@ do_execsql_test 4.0 {
|
||||
INSERT INTO x1 SELECT rnddoc(5) FROM ii;
|
||||
}
|
||||
|
||||
do_execsql_test 4.1 {
|
||||
do_execsql_test 14.4 {
|
||||
SELECT rowid, x, x1 FROM x1 WHERE x1 MATCH '*reads'
|
||||
} {0 {} 4}
|
||||
} {0 {} 3}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
@ -409,5 +409,63 @@ do_catchsql_test 19.2 {
|
||||
SELECT * FROM x1 WHERE x1 MATCH 'c0 AND (c1 AND (c2 AND (c3 AND (c4 AND (c5 AND (c6 AND (c7 AND (c8 AND (c9 AND (c10 AND (c11 AND (c12 AND (c13 AND (c14 AND (c15 AND (c16 AND (c17 AND (c18 AND (c19 AND (c20 AND (c21 AND (c22 AND (c23 AND (c24 AND (c25 AND (c26 AND (c27 AND (c28 AND (c29 AND (c30 AND (c31 AND (c32 AND (c33 AND (c34 AND (c35 AND (c36 AND (c37 AND (c38 AND (c39 AND (c40 AND (c41 AND (c42 AND (c43 AND (c44 AND (c45 AND (c46 AND (c47 AND (c48 AND (c49 AND (c50 AND (c51 AND (c52 AND (c53 AND (c54 AND (c55 AND (c56 AND (c57 AND (c58 AND (c59 AND (c60 AND (c61 AND (c62 AND (c63 AND (c64 AND (c65 AND (c66 AND (c67 AND (c68 AND (c69 AND (c70 AND (c71 AND (c72 AND (c73 AND (c74 AND (c75 AND (c76 AND (c77 AND (c78 AND (c79 AND (c80 AND (c81 AND (c82 AND (c83 AND (c84 AND (c85 AND (c86 AND (c87 AND (c88 AND (c89 AND (c90 AND (c91 AND (c92 AND (c93 AND (c94 AND (c95 AND (c96 AND (c97 AND (c98 AND (c99 AND (c100 AND (c101 AND (c102 AND (c103 AND (c104 AND (c105 AND (c106 AND (c107 AND (c108 AND (c109 AND (c110 AND (c111 AND (c112 AND (c113 AND (c114 AND (c115 AND (c116 AND (c117 AND (c118 AND (c119 AND (c120 AND (c121 AND (c122 AND (c123 AND (c124 AND (c125 AND (c126 AND (c127 AND (c128 AND (c129 AND (c130 AND (c131 AND (c132 AND (c133 AND (c134 AND (c135 AND (c136 AND (c137 AND (c138 AND (c139 AND (c140 AND (c141 AND (c142 AND (c143 AND (c144 AND (c145 AND (c146 AND (c147 AND (c148 AND (c149 AND (c150 AND (c151 AND (c152 AND (c153 AND (c154 AND (c155 AND (c156 AND (c157 AND (c158 AND (c159 AND (c160 AND (c161 AND (c162 AND (c163 AND (c164 AND (c165 AND (c166 AND (c167 AND (c168 AND (c169 AND (c170 AND (c171 AND (c172 AND (c173 AND (c174 AND (c175 AND (c176 AND (c177 AND (c178 AND (c179 AND (c180 AND (c181 AND (c182 AND (c183 AND (c184 AND (c185 AND (c186 AND (c187 AND (c188 AND (c189 AND (c190 AND (c191 AND (c192 AND (c193 AND (c194 AND (c195 AND (c196 AND (c197 AND (c198 AND (c199 AND c200)))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))';
|
||||
} {1 {fts5: parser stack overflow}}
|
||||
|
||||
finish_test
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
breakpoint
|
||||
do_execsql_test 20.0 {
|
||||
CREATE VIRTUAL TABLE x1 USING fts5(x);
|
||||
INSERT INTO x1(x1, rank) VALUES('pgsz', 32);
|
||||
INSERT INTO x1(rowid, x) VALUES(11111, 'onetwothree');
|
||||
}
|
||||
do_test 20.1 {
|
||||
for {set i 1} {$i <= 200} {incr i} {
|
||||
execsql { INSERT INTO x1(rowid, x) VALUES($i, 'one two three'); }
|
||||
}
|
||||
execsql { INSERT INTO x1(x1) VALUES('optimize'); }
|
||||
execsql { DELETE FROM x1 WHERE rowid = 4; }
|
||||
} {}
|
||||
do_execsql_test 20.2 {
|
||||
INSERT INTO x1(x1) VALUES('optimize');
|
||||
INSERT INTO x1(x1) VALUES('integrity-check');
|
||||
} {}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 20.0 {
|
||||
CREATE VIRTUAL TABLE x1 USING fts5(x);
|
||||
INSERT INTO x1(x1, rank) VALUES('pgsz', 32);
|
||||
INSERT INTO x1(rowid, x) VALUES(11111, 'onetwothree');
|
||||
}
|
||||
do_test 20.1 {
|
||||
for {set i 1} {$i <= 200} {incr i} {
|
||||
execsql { INSERT INTO x1(rowid, x) VALUES($i, 'one two three'); }
|
||||
}
|
||||
execsql { INSERT INTO x1(x1) VALUES('optimize'); }
|
||||
execsql { DELETE FROM x1 WHERE rowid = 4; }
|
||||
} {}
|
||||
do_execsql_test 20.2 {
|
||||
INSERT INTO x1(x1) VALUES('optimize');
|
||||
INSERT INTO x1(x1) VALUES('integrity-check');
|
||||
} {}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
set doc "a b [string repeat x 100000]"
|
||||
do_execsql_test 21.0 {
|
||||
CREATE VIRTUAL TABLE x1 USING fts5(x);
|
||||
INSERT INTO x1(rowid, x) VALUES(11111, $doc);
|
||||
INSERT INTO x1(rowid, x) VALUES(11112, $doc);
|
||||
}
|
||||
do_execsql_test 21.1 {
|
||||
INSERT INTO x1(x1) VALUES('integrity-check');
|
||||
}
|
||||
do_execsql_test 21.2 {
|
||||
SELECT rowid FROM x1($doc);
|
||||
} {11111 11112}
|
||||
do_execsql_test 21.3 {
|
||||
DELETE FROM x1 WHERE rowid=11111;
|
||||
INSERT INTO x1(x1) VALUES('integrity-check');
|
||||
SELECT rowid FROM x1($doc);
|
||||
} {11112}
|
||||
|
||||
finish_test
|
||||
|
||||
@ -80,6 +80,40 @@ do_execsql_test 3.0 {
|
||||
SELECT * FROM x3('x OR y OR z');
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that a crash occuring when the second or subsequent tokens in a
|
||||
# phrase matched zero rows has been fixed.
|
||||
#
|
||||
do_execsql_test 4.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x);
|
||||
INSERT INTO t1 VALUES('ab');
|
||||
INSERT INTO t1 VALUES('cd');
|
||||
INSERT INTO t1 VALUES('ab cd');
|
||||
INSERT INTO t1 VALUES('ab cdXXX');
|
||||
INSERT INTO t1 VALUES('abXXX cd');
|
||||
}
|
||||
do_execsql_test 4.1 {
|
||||
SELECT * FROM t1('"ab cd" OR "ab cd" *');
|
||||
} {{ab cd} {ab cdXXX}}
|
||||
do_execsql_test 4.2 {
|
||||
SELECT * FROM t1('"xy zz" OR "ab cd" *');
|
||||
} {{ab cd} {ab cdXXX}}
|
||||
do_execsql_test 4.3 {
|
||||
SELECT * FROM t1('"xy zz" OR "xy zz" *');
|
||||
}
|
||||
do_execsql_test 4.4 {
|
||||
SELECT * FROM t1('"ab cd" OR "xy zz" *');
|
||||
} {{ab cd}}
|
||||
do_execsql_test 4.5 {
|
||||
CREATE VIRTUAL TABLE t2 USING fts5(x);
|
||||
INSERT INTO t2 VALUES('ab');
|
||||
INSERT INTO t2 VALUES('cd');
|
||||
INSERT INTO t2 VALUES('ef');
|
||||
}
|
||||
do_execsql_test 4.6 {
|
||||
SELECT * FROM t2('ab + xyz');
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@ -160,12 +160,12 @@ foreach {tn query snippet} {
|
||||
the maximum x value.
|
||||
}
|
||||
4 "rollback" {
|
||||
...[ROLLBACK]. Instead, the pending statement
|
||||
will return SQLITE_ABORT upon next access after the [ROLLBACK].
|
||||
Pending statements no longer block [ROLLBACK]. Instead, the pending
|
||||
statement will return SQLITE_ABORT upon...
|
||||
}
|
||||
5 "rOllback" {
|
||||
...[ROLLBACK]. Instead, the pending statement
|
||||
will return SQLITE_ABORT upon next access after the [ROLLBACK].
|
||||
Pending statements no longer block [ROLLBACK]. Instead, the pending
|
||||
statement will return SQLITE_ABORT upon...
|
||||
}
|
||||
6 "lang*" {
|
||||
Added support for the FTS4 [languageid] option.
|
||||
|
||||
@ -442,8 +442,44 @@ if {[detail_is_none]} {
|
||||
}
|
||||
|
||||
sqlite3_fts5_may_be_corrupt 0
|
||||
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that both "ORDER BY term" and "ORDER BY term DESC" work.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 9.1 {
|
||||
CREATE VIRTUAL TABLE x1 USING fts5(x);
|
||||
INSERT INTO x1 VALUES('def ABC ghi');
|
||||
INSERT INTO x1 VALUES('DEF abc GHI');
|
||||
}
|
||||
|
||||
do_execsql_test 9.2 {
|
||||
CREATE VIRTUAL TABLE rrr USING fts5vocab(x1, row);
|
||||
SELECT * FROM rrr
|
||||
} {
|
||||
abc 2 2 def 2 2 ghi 2 2
|
||||
}
|
||||
do_execsql_test 9.3 {
|
||||
SELECT * FROM rrr ORDER BY term ASC
|
||||
} {
|
||||
abc 2 2 def 2 2 ghi 2 2
|
||||
}
|
||||
do_execsql_test 9.4 {
|
||||
SELECT * FROM rrr ORDER BY term DESC
|
||||
} {
|
||||
ghi 2 2 def 2 2 abc 2 2
|
||||
}
|
||||
do_test 9.5 {
|
||||
set e2 [db eval { EXPLAIN SELECT * FROM rrr ORDER BY term ASC }]
|
||||
expr [lsearch $e2 SorterSort]<0
|
||||
} 1
|
||||
do_test 9.6 {
|
||||
set e2 [db eval { EXPLAIN SELECT * FROM rrr ORDER BY term DESC }]
|
||||
expr [lsearch $e2 SorterSort]<0
|
||||
} 0
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@ -17,6 +17,7 @@ proc process_cmdline {} {
|
||||
{detail "full" "Fts5 detail mode to use"}
|
||||
{repeat 1 "Load each file this many times"}
|
||||
{prefix "" "Fts prefix= option"}
|
||||
{trans 1 "True to use a transaction"}
|
||||
database
|
||||
file...
|
||||
} {
|
||||
@ -214,7 +215,7 @@ foreach c [lrange $cols 1 end] {
|
||||
}
|
||||
append sql ")"
|
||||
|
||||
db eval BEGIN
|
||||
if {$A(trans)} { db eval BEGIN }
|
||||
while {$i < $N} {
|
||||
foreach c $cols s $A(colsize) {
|
||||
set R($c) [lrange $tokens $i [expr $i+$s-1]]
|
||||
@ -222,7 +223,7 @@ db eval BEGIN
|
||||
}
|
||||
db eval $sql
|
||||
}
|
||||
db eval COMMIT
|
||||
if {$A(trans)} { db eval COMMIT }
|
||||
|
||||
|
||||
|
||||
|
||||
142
ext/icu/icu.c
142
ext/icu/icu.c
@ -60,6 +60,38 @@ static void xFree(void *p){
|
||||
sqlite3_free(p);
|
||||
}
|
||||
|
||||
/*
|
||||
** This lookup table is used to help decode the first byte of
|
||||
** a multi-byte UTF8 character. It is copied here from SQLite source
|
||||
** code file utf8.c.
|
||||
*/
|
||||
static const unsigned char icuUtf8Trans1[] = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00,
|
||||
};
|
||||
|
||||
#define SQLITE_ICU_READ_UTF8(zIn, c) \
|
||||
c = *(zIn++); \
|
||||
if( c>=0xc0 ){ \
|
||||
c = icuUtf8Trans1[c-0xc0]; \
|
||||
while( (*zIn & 0xc0)==0x80 ){ \
|
||||
c = (c<<6) + (0x3f & *(zIn++)); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define SQLITE_ICU_SKIP_UTF8(zIn) \
|
||||
assert( *zIn ); \
|
||||
if( *(zIn++)>=0xc0 ){ \
|
||||
while( (*zIn & 0xc0)==0x80 ){zIn++;} \
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Compare two UTF-8 strings for equality where the first string is
|
||||
** a "LIKE" expression. Return true (1) if they are the same and
|
||||
@ -73,16 +105,14 @@ static int icuLikeCompare(
|
||||
static const int MATCH_ONE = (UChar32)'_';
|
||||
static const int MATCH_ALL = (UChar32)'%';
|
||||
|
||||
int iPattern = 0; /* Current byte index in zPattern */
|
||||
int iString = 0; /* Current byte index in zString */
|
||||
|
||||
int prevEscape = 0; /* True if the previous character was uEsc */
|
||||
|
||||
while( zPattern[iPattern]!=0 ){
|
||||
while( 1 ){
|
||||
|
||||
/* Read (and consume) the next character from the input pattern. */
|
||||
UChar32 uPattern;
|
||||
U8_NEXT_UNSAFE(zPattern, iPattern, uPattern);
|
||||
SQLITE_ICU_READ_UTF8(zPattern, uPattern);
|
||||
if( uPattern==0 ) break;
|
||||
|
||||
/* There are now 4 possibilities:
|
||||
**
|
||||
@ -99,28 +129,28 @@ static int icuLikeCompare(
|
||||
** MATCH_ALL. For each MATCH_ONE, skip one character in the
|
||||
** test string.
|
||||
*/
|
||||
while( (c=zPattern[iPattern]) == MATCH_ALL || c == MATCH_ONE ){
|
||||
while( (c=*zPattern) == MATCH_ALL || c == MATCH_ONE ){
|
||||
if( c==MATCH_ONE ){
|
||||
if( zString[iString]==0 ) return 0;
|
||||
U8_FWD_1_UNSAFE(zString, iString);
|
||||
if( *zString==0 ) return 0;
|
||||
SQLITE_ICU_SKIP_UTF8(zString);
|
||||
}
|
||||
iPattern++;
|
||||
zPattern++;
|
||||
}
|
||||
|
||||
if( zPattern[iPattern]==0 ) return 1;
|
||||
if( *zPattern==0 ) return 1;
|
||||
|
||||
while( zString[iString] ){
|
||||
if( icuLikeCompare(&zPattern[iPattern], &zString[iString], uEsc) ){
|
||||
while( *zString ){
|
||||
if( icuLikeCompare(zPattern, zString, uEsc) ){
|
||||
return 1;
|
||||
}
|
||||
U8_FWD_1_UNSAFE(zString, iString);
|
||||
SQLITE_ICU_SKIP_UTF8(zString);
|
||||
}
|
||||
return 0;
|
||||
|
||||
}else if( !prevEscape && uPattern==MATCH_ONE ){
|
||||
/* Case 2. */
|
||||
if( zString[iString]==0 ) return 0;
|
||||
U8_FWD_1_UNSAFE(zString, iString);
|
||||
if( *zString==0 ) return 0;
|
||||
SQLITE_ICU_SKIP_UTF8(zString);
|
||||
|
||||
}else if( !prevEscape && uPattern==uEsc){
|
||||
/* Case 3. */
|
||||
@ -129,7 +159,7 @@ static int icuLikeCompare(
|
||||
}else{
|
||||
/* Case 4. */
|
||||
UChar32 uString;
|
||||
U8_NEXT_UNSAFE(zString, iString, uString);
|
||||
SQLITE_ICU_READ_UTF8(zString, uString);
|
||||
uString = u_foldCase(uString, U_FOLD_CASE_DEFAULT);
|
||||
uPattern = u_foldCase(uPattern, U_FOLD_CASE_DEFAULT);
|
||||
if( uString!=uPattern ){
|
||||
@ -139,7 +169,7 @@ static int icuLikeCompare(
|
||||
}
|
||||
}
|
||||
|
||||
return zString[iString]==0;
|
||||
return *zString==0;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -319,20 +349,22 @@ static void icuRegexpFunc(sqlite3_context *p, int nArg, sqlite3_value **apArg){
|
||||
** of upper() or lower().
|
||||
**
|
||||
** lower('I', 'en_us') -> 'i'
|
||||
** lower('I', 'tr_tr') -> 'ı' (small dotless i)
|
||||
** lower('I', 'tr_tr') -> '\u131' (small dotless i)
|
||||
**
|
||||
** http://www.icu-project.org/userguide/posix.html#case_mappings
|
||||
*/
|
||||
static void icuCaseFunc16(sqlite3_context *p, int nArg, sqlite3_value **apArg){
|
||||
const UChar *zInput;
|
||||
UChar *zOutput;
|
||||
int nInput;
|
||||
int nOutput;
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
const UChar *zInput; /* Pointer to input string */
|
||||
UChar *zOutput = 0; /* Pointer to output buffer */
|
||||
int nInput; /* Size of utf-16 input string in bytes */
|
||||
int nOut; /* Size of output buffer in bytes */
|
||||
int cnt;
|
||||
int bToUpper; /* True for toupper(), false for tolower() */
|
||||
UErrorCode status;
|
||||
const char *zLocale = 0;
|
||||
|
||||
assert(nArg==1 || nArg==2);
|
||||
bToUpper = (sqlite3_user_data(p)!=0);
|
||||
if( nArg==2 ){
|
||||
zLocale = (const char *)sqlite3_value_text(apArg[1]);
|
||||
}
|
||||
@ -341,26 +373,38 @@ static void icuCaseFunc16(sqlite3_context *p, int nArg, sqlite3_value **apArg){
|
||||
if( !zInput ){
|
||||
return;
|
||||
}
|
||||
nInput = sqlite3_value_bytes16(apArg[0]);
|
||||
|
||||
nOutput = nInput * 2 + 2;
|
||||
zOutput = sqlite3_malloc(nOutput);
|
||||
if( !zOutput ){
|
||||
nOut = nInput = sqlite3_value_bytes16(apArg[0]);
|
||||
if( nOut==0 ){
|
||||
sqlite3_result_text16(p, "", 0, SQLITE_STATIC);
|
||||
return;
|
||||
}
|
||||
|
||||
if( sqlite3_user_data(p) ){
|
||||
u_strToUpper(zOutput, nOutput/2, zInput, nInput/2, zLocale, &status);
|
||||
}else{
|
||||
u_strToLower(zOutput, nOutput/2, zInput, nInput/2, zLocale, &status);
|
||||
}
|
||||
for(cnt=0; cnt<2; cnt++){
|
||||
UChar *zNew = sqlite3_realloc(zOutput, nOut);
|
||||
if( zNew==0 ){
|
||||
sqlite3_free(zOutput);
|
||||
sqlite3_result_error_nomem(p);
|
||||
return;
|
||||
}
|
||||
zOutput = zNew;
|
||||
status = U_ZERO_ERROR;
|
||||
if( bToUpper ){
|
||||
nOut = 2*u_strToUpper(zOutput,nOut/2,zInput,nInput/2,zLocale,&status);
|
||||
}else{
|
||||
nOut = 2*u_strToLower(zOutput,nOut/2,zInput,nInput/2,zLocale,&status);
|
||||
}
|
||||
|
||||
if( !U_SUCCESS(status) ){
|
||||
icuFunctionError(p, "u_strToLower()/u_strToUpper", status);
|
||||
if( U_SUCCESS(status) ){
|
||||
sqlite3_result_text16(p, zOutput, nOut, xFree);
|
||||
}else if( status==U_BUFFER_OVERFLOW_ERROR ){
|
||||
assert( cnt==0 );
|
||||
continue;
|
||||
}else{
|
||||
icuFunctionError(p, bToUpper ? "u_strToUpper" : "u_strToLower", status);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sqlite3_result_text16(p, zOutput, -1, xFree);
|
||||
assert( 0 ); /* Unreachable */
|
||||
}
|
||||
|
||||
/*
|
||||
@ -456,20 +500,20 @@ int sqlite3IcuInit(sqlite3 *db){
|
||||
void *pContext; /* sqlite3_user_data() context */
|
||||
void (*xFunc)(sqlite3_context*,int,sqlite3_value**);
|
||||
} scalars[] = {
|
||||
{"regexp", 2, SQLITE_ANY, 0, icuRegexpFunc},
|
||||
{"regexp", 2, SQLITE_ANY|SQLITE_DETERMINISTIC, 0, icuRegexpFunc},
|
||||
|
||||
{"lower", 1, SQLITE_UTF16, 0, icuCaseFunc16},
|
||||
{"lower", 2, SQLITE_UTF16, 0, icuCaseFunc16},
|
||||
{"upper", 1, SQLITE_UTF16, (void*)1, icuCaseFunc16},
|
||||
{"upper", 2, SQLITE_UTF16, (void*)1, icuCaseFunc16},
|
||||
{"lower", 1, SQLITE_UTF16|SQLITE_DETERMINISTIC, 0, icuCaseFunc16},
|
||||
{"lower", 2, SQLITE_UTF16|SQLITE_DETERMINISTIC, 0, icuCaseFunc16},
|
||||
{"upper", 1, SQLITE_UTF16|SQLITE_DETERMINISTIC, (void*)1, icuCaseFunc16},
|
||||
{"upper", 2, SQLITE_UTF16|SQLITE_DETERMINISTIC, (void*)1, icuCaseFunc16},
|
||||
|
||||
{"lower", 1, SQLITE_UTF8, 0, icuCaseFunc16},
|
||||
{"lower", 2, SQLITE_UTF8, 0, icuCaseFunc16},
|
||||
{"upper", 1, SQLITE_UTF8, (void*)1, icuCaseFunc16},
|
||||
{"upper", 2, SQLITE_UTF8, (void*)1, icuCaseFunc16},
|
||||
{"lower", 1, SQLITE_UTF8|SQLITE_DETERMINISTIC, 0, icuCaseFunc16},
|
||||
{"lower", 2, SQLITE_UTF8|SQLITE_DETERMINISTIC, 0, icuCaseFunc16},
|
||||
{"upper", 1, SQLITE_UTF8|SQLITE_DETERMINISTIC, (void*)1, icuCaseFunc16},
|
||||
{"upper", 2, SQLITE_UTF8|SQLITE_DETERMINISTIC, (void*)1, icuCaseFunc16},
|
||||
|
||||
{"like", 2, SQLITE_UTF8, 0, icuLikeFunc},
|
||||
{"like", 3, SQLITE_UTF8, 0, icuLikeFunc},
|
||||
{"like", 2, SQLITE_UTF8|SQLITE_DETERMINISTIC, 0, icuLikeFunc},
|
||||
{"like", 3, SQLITE_UTF8|SQLITE_DETERMINISTIC, 0, icuLikeFunc},
|
||||
|
||||
{"icu_load_collation", 2, SQLITE_UTF8, (void*)db, icuLoadCollation},
|
||||
};
|
||||
|
||||
@ -625,10 +625,10 @@ static int amatchLoadOneRule(
|
||||
}else{
|
||||
memset(pRule, 0, sizeof(*pRule));
|
||||
pRule->zFrom = &pRule->zTo[nTo+1];
|
||||
pRule->nFrom = nFrom;
|
||||
pRule->nFrom = (amatch_len)nFrom;
|
||||
memcpy(pRule->zFrom, zFrom, nFrom+1);
|
||||
memcpy(pRule->zTo, zTo, nTo+1);
|
||||
pRule->nTo = nTo;
|
||||
pRule->nTo = (amatch_len)nTo;
|
||||
pRule->rCost = rCost;
|
||||
pRule->iLang = (int)iLang;
|
||||
}
|
||||
@ -1081,7 +1081,7 @@ static void amatchAddWord(
|
||||
pWord->rCost = rCost;
|
||||
pWord->iSeq = pCur->nWord++;
|
||||
amatchWriteCost(pWord);
|
||||
pWord->nMatch = nMatch;
|
||||
pWord->nMatch = (short)nMatch;
|
||||
pWord->pNext = pCur->pAllWords;
|
||||
pCur->pAllWords = pWord;
|
||||
pWord->sCost.zKey = pWord->zCost;
|
||||
@ -1162,7 +1162,7 @@ static int amatchNext(sqlite3_vtab_cursor *cur){
|
||||
#endif
|
||||
nWord = (int)strlen(pWord->zWord+2);
|
||||
if( nWord+20>nBuf ){
|
||||
nBuf = nWord+100;
|
||||
nBuf = (char)(nWord+100);
|
||||
zBuf = sqlite3_realloc(zBuf, nBuf);
|
||||
if( zBuf==0 ) return SQLITE_NOMEM;
|
||||
}
|
||||
|
||||
364
ext/misc/carray.c
Normal file
364
ext/misc/carray.c
Normal file
@ -0,0 +1,364 @@
|
||||
/*
|
||||
** 2016-06-29
|
||||
**
|
||||
** 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 demonstrates how to create a table-valued-function that
|
||||
** returns the values in a C-language array.
|
||||
** Examples:
|
||||
**
|
||||
** SELECT * FROM carray($ptr,5)
|
||||
**
|
||||
** The query above returns 5 integers contained in a C-language array
|
||||
** at the address $ptr. $ptr is a pointer to the array of integers that
|
||||
** has been cast to an integer.
|
||||
**
|
||||
** There is an optional third parameter to determine the datatype of
|
||||
** the C-language array. Allowed values of the third parameter are
|
||||
** 'int32', 'int64', 'double', 'char*'. Example:
|
||||
**
|
||||
** SELECT * FROM carray($ptr,10,'char*');
|
||||
**
|
||||
** HOW IT WORKS
|
||||
**
|
||||
** The carray "function" is really a virtual table with the
|
||||
** following schema:
|
||||
**
|
||||
** CREATE TABLE carray(
|
||||
** value,
|
||||
** pointer HIDDEN,
|
||||
** count HIDDEN,
|
||||
** ctype TEXT HIDDEN
|
||||
** );
|
||||
**
|
||||
** If the hidden columns "pointer" and "count" are unconstrained, then
|
||||
** the virtual table has no rows. Otherwise, the virtual table interprets
|
||||
** the integer value of "pointer" as a pointer to the array and "count"
|
||||
** as the number of elements in the array. The virtual table steps through
|
||||
** the array, element by element.
|
||||
*/
|
||||
#include "sqlite3ext.h"
|
||||
SQLITE_EXTENSION_INIT1
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
|
||||
/*
|
||||
** Allowed datatypes
|
||||
*/
|
||||
#define CARRAY_INT32 0
|
||||
#define CARRAY_INT64 1
|
||||
#define CARRAY_DOUBLE 2
|
||||
#define CARRAY_TEXT 3
|
||||
|
||||
/*
|
||||
** Names of types
|
||||
*/
|
||||
static const char *azType[] = { "int32", "int64", "double", "char*" };
|
||||
|
||||
|
||||
/* carray_cursor is a subclass of sqlite3_vtab_cursor which will
|
||||
** serve as the underlying representation of a cursor that scans
|
||||
** over rows of the result
|
||||
*/
|
||||
typedef struct carray_cursor carray_cursor;
|
||||
struct carray_cursor {
|
||||
sqlite3_vtab_cursor base; /* Base class - must be first */
|
||||
sqlite3_int64 iRowid; /* The rowid */
|
||||
sqlite3_int64 iPtr; /* Pointer to array of values */
|
||||
sqlite3_int64 iCnt; /* Number of integers in the array */
|
||||
unsigned char eType; /* One of the CARRAY_type values */
|
||||
};
|
||||
|
||||
/*
|
||||
** The carrayConnect() method is invoked to create a new
|
||||
** carray_vtab that describes the carray virtual table.
|
||||
**
|
||||
** Think of this routine as the constructor for carray_vtab objects.
|
||||
**
|
||||
** All this routine needs to do is:
|
||||
**
|
||||
** (1) Allocate the carray_vtab object and initialize all fields.
|
||||
**
|
||||
** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the
|
||||
** result set of queries against carray will look like.
|
||||
*/
|
||||
static int carrayConnect(
|
||||
sqlite3 *db,
|
||||
void *pAux,
|
||||
int argc, const char *const*argv,
|
||||
sqlite3_vtab **ppVtab,
|
||||
char **pzErr
|
||||
){
|
||||
sqlite3_vtab *pNew;
|
||||
int rc;
|
||||
|
||||
/* Column numbers */
|
||||
#define CARRAY_COLUMN_VALUE 0
|
||||
#define CARRAY_COLUMN_POINTER 1
|
||||
#define CARRAY_COLUMN_COUNT 2
|
||||
#define CARRAY_COLUMN_CTYPE 3
|
||||
|
||||
rc = sqlite3_declare_vtab(db,
|
||||
"CREATE TABLE x(value,pointer hidden,count hidden,ctype hidden)");
|
||||
if( rc==SQLITE_OK ){
|
||||
pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) );
|
||||
if( pNew==0 ) return SQLITE_NOMEM;
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** This method is the destructor for carray_cursor objects.
|
||||
*/
|
||||
static int carrayDisconnect(sqlite3_vtab *pVtab){
|
||||
sqlite3_free(pVtab);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Constructor for a new carray_cursor object.
|
||||
*/
|
||||
static int carrayOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
|
||||
carray_cursor *pCur;
|
||||
pCur = sqlite3_malloc( sizeof(*pCur) );
|
||||
if( pCur==0 ) return SQLITE_NOMEM;
|
||||
memset(pCur, 0, sizeof(*pCur));
|
||||
*ppCursor = &pCur->base;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Destructor for a carray_cursor.
|
||||
*/
|
||||
static int carrayClose(sqlite3_vtab_cursor *cur){
|
||||
sqlite3_free(cur);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Advance a carray_cursor to its next row of output.
|
||||
*/
|
||||
static int carrayNext(sqlite3_vtab_cursor *cur){
|
||||
carray_cursor *pCur = (carray_cursor*)cur;
|
||||
pCur->iRowid++;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return values of columns for the row at which the carray_cursor
|
||||
** is currently pointing.
|
||||
*/
|
||||
static int carrayColumn(
|
||||
sqlite3_vtab_cursor *cur, /* The cursor */
|
||||
sqlite3_context *ctx, /* First argument to sqlite3_result_...() */
|
||||
int i /* Which column to return */
|
||||
){
|
||||
carray_cursor *pCur = (carray_cursor*)cur;
|
||||
sqlite3_int64 x = 0;
|
||||
switch( i ){
|
||||
case CARRAY_COLUMN_POINTER: x = pCur->iPtr; break;
|
||||
case CARRAY_COLUMN_COUNT: x = pCur->iCnt; break;
|
||||
case CARRAY_COLUMN_CTYPE: {
|
||||
sqlite3_result_text(ctx, azType[pCur->eType], -1, SQLITE_STATIC);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
default: {
|
||||
switch( pCur->eType ){
|
||||
case CARRAY_INT32: {
|
||||
int *p = (int*)pCur->iPtr;
|
||||
sqlite3_result_int(ctx, p[pCur->iRowid-1]);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
case CARRAY_INT64: {
|
||||
sqlite3_int64 *p = (sqlite3_int64*)pCur->iPtr;
|
||||
sqlite3_result_int64(ctx, p[pCur->iRowid-1]);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
case CARRAY_DOUBLE: {
|
||||
double *p = (double*)pCur->iPtr;
|
||||
sqlite3_result_double(ctx, p[pCur->iRowid-1]);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
case CARRAY_TEXT: {
|
||||
const char **p = (const char**)pCur->iPtr;
|
||||
sqlite3_result_text(ctx, p[pCur->iRowid-1], -1, SQLITE_TRANSIENT);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sqlite3_result_int64(ctx, x);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the rowid for the current row. In this implementation, the
|
||||
** rowid is the same as the output value.
|
||||
*/
|
||||
static int carrayRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
|
||||
carray_cursor *pCur = (carray_cursor*)cur;
|
||||
*pRowid = pCur->iRowid;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return TRUE if the cursor has been moved off of the last
|
||||
** row of output.
|
||||
*/
|
||||
static int carrayEof(sqlite3_vtab_cursor *cur){
|
||||
carray_cursor *pCur = (carray_cursor*)cur;
|
||||
return pCur->iRowid>pCur->iCnt;
|
||||
}
|
||||
|
||||
/*
|
||||
** This method is called to "rewind" the carray_cursor object back
|
||||
** to the first row of output.
|
||||
*/
|
||||
static int carrayFilter(
|
||||
sqlite3_vtab_cursor *pVtabCursor,
|
||||
int idxNum, const char *idxStr,
|
||||
int argc, sqlite3_value **argv
|
||||
){
|
||||
carray_cursor *pCur = (carray_cursor *)pVtabCursor;
|
||||
if( idxNum ){
|
||||
pCur->iPtr = sqlite3_value_int64(argv[0]);
|
||||
pCur->iCnt = sqlite3_value_int64(argv[1]);
|
||||
if( idxNum<3 ){
|
||||
pCur->eType = CARRAY_INT32;
|
||||
}else{
|
||||
unsigned char i;
|
||||
const char *zType = (const char*)sqlite3_value_text(argv[2]);
|
||||
for(i=0; i<sizeof(azType)/sizeof(azType[0]); i++){
|
||||
if( sqlite3_stricmp(zType, azType[i])==0 ) break;
|
||||
}
|
||||
if( i>=sizeof(azType)/sizeof(azType[0]) ){
|
||||
pVtabCursor->pVtab->zErrMsg = sqlite3_mprintf(
|
||||
"unknown datatype: %Q", zType);
|
||||
return SQLITE_ERROR;
|
||||
}else{
|
||||
pCur->eType = i;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
pCur->iPtr = 0;
|
||||
pCur->iCnt = 0;
|
||||
}
|
||||
pCur->iRowid = 1;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** SQLite will invoke this method one or more times while planning a query
|
||||
** that uses the carray virtual table. This routine needs to create
|
||||
** a query plan for each invocation and compute an estimated cost for that
|
||||
** plan.
|
||||
**
|
||||
** In this implementation idxNum is used to represent the
|
||||
** query plan. idxStr is unused.
|
||||
**
|
||||
** idxNum is 2 if the pointer= and count= constraints exist,
|
||||
** 3 if the ctype= constraint also exists, and is 0 otherwise.
|
||||
** If idxNum is 0, then carray becomes an empty table.
|
||||
*/
|
||||
static int carrayBestIndex(
|
||||
sqlite3_vtab *tab,
|
||||
sqlite3_index_info *pIdxInfo
|
||||
){
|
||||
int i; /* Loop over constraints */
|
||||
int ptrIdx = -1; /* Index of the pointer= constraint, or -1 if none */
|
||||
int cntIdx = -1; /* Index of the count= constraint, or -1 if none */
|
||||
int ctypeIdx = -1; /* Index of the ctype= constraint, or -1 if none */
|
||||
|
||||
const struct sqlite3_index_constraint *pConstraint;
|
||||
pConstraint = pIdxInfo->aConstraint;
|
||||
for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){
|
||||
if( pConstraint->usable==0 ) continue;
|
||||
if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue;
|
||||
switch( pConstraint->iColumn ){
|
||||
case CARRAY_COLUMN_POINTER:
|
||||
ptrIdx = i;
|
||||
break;
|
||||
case CARRAY_COLUMN_COUNT:
|
||||
cntIdx = i;
|
||||
break;
|
||||
case CARRAY_COLUMN_CTYPE:
|
||||
ctypeIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( ptrIdx>=0 && cntIdx>=0 ){
|
||||
pIdxInfo->aConstraintUsage[ptrIdx].argvIndex = 1;
|
||||
pIdxInfo->aConstraintUsage[ptrIdx].omit = 1;
|
||||
pIdxInfo->aConstraintUsage[cntIdx].argvIndex = 2;
|
||||
pIdxInfo->aConstraintUsage[cntIdx].omit = 1;
|
||||
pIdxInfo->estimatedCost = (double)1;
|
||||
pIdxInfo->estimatedRows = 100;
|
||||
pIdxInfo->idxNum = 2;
|
||||
if( ctypeIdx>=0 ){
|
||||
pIdxInfo->aConstraintUsage[ctypeIdx].argvIndex = 3;
|
||||
pIdxInfo->aConstraintUsage[ctypeIdx].omit = 1;
|
||||
pIdxInfo->idxNum = 3;
|
||||
}
|
||||
}else{
|
||||
pIdxInfo->estimatedCost = (double)2147483647;
|
||||
pIdxInfo->estimatedRows = 2147483647;
|
||||
pIdxInfo->idxNum = 0;
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** This following structure defines all the methods for the
|
||||
** carray virtual table.
|
||||
*/
|
||||
static sqlite3_module carrayModule = {
|
||||
0, /* iVersion */
|
||||
0, /* xCreate */
|
||||
carrayConnect, /* xConnect */
|
||||
carrayBestIndex, /* xBestIndex */
|
||||
carrayDisconnect, /* xDisconnect */
|
||||
0, /* xDestroy */
|
||||
carrayOpen, /* xOpen - open a cursor */
|
||||
carrayClose, /* xClose - close a cursor */
|
||||
carrayFilter, /* xFilter - configure scan constraints */
|
||||
carrayNext, /* xNext - advance a cursor */
|
||||
carrayEof, /* xEof - check for end of scan */
|
||||
carrayColumn, /* xColumn - read data */
|
||||
carrayRowid, /* xRowid - read data */
|
||||
0, /* xUpdate */
|
||||
0, /* xBegin */
|
||||
0, /* xSync */
|
||||
0, /* xCommit */
|
||||
0, /* xRollback */
|
||||
0, /* xFindMethod */
|
||||
0, /* xRename */
|
||||
};
|
||||
|
||||
#endif /* SQLITE_OMIT_VIRTUALTABLE */
|
||||
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
int sqlite3_carray_init(
|
||||
sqlite3 *db,
|
||||
char **pzErrMsg,
|
||||
const sqlite3_api_routines *pApi
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
SQLITE_EXTENSION_INIT2(pApi);
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
rc = sqlite3_create_module(db, "carray", &carrayModule, 0);
|
||||
#endif
|
||||
return rc;
|
||||
}
|
||||
869
ext/misc/csv.c
Normal file
869
ext/misc/csv.c
Normal file
@ -0,0 +1,869 @@
|
||||
/*
|
||||
** 2016-05-28
|
||||
**
|
||||
** 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 contains the implementation of an SQLite virtual table for
|
||||
** reading CSV files.
|
||||
**
|
||||
** Usage:
|
||||
**
|
||||
** .load ./csv
|
||||
** CREATE VIRTUAL TABLE temp.csv USING csv(filename=FILENAME);
|
||||
** SELECT * FROM csv;
|
||||
**
|
||||
** The columns are named "c1", "c2", "c3", ... by default. But the
|
||||
** application can define its own CREATE TABLE statement as an additional
|
||||
** parameter. For example:
|
||||
**
|
||||
** CREATE VIRTUAL TABLE temp.csv2 USING csv(
|
||||
** filename = "../http.log",
|
||||
** schema = "CREATE TABLE x(date,ipaddr,url,referrer,userAgent)"
|
||||
** );
|
||||
**
|
||||
** Instead of specifying a file, the text of the CSV can be loaded using
|
||||
** the data= parameter.
|
||||
**
|
||||
** If the columns=N parameter is supplied, then the CSV file is assumed to have
|
||||
** N columns. If the columns parameter is omitted, the CSV file is opened
|
||||
** as soon as the virtual table is constructed and the first row of the CSV
|
||||
** is read in order to count the tables.
|
||||
**
|
||||
** Some extra debugging features (used for testing virtual tables) are available
|
||||
** if this module is compiled with -DSQLITE_TEST.
|
||||
*/
|
||||
#include <sqlite3ext.h>
|
||||
SQLITE_EXTENSION_INIT1
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <stdarg.h>
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
|
||||
/*
|
||||
** A macro to hint to the compiler that a function should not be
|
||||
** inlined.
|
||||
*/
|
||||
#if defined(__GNUC__)
|
||||
# define CSV_NOINLINE __attribute__((noinline))
|
||||
#elif defined(_MSC_VER) && _MSC_VER>=1310
|
||||
# define CSV_NOINLINE __declspec(noinline)
|
||||
#else
|
||||
# define CSV_NOINLINE
|
||||
#endif
|
||||
|
||||
|
||||
/* Max size of the error message in a CsvReader */
|
||||
#define CSV_MXERR 200
|
||||
|
||||
/* Size of the CsvReader input buffer */
|
||||
#define CSV_INBUFSZ 1024
|
||||
|
||||
/* A context object used when read a CSV file. */
|
||||
typedef struct CsvReader CsvReader;
|
||||
struct CsvReader {
|
||||
FILE *in; /* Read the CSV text from this input stream */
|
||||
char *z; /* Accumulated text for a field */
|
||||
int n; /* Number of bytes in z */
|
||||
int nAlloc; /* Space allocated for z[] */
|
||||
int nLine; /* Current line number */
|
||||
char cTerm; /* Character that terminated the most recent field */
|
||||
size_t iIn; /* Next unread character in the input buffer */
|
||||
size_t nIn; /* Number of characters in the input buffer */
|
||||
char *zIn; /* The input buffer */
|
||||
char zErr[CSV_MXERR]; /* Error message */
|
||||
};
|
||||
|
||||
/* Initialize a CsvReader object */
|
||||
static void csv_reader_init(CsvReader *p){
|
||||
p->in = 0;
|
||||
p->z = 0;
|
||||
p->n = 0;
|
||||
p->nAlloc = 0;
|
||||
p->nLine = 0;
|
||||
p->nIn = 0;
|
||||
p->zIn = 0;
|
||||
p->zErr[0] = 0;
|
||||
}
|
||||
|
||||
/* Close and reset a CsvReader object */
|
||||
static void csv_reader_reset(CsvReader *p){
|
||||
if( p->in ){
|
||||
fclose(p->in);
|
||||
sqlite3_free(p->zIn);
|
||||
}
|
||||
sqlite3_free(p->z);
|
||||
csv_reader_init(p);
|
||||
}
|
||||
|
||||
/* Report an error on a CsvReader */
|
||||
static void csv_errmsg(CsvReader *p, const char *zFormat, ...){
|
||||
va_list ap;
|
||||
va_start(ap, zFormat);
|
||||
sqlite3_vsnprintf(CSV_MXERR, p->zErr, zFormat, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/* Open the file associated with a CsvReader
|
||||
** Return the number of errors.
|
||||
*/
|
||||
static int csv_reader_open(
|
||||
CsvReader *p, /* The reader to open */
|
||||
const char *zFilename, /* Read from this filename */
|
||||
const char *zData /* ... or use this data */
|
||||
){
|
||||
if( zFilename ){
|
||||
p->zIn = sqlite3_malloc( CSV_INBUFSZ );
|
||||
if( p->zIn==0 ){
|
||||
csv_errmsg(p, "out of memory");
|
||||
return 1;
|
||||
}
|
||||
p->in = fopen(zFilename, "rb");
|
||||
if( p->in==0 ){
|
||||
csv_reader_reset(p);
|
||||
csv_errmsg(p, "cannot open '%s' for reading", zFilename);
|
||||
return 1;
|
||||
}
|
||||
}else{
|
||||
assert( p->in==0 );
|
||||
p->zIn = (char*)zData;
|
||||
p->nIn = strlen(zData);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The input buffer has overflowed. Refill the input buffer, then
|
||||
** return the next character
|
||||
*/
|
||||
static CSV_NOINLINE int csv_getc_refill(CsvReader *p){
|
||||
size_t got;
|
||||
|
||||
assert( p->iIn>=p->nIn ); /* Only called on an empty input buffer */
|
||||
assert( p->in!=0 ); /* Only called if reading froma file */
|
||||
|
||||
got = fread(p->zIn, 1, CSV_INBUFSZ, p->in);
|
||||
if( got==0 ) return EOF;
|
||||
p->nIn = got;
|
||||
p->iIn = 1;
|
||||
return p->zIn[0];
|
||||
}
|
||||
|
||||
/* Return the next character of input. Return EOF at end of input. */
|
||||
static int csv_getc(CsvReader *p){
|
||||
if( p->iIn >= p->nIn ){
|
||||
if( p->in!=0 ) return csv_getc_refill(p);
|
||||
return EOF;
|
||||
}
|
||||
return p->zIn[p->iIn++];
|
||||
}
|
||||
|
||||
/* Increase the size of p->z and append character c to the end.
|
||||
** Return 0 on success and non-zero if there is an OOM error */
|
||||
static CSV_NOINLINE int csv_resize_and_append(CsvReader *p, char c){
|
||||
char *zNew;
|
||||
int nNew = p->nAlloc*2 + 100;
|
||||
zNew = sqlite3_realloc64(p->z, nNew);
|
||||
if( zNew ){
|
||||
p->z = zNew;
|
||||
p->nAlloc = nNew;
|
||||
p->z[p->n++] = c;
|
||||
return 0;
|
||||
}else{
|
||||
csv_errmsg(p, "out of memory");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Append a single character to the CsvReader.z[] array.
|
||||
** Return 0 on success and non-zero if there is an OOM error */
|
||||
static int csv_append(CsvReader *p, char c){
|
||||
if( p->n>=p->nAlloc-1 ) return csv_resize_and_append(p, c);
|
||||
p->z[p->n++] = c;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Read a single field of CSV text. Compatible with rfc4180 and extended
|
||||
** with the option of having a separator other than ",".
|
||||
**
|
||||
** + Input comes from p->in.
|
||||
** + Store results in p->z of length p->n. Space to hold p->z comes
|
||||
** from sqlite3_malloc64().
|
||||
** + Keep track of the line number in p->nLine.
|
||||
** + Store the character that terminates the field in p->cTerm. Store
|
||||
** EOF on end-of-file.
|
||||
**
|
||||
** Return "" at EOF. Return 0 on an OOM error.
|
||||
*/
|
||||
static char *csv_read_one_field(CsvReader *p){
|
||||
int c;
|
||||
p->n = 0;
|
||||
c = csv_getc(p);
|
||||
if( c==EOF ){
|
||||
p->cTerm = EOF;
|
||||
return "";
|
||||
}
|
||||
if( c=='"' ){
|
||||
int pc, ppc;
|
||||
int startLine = p->nLine;
|
||||
pc = ppc = 0;
|
||||
while( 1 ){
|
||||
c = csv_getc(p);
|
||||
if( c<='"' || pc=='"' ){
|
||||
if( c=='\n' ) p->nLine++;
|
||||
if( c=='"' ){
|
||||
if( pc=='"' ){
|
||||
pc = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if( (c==',' && pc=='"')
|
||||
|| (c=='\n' && pc=='"')
|
||||
|| (c=='\n' && pc=='\r' && ppc=='"')
|
||||
|| (c==EOF && pc=='"')
|
||||
){
|
||||
do{ p->n--; }while( p->z[p->n]!='"' );
|
||||
p->cTerm = (char)c;
|
||||
break;
|
||||
}
|
||||
if( pc=='"' && c!='\r' ){
|
||||
csv_errmsg(p, "line %d: unescaped %c character", p->nLine, '"');
|
||||
break;
|
||||
}
|
||||
if( c==EOF ){
|
||||
csv_errmsg(p, "line %d: unterminated %c-quoted field\n",
|
||||
startLine, '"');
|
||||
p->cTerm = (char)c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( csv_append(p, (char)c) ) return 0;
|
||||
ppc = pc;
|
||||
pc = c;
|
||||
}
|
||||
}else{
|
||||
while( c>',' || (c!=EOF && c!=',' && c!='\n') ){
|
||||
if( csv_append(p, (char)c) ) return 0;
|
||||
c = csv_getc(p);
|
||||
}
|
||||
if( c=='\n' ){
|
||||
p->nLine++;
|
||||
if( p->n>0 && p->z[p->n-1]=='\r' ) p->n--;
|
||||
}
|
||||
p->cTerm = (char)c;
|
||||
}
|
||||
if( p->z ) p->z[p->n] = 0;
|
||||
return p->z;
|
||||
}
|
||||
|
||||
|
||||
/* Forward references to the various virtual table methods implemented
|
||||
** in this file. */
|
||||
static int csvtabCreate(sqlite3*, void*, int, const char*const*,
|
||||
sqlite3_vtab**,char**);
|
||||
static int csvtabConnect(sqlite3*, void*, int, const char*const*,
|
||||
sqlite3_vtab**,char**);
|
||||
static int csvtabBestIndex(sqlite3_vtab*,sqlite3_index_info*);
|
||||
static int csvtabDisconnect(sqlite3_vtab*);
|
||||
static int csvtabOpen(sqlite3_vtab*, sqlite3_vtab_cursor**);
|
||||
static int csvtabClose(sqlite3_vtab_cursor*);
|
||||
static int csvtabFilter(sqlite3_vtab_cursor*, int idxNum, const char *idxStr,
|
||||
int argc, sqlite3_value **argv);
|
||||
static int csvtabNext(sqlite3_vtab_cursor*);
|
||||
static int csvtabEof(sqlite3_vtab_cursor*);
|
||||
static int csvtabColumn(sqlite3_vtab_cursor*,sqlite3_context*,int);
|
||||
static int csvtabRowid(sqlite3_vtab_cursor*,sqlite3_int64*);
|
||||
|
||||
/* An instance of the CSV virtual table */
|
||||
typedef struct CsvTable {
|
||||
sqlite3_vtab base; /* Base class. Must be first */
|
||||
char *zFilename; /* Name of the CSV file */
|
||||
char *zData; /* Raw CSV data in lieu of zFilename */
|
||||
long iStart; /* Offset to start of data in zFilename */
|
||||
int nCol; /* Number of columns in the CSV file */
|
||||
unsigned int tstFlags; /* Bit values used for testing */
|
||||
} CsvTable;
|
||||
|
||||
/* Allowed values for tstFlags */
|
||||
#define CSVTEST_FIDX 0x0001 /* Pretend that constrained searchs cost less*/
|
||||
|
||||
/* A cursor for the CSV virtual table */
|
||||
typedef struct CsvCursor {
|
||||
sqlite3_vtab_cursor base; /* Base class. Must be first */
|
||||
CsvReader rdr; /* The CsvReader object */
|
||||
char **azVal; /* Value of the current row */
|
||||
int *aLen; /* Length of each entry */
|
||||
sqlite3_int64 iRowid; /* The current rowid. Negative for EOF */
|
||||
} CsvCursor;
|
||||
|
||||
/* Transfer error message text from a reader into a CsvTable */
|
||||
static void csv_xfer_error(CsvTable *pTab, CsvReader *pRdr){
|
||||
sqlite3_free(pTab->base.zErrMsg);
|
||||
pTab->base.zErrMsg = sqlite3_mprintf("%s", pRdr->zErr);
|
||||
}
|
||||
|
||||
/*
|
||||
** This method is the destructor fo a CsvTable object.
|
||||
*/
|
||||
static int csvtabDisconnect(sqlite3_vtab *pVtab){
|
||||
CsvTable *p = (CsvTable*)pVtab;
|
||||
sqlite3_free(p->zFilename);
|
||||
sqlite3_free(p->zData);
|
||||
sqlite3_free(p);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/* Skip leading whitespace. Return a pointer to the first non-whitespace
|
||||
** character, or to the zero terminator if the string has only whitespace */
|
||||
static const char *csv_skip_whitespace(const char *z){
|
||||
while( isspace((unsigned char)z[0]) ) z++;
|
||||
return z;
|
||||
}
|
||||
|
||||
/* Remove trailing whitespace from the end of string z[] */
|
||||
static void csv_trim_whitespace(char *z){
|
||||
size_t n = strlen(z);
|
||||
while( n>0 && isspace((unsigned char)z[n]) ) n--;
|
||||
z[n] = 0;
|
||||
}
|
||||
|
||||
/* Dequote the string */
|
||||
static void csv_dequote(char *z){
|
||||
int j;
|
||||
char cQuote = z[0];
|
||||
size_t i, n;
|
||||
|
||||
if( cQuote!='\'' && cQuote!='"' ) return;
|
||||
n = strlen(z);
|
||||
if( n<2 || z[n-1]!=z[0] ) return;
|
||||
for(i=1, j=0; i<n-1; i++){
|
||||
if( z[i]==cQuote && z[i+1]==cQuote ) i++;
|
||||
z[j++] = z[i];
|
||||
}
|
||||
z[j] = 0;
|
||||
}
|
||||
|
||||
/* Check to see if the string is of the form: "TAG = VALUE" with optional
|
||||
** whitespace before and around tokens. If it is, return a pointer to the
|
||||
** first character of VALUE. If it is not, return NULL.
|
||||
*/
|
||||
static const char *csv_parameter(const char *zTag, int nTag, const char *z){
|
||||
z = csv_skip_whitespace(z);
|
||||
if( strncmp(zTag, z, nTag)!=0 ) return 0;
|
||||
z = csv_skip_whitespace(z+nTag);
|
||||
if( z[0]!='=' ) return 0;
|
||||
return csv_skip_whitespace(z+1);
|
||||
}
|
||||
|
||||
/* Decode a parameter that requires a dequoted string.
|
||||
**
|
||||
** Return 1 if the parameter is seen, or 0 if not. 1 is returned
|
||||
** even if there is an error. If an error occurs, then an error message
|
||||
** is left in p->zErr. If there are no errors, p->zErr[0]==0.
|
||||
*/
|
||||
static int csv_string_parameter(
|
||||
CsvReader *p, /* Leave the error message here, if there is one */
|
||||
const char *zParam, /* Parameter we are checking for */
|
||||
const char *zArg, /* Raw text of the virtual table argment */
|
||||
char **pzVal /* Write the dequoted string value here */
|
||||
){
|
||||
const char *zValue;
|
||||
zValue = csv_parameter(zParam,(int)strlen(zParam),zArg);
|
||||
if( zValue==0 ) return 0;
|
||||
p->zErr[0] = 0;
|
||||
if( *pzVal ){
|
||||
csv_errmsg(p, "more than one '%s' parameter", zParam);
|
||||
return 1;
|
||||
}
|
||||
*pzVal = sqlite3_mprintf("%s", zValue);
|
||||
if( *pzVal==0 ){
|
||||
csv_errmsg(p, "out of memory");
|
||||
return 1;
|
||||
}
|
||||
csv_trim_whitespace(*pzVal);
|
||||
csv_dequote(*pzVal);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/* Return 0 if the argument is false and 1 if it is true. Return -1 if
|
||||
** we cannot really tell.
|
||||
*/
|
||||
static int csv_boolean(const char *z){
|
||||
if( sqlite3_stricmp("yes",z)==0
|
||||
|| sqlite3_stricmp("on",z)==0
|
||||
|| sqlite3_stricmp("true",z)==0
|
||||
|| (z[0]=='1' && z[0]==0)
|
||||
){
|
||||
return 1;
|
||||
}
|
||||
if( sqlite3_stricmp("no",z)==0
|
||||
|| sqlite3_stricmp("off",z)==0
|
||||
|| sqlite3_stricmp("false",z)==0
|
||||
|| (z[0]=='0' && z[1]==0)
|
||||
){
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Parameters:
|
||||
** filename=FILENAME Name of file containing CSV content
|
||||
** data=TEXT Direct CSV content.
|
||||
** schema=SCHEMA Alternative CSV schema.
|
||||
** header=YES|NO First row of CSV defines the names of
|
||||
** columns if "yes". Default "no".
|
||||
** columns=N Assume the CSV file contains N columns.
|
||||
**
|
||||
** Only available if compiled with SQLITE_TEST:
|
||||
**
|
||||
** testflags=N Bitmask of test flags. Optional
|
||||
**
|
||||
** If schema= is omitted, then the columns are named "c0", "c1", "c2",
|
||||
** and so forth. If columns=N is omitted, then the file is opened and
|
||||
** the number of columns in the first row is counted to determine the
|
||||
** column count. If header=YES, then the first row is skipped.
|
||||
*/
|
||||
static int csvtabConnect(
|
||||
sqlite3 *db,
|
||||
void *pAux,
|
||||
int argc, const char *const*argv,
|
||||
sqlite3_vtab **ppVtab,
|
||||
char **pzErr
|
||||
){
|
||||
CsvTable *pNew = 0; /* The CsvTable object to construct */
|
||||
int bHeader = -1; /* header= flags. -1 means not seen yet */
|
||||
int rc = SQLITE_OK; /* Result code from this routine */
|
||||
int i, j; /* Loop counters */
|
||||
#ifdef SQLITE_TEST
|
||||
int tstFlags = 0; /* Value for testflags=N parameter */
|
||||
#endif
|
||||
int nCol = -99; /* Value of the columns= parameter */
|
||||
CsvReader sRdr; /* A CSV file reader used to store an error
|
||||
** message and/or to count the number of columns */
|
||||
static const char *azParam[] = {
|
||||
"filename", "data", "schema",
|
||||
};
|
||||
char *azPValue[3]; /* Parameter values */
|
||||
# define CSV_FILENAME (azPValue[0])
|
||||
# define CSV_DATA (azPValue[1])
|
||||
# define CSV_SCHEMA (azPValue[2])
|
||||
|
||||
|
||||
assert( sizeof(azPValue)==sizeof(azParam) );
|
||||
memset(&sRdr, 0, sizeof(sRdr));
|
||||
memset(azPValue, 0, sizeof(azPValue));
|
||||
for(i=3; i<argc; i++){
|
||||
const char *z = argv[i];
|
||||
const char *zValue;
|
||||
for(j=0; j<sizeof(azParam)/sizeof(azParam[0]); j++){
|
||||
if( csv_string_parameter(&sRdr, azParam[j], z, &azPValue[j]) ) break;
|
||||
}
|
||||
if( j<sizeof(azParam)/sizeof(azParam[0]) ){
|
||||
if( sRdr.zErr[0] ) goto csvtab_connect_error;
|
||||
}else
|
||||
if( (zValue = csv_parameter("header",6,z))!=0 ){
|
||||
int x;
|
||||
if( bHeader>=0 ){
|
||||
csv_errmsg(&sRdr, "more than one 'header' parameter");
|
||||
goto csvtab_connect_error;
|
||||
}
|
||||
x = csv_boolean(zValue);
|
||||
if( x==1 ){
|
||||
bHeader = 1;
|
||||
}else if( x==0 ){
|
||||
bHeader = 0;
|
||||
}else{
|
||||
csv_errmsg(&sRdr, "unrecognized argument to 'header': %s", zValue);
|
||||
goto csvtab_connect_error;
|
||||
}
|
||||
}else
|
||||
#ifdef SQLITE_TEST
|
||||
if( (zValue = csv_parameter("testflags",9,z))!=0 ){
|
||||
tstFlags = (unsigned int)atoi(zValue);
|
||||
}else
|
||||
#endif
|
||||
if( (zValue = csv_parameter("columns",7,z))!=0 ){
|
||||
if( nCol>0 ){
|
||||
csv_errmsg(&sRdr, "more than one 'columns' parameter");
|
||||
goto csvtab_connect_error;
|
||||
}
|
||||
nCol = atoi(zValue);
|
||||
if( nCol<=0 ){
|
||||
csv_errmsg(&sRdr, "must have at least one column");
|
||||
goto csvtab_connect_error;
|
||||
}
|
||||
}else
|
||||
{
|
||||
csv_errmsg(&sRdr, "unrecognized parameter '%s'", z);
|
||||
goto csvtab_connect_error;
|
||||
}
|
||||
}
|
||||
if( (CSV_FILENAME==0)==(CSV_DATA==0) ){
|
||||
csv_errmsg(&sRdr, "must either filename= or data= but not both");
|
||||
goto csvtab_connect_error;
|
||||
}
|
||||
if( nCol<=0 && csv_reader_open(&sRdr, CSV_FILENAME, CSV_DATA) ){
|
||||
goto csvtab_connect_error;
|
||||
}
|
||||
pNew = sqlite3_malloc( sizeof(*pNew) );
|
||||
*ppVtab = (sqlite3_vtab*)pNew;
|
||||
if( pNew==0 ) goto csvtab_connect_oom;
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
if( nCol>0 ){
|
||||
pNew->nCol = nCol;
|
||||
}else{
|
||||
do{
|
||||
const char *z = csv_read_one_field(&sRdr);
|
||||
if( z==0 ) goto csvtab_connect_oom;
|
||||
pNew->nCol++;
|
||||
}while( sRdr.cTerm==',' );
|
||||
}
|
||||
pNew->zFilename = CSV_FILENAME; CSV_FILENAME = 0;
|
||||
pNew->zData = CSV_DATA; CSV_DATA = 0;
|
||||
#ifdef SQLITE_TEST
|
||||
pNew->tstFlags = tstFlags;
|
||||
#endif
|
||||
pNew->iStart = bHeader==1 ? ftell(sRdr.in) : 0;
|
||||
csv_reader_reset(&sRdr);
|
||||
if( CSV_SCHEMA==0 ){
|
||||
char *zSep = "";
|
||||
CSV_SCHEMA = sqlite3_mprintf("CREATE TABLE x(");
|
||||
if( CSV_SCHEMA==0 ) goto csvtab_connect_oom;
|
||||
for(i=0; i<pNew->nCol; i++){
|
||||
CSV_SCHEMA = sqlite3_mprintf("%z%sc%d TEXT",CSV_SCHEMA, zSep, i);
|
||||
zSep = ",";
|
||||
}
|
||||
CSV_SCHEMA = sqlite3_mprintf("%z);", CSV_SCHEMA);
|
||||
}
|
||||
rc = sqlite3_declare_vtab(db, CSV_SCHEMA);
|
||||
if( rc ) goto csvtab_connect_error;
|
||||
for(i=0; i<sizeof(azPValue)/sizeof(azPValue[0]); i++){
|
||||
sqlite3_free(azPValue[i]);
|
||||
}
|
||||
return SQLITE_OK;
|
||||
|
||||
csvtab_connect_oom:
|
||||
rc = SQLITE_NOMEM;
|
||||
csv_errmsg(&sRdr, "out of memory");
|
||||
|
||||
csvtab_connect_error:
|
||||
if( pNew ) csvtabDisconnect(&pNew->base);
|
||||
for(i=0; i<sizeof(azPValue)/sizeof(azPValue[0]); i++){
|
||||
sqlite3_free(azPValue[i]);
|
||||
}
|
||||
if( sRdr.zErr[0] ){
|
||||
sqlite3_free(*pzErr);
|
||||
*pzErr = sqlite3_mprintf("%s", sRdr.zErr);
|
||||
}
|
||||
csv_reader_reset(&sRdr);
|
||||
if( rc==SQLITE_OK ) rc = SQLITE_ERROR;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Reset the current row content held by a CsvCursor.
|
||||
*/
|
||||
static void csvtabCursorRowReset(CsvCursor *pCur){
|
||||
CsvTable *pTab = (CsvTable*)pCur->base.pVtab;
|
||||
int i;
|
||||
for(i=0; i<pTab->nCol; i++){
|
||||
sqlite3_free(pCur->azVal[i]);
|
||||
pCur->azVal[i] = 0;
|
||||
pCur->aLen[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** The xConnect and xCreate methods do the same thing, but they must be
|
||||
** different so that the virtual table is not an eponymous virtual table.
|
||||
*/
|
||||
static int csvtabCreate(
|
||||
sqlite3 *db,
|
||||
void *pAux,
|
||||
int argc, const char *const*argv,
|
||||
sqlite3_vtab **ppVtab,
|
||||
char **pzErr
|
||||
){
|
||||
return csvtabConnect(db, pAux, argc, argv, ppVtab, pzErr);
|
||||
}
|
||||
|
||||
/*
|
||||
** Destructor for a CsvCursor.
|
||||
*/
|
||||
static int csvtabClose(sqlite3_vtab_cursor *cur){
|
||||
CsvCursor *pCur = (CsvCursor*)cur;
|
||||
csvtabCursorRowReset(pCur);
|
||||
csv_reader_reset(&pCur->rdr);
|
||||
sqlite3_free(cur);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Constructor for a new CsvTable cursor object.
|
||||
*/
|
||||
static int csvtabOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
|
||||
CsvTable *pTab = (CsvTable*)p;
|
||||
CsvCursor *pCur;
|
||||
size_t nByte;
|
||||
nByte = sizeof(*pCur) + (sizeof(char*)+sizeof(int))*pTab->nCol;
|
||||
pCur = sqlite3_malloc64( nByte );
|
||||
if( pCur==0 ) return SQLITE_NOMEM;
|
||||
memset(pCur, 0, nByte);
|
||||
pCur->azVal = (char**)&pCur[1];
|
||||
pCur->aLen = (int*)&pCur->azVal[pTab->nCol];
|
||||
*ppCursor = &pCur->base;
|
||||
if( csv_reader_open(&pCur->rdr, pTab->zFilename, pTab->zData) ){
|
||||
csv_xfer_error(pTab, &pCur->rdr);
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Advance a CsvCursor to its next row of input.
|
||||
** Set the EOF marker if we reach the end of input.
|
||||
*/
|
||||
static int csvtabNext(sqlite3_vtab_cursor *cur){
|
||||
CsvCursor *pCur = (CsvCursor*)cur;
|
||||
CsvTable *pTab = (CsvTable*)cur->pVtab;
|
||||
int i = 0;
|
||||
char *z;
|
||||
do{
|
||||
z = csv_read_one_field(&pCur->rdr);
|
||||
if( z==0 ){
|
||||
csv_xfer_error(pTab, &pCur->rdr);
|
||||
break;
|
||||
}
|
||||
if( i<pTab->nCol ){
|
||||
if( pCur->aLen[i] < pCur->rdr.n+1 ){
|
||||
char *zNew = sqlite3_realloc64(pCur->azVal[i], pCur->rdr.n+1);
|
||||
if( zNew==0 ){
|
||||
csv_errmsg(&pCur->rdr, "out of memory");
|
||||
csv_xfer_error(pTab, &pCur->rdr);
|
||||
break;
|
||||
}
|
||||
pCur->azVal[i] = zNew;
|
||||
pCur->aLen[i] = pCur->rdr.n+1;
|
||||
}
|
||||
memcpy(pCur->azVal[i], z, pCur->rdr.n+1);
|
||||
i++;
|
||||
}
|
||||
}while( pCur->rdr.cTerm==',' );
|
||||
while( i<pTab->nCol ){
|
||||
sqlite3_free(pCur->azVal[i]);
|
||||
pCur->azVal[i] = 0;
|
||||
pCur->aLen[i] = 0;
|
||||
i++;
|
||||
}
|
||||
if( z==0 || pCur->rdr.cTerm==EOF ){
|
||||
pCur->iRowid = -1;
|
||||
}else{
|
||||
pCur->iRowid++;
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return values of columns for the row at which the CsvCursor
|
||||
** is currently pointing.
|
||||
*/
|
||||
static int csvtabColumn(
|
||||
sqlite3_vtab_cursor *cur, /* The cursor */
|
||||
sqlite3_context *ctx, /* First argument to sqlite3_result_...() */
|
||||
int i /* Which column to return */
|
||||
){
|
||||
CsvCursor *pCur = (CsvCursor*)cur;
|
||||
CsvTable *pTab = (CsvTable*)cur->pVtab;
|
||||
if( i>=0 && i<pTab->nCol && pCur->azVal[i]!=0 ){
|
||||
sqlite3_result_text(ctx, pCur->azVal[i], -1, SQLITE_STATIC);
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the rowid for the current row.
|
||||
*/
|
||||
static int csvtabRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
|
||||
CsvCursor *pCur = (CsvCursor*)cur;
|
||||
*pRowid = pCur->iRowid;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return TRUE if the cursor has been moved off of the last
|
||||
** row of output.
|
||||
*/
|
||||
static int csvtabEof(sqlite3_vtab_cursor *cur){
|
||||
CsvCursor *pCur = (CsvCursor*)cur;
|
||||
return pCur->iRowid<0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Only a full table scan is supported. So xFilter simply rewinds to
|
||||
** the beginning.
|
||||
*/
|
||||
static int csvtabFilter(
|
||||
sqlite3_vtab_cursor *pVtabCursor,
|
||||
int idxNum, const char *idxStr,
|
||||
int argc, sqlite3_value **argv
|
||||
){
|
||||
CsvCursor *pCur = (CsvCursor*)pVtabCursor;
|
||||
CsvTable *pTab = (CsvTable*)pVtabCursor->pVtab;
|
||||
pCur->iRowid = 0;
|
||||
if( pCur->rdr.in==0 ){
|
||||
assert( pCur->rdr.zIn==pTab->zData );
|
||||
assert( pTab->iStart>=0 );
|
||||
assert( (size_t)pTab->iStart<=pCur->rdr.nIn );
|
||||
pCur->rdr.iIn = pTab->iStart;
|
||||
}else{
|
||||
fseek(pCur->rdr.in, pTab->iStart, SEEK_SET);
|
||||
pCur->rdr.iIn = 0;
|
||||
pCur->rdr.nIn = 0;
|
||||
}
|
||||
return csvtabNext(pVtabCursor);
|
||||
}
|
||||
|
||||
/*
|
||||
** Only a forward full table scan is supported. xBestIndex is mostly
|
||||
** a no-op. If CSVTEST_FIDX is set, then the presence of equality
|
||||
** constraints lowers the estimated cost, which is fiction, but is useful
|
||||
** for testing certain kinds of virtual table behavior.
|
||||
*/
|
||||
static int csvtabBestIndex(
|
||||
sqlite3_vtab *tab,
|
||||
sqlite3_index_info *pIdxInfo
|
||||
){
|
||||
pIdxInfo->estimatedCost = 1000000;
|
||||
#ifdef SQLITE_TEST
|
||||
if( (((CsvTable*)tab)->tstFlags & CSVTEST_FIDX)!=0 ){
|
||||
/* The usual (and sensible) case is to always do a full table scan.
|
||||
** The code in this branch only runs when testflags=1. This code
|
||||
** generates an artifical and unrealistic plan which is useful
|
||||
** for testing virtual table logic but is not helpful to real applications.
|
||||
**
|
||||
** Any ==, LIKE, or GLOB constraint is marked as usable by the virtual
|
||||
** table (even though it is not) and the cost of running the virtual table
|
||||
** is reduced from 1 million to just 10. The constraints are *not* marked
|
||||
** as omittable, however, so the query planner should still generate a
|
||||
** plan that gives a correct answer, even if they plan is not optimal.
|
||||
*/
|
||||
int i;
|
||||
int nConst = 0;
|
||||
for(i=0; i<pIdxInfo->nConstraint; i++){
|
||||
unsigned char op;
|
||||
if( pIdxInfo->aConstraint[i].usable==0 ) continue;
|
||||
op = pIdxInfo->aConstraint[i].op;
|
||||
if( op==SQLITE_INDEX_CONSTRAINT_EQ
|
||||
|| op==SQLITE_INDEX_CONSTRAINT_LIKE
|
||||
|| op==SQLITE_INDEX_CONSTRAINT_GLOB
|
||||
){
|
||||
pIdxInfo->estimatedCost = 10;
|
||||
pIdxInfo->aConstraintUsage[nConst].argvIndex = nConst+1;
|
||||
nConst++;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
|
||||
static sqlite3_module CsvModule = {
|
||||
0, /* iVersion */
|
||||
csvtabCreate, /* xCreate */
|
||||
csvtabConnect, /* xConnect */
|
||||
csvtabBestIndex, /* xBestIndex */
|
||||
csvtabDisconnect, /* xDisconnect */
|
||||
csvtabDisconnect, /* xDestroy */
|
||||
csvtabOpen, /* xOpen - open a cursor */
|
||||
csvtabClose, /* xClose - close a cursor */
|
||||
csvtabFilter, /* xFilter - configure scan constraints */
|
||||
csvtabNext, /* xNext - advance a cursor */
|
||||
csvtabEof, /* xEof - check for end of scan */
|
||||
csvtabColumn, /* xColumn - read data */
|
||||
csvtabRowid, /* xRowid - read data */
|
||||
0, /* xUpdate */
|
||||
0, /* xBegin */
|
||||
0, /* xSync */
|
||||
0, /* xCommit */
|
||||
0, /* xRollback */
|
||||
0, /* xFindMethod */
|
||||
0, /* xRename */
|
||||
};
|
||||
|
||||
#ifdef SQLITE_TEST
|
||||
/*
|
||||
** For virtual table testing, make a version of the CSV virtual table
|
||||
** available that has an xUpdate function. But the xUpdate always returns
|
||||
** SQLITE_READONLY since the CSV file is not really writable.
|
||||
*/
|
||||
static int csvtabUpdate(sqlite3_vtab *p,int n,sqlite3_value**v,sqlite3_int64*x){
|
||||
return SQLITE_READONLY;
|
||||
}
|
||||
static sqlite3_module CsvModuleFauxWrite = {
|
||||
0, /* iVersion */
|
||||
csvtabCreate, /* xCreate */
|
||||
csvtabConnect, /* xConnect */
|
||||
csvtabBestIndex, /* xBestIndex */
|
||||
csvtabDisconnect, /* xDisconnect */
|
||||
csvtabDisconnect, /* xDestroy */
|
||||
csvtabOpen, /* xOpen - open a cursor */
|
||||
csvtabClose, /* xClose - close a cursor */
|
||||
csvtabFilter, /* xFilter - configure scan constraints */
|
||||
csvtabNext, /* xNext - advance a cursor */
|
||||
csvtabEof, /* xEof - check for end of scan */
|
||||
csvtabColumn, /* xColumn - read data */
|
||||
csvtabRowid, /* xRowid - read data */
|
||||
csvtabUpdate, /* xUpdate */
|
||||
0, /* xBegin */
|
||||
0, /* xSync */
|
||||
0, /* xCommit */
|
||||
0, /* xRollback */
|
||||
0, /* xFindMethod */
|
||||
0, /* xRename */
|
||||
};
|
||||
#endif /* SQLITE_TEST */
|
||||
|
||||
#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) */
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
/*
|
||||
** This routine is called when the extension is loaded. The new
|
||||
** CSV virtual table module is registered with the calling database
|
||||
** connection.
|
||||
*/
|
||||
int sqlite3_csv_init(
|
||||
sqlite3 *db,
|
||||
char **pzErrMsg,
|
||||
const sqlite3_api_routines *pApi
|
||||
){
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
int rc;
|
||||
SQLITE_EXTENSION_INIT2(pApi);
|
||||
rc = sqlite3_create_module(db, "csv", &CsvModule, 0);
|
||||
#ifdef SQLITE_TEST
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_create_module(db, "csv_wr", &CsvModuleFauxWrite, 0);
|
||||
}
|
||||
#endif
|
||||
return rc;
|
||||
#else
|
||||
return SQLITE_OK;
|
||||
#endif
|
||||
}
|
||||
@ -344,10 +344,10 @@ static int fuzzerLoadOneRule(
|
||||
memset(pRule, 0, sizeof(*pRule));
|
||||
pRule->zFrom = pRule->zTo;
|
||||
pRule->zFrom += nTo + 1;
|
||||
pRule->nFrom = nFrom;
|
||||
pRule->nFrom = (fuzzer_len)nFrom;
|
||||
memcpy(pRule->zFrom, zFrom, nFrom+1);
|
||||
memcpy(pRule->zTo, zTo, nTo+1);
|
||||
pRule->nTo = nTo;
|
||||
pRule->nTo = (fuzzer_len)nTo;
|
||||
pRule->rCost = nCost;
|
||||
pRule->iRuleset = (int)iRuleset;
|
||||
}
|
||||
|
||||
@ -49,13 +49,15 @@ SQLITE_EXTENSION_INIT1
|
||||
#ifdef sqlite3Isdigit
|
||||
/* Use the SQLite core versions if this routine is part of the
|
||||
** SQLite amalgamation */
|
||||
# define safe_isdigit(x) sqlite3Isdigit(x)
|
||||
# define safe_isalnum(x) sqlite3Isalnum(x)
|
||||
# define safe_isdigit(x) sqlite3Isdigit(x)
|
||||
# define safe_isalnum(x) sqlite3Isalnum(x)
|
||||
# define safe_isxdigit(x) sqlite3Isxdigit(x)
|
||||
#else
|
||||
/* Use the standard library for separate compilation */
|
||||
#include <ctype.h> /* amalgamator: keep */
|
||||
# define safe_isdigit(x) isdigit((unsigned char)(x))
|
||||
# define safe_isalnum(x) isalnum((unsigned char)(x))
|
||||
# define safe_isdigit(x) isdigit((unsigned char)(x))
|
||||
# define safe_isalnum(x) isalnum((unsigned char)(x))
|
||||
# define safe_isxdigit(x) isxdigit((unsigned char)(x))
|
||||
#endif
|
||||
|
||||
/*
|
||||
@ -702,6 +704,15 @@ static int jsonParseAddNode(
|
||||
return pParse->nNode++;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return true if z[] begins with 4 (or more) hexadecimal digits
|
||||
*/
|
||||
static int jsonIs4Hex(const char *z){
|
||||
int i;
|
||||
for(i=0; i<4; i++) if( !safe_isxdigit(z[i]) ) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
** Parse a single JSON value which begins at pParse->zJson[i]. Return the
|
||||
** index of the first character past the end of the value parsed.
|
||||
@ -776,8 +787,13 @@ static int jsonParseValue(JsonParse *pParse, u32 i){
|
||||
if( c==0 ) return -1;
|
||||
if( c=='\\' ){
|
||||
c = pParse->zJson[++j];
|
||||
if( c==0 ) return -1;
|
||||
jnFlags = JNODE_ESCAPE;
|
||||
if( c=='"' || c=='\\' || c=='/' || c=='b' || c=='f'
|
||||
|| c=='n' || c=='r' || c=='t'
|
||||
|| (c=='u' && jsonIs4Hex(pParse->zJson+j+1)) ){
|
||||
jnFlags = JNODE_ESCAPE;
|
||||
}else{
|
||||
return -1;
|
||||
}
|
||||
}else if( c=='"' ){
|
||||
break;
|
||||
}
|
||||
@ -1211,6 +1227,26 @@ static void jsonTest1Func(
|
||||
** Scalar SQL function implementations
|
||||
****************************************************************************/
|
||||
|
||||
/*
|
||||
** Implementation of the json_QUOTE(VALUE) function. Return a JSON value
|
||||
** corresponding to the SQL value input. Mostly this means putting
|
||||
** double-quotes around strings and returning the unquoted string "null"
|
||||
** when given a NULL input.
|
||||
*/
|
||||
static void jsonQuoteFunc(
|
||||
sqlite3_context *ctx,
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
){
|
||||
JsonString jx;
|
||||
UNUSED_PARAM(argc);
|
||||
|
||||
jsonInit(&jx, ctx);
|
||||
jsonAppendValue(&jx, argv[0]);
|
||||
jsonResult(&jx);
|
||||
sqlite3_result_subtype(ctx, JSON_SUBTYPE);
|
||||
}
|
||||
|
||||
/*
|
||||
** Implementation of the json_array(VALUE,...) function. Return a JSON
|
||||
** array that contains all values given in arguments. Or if any argument
|
||||
@ -1625,7 +1661,7 @@ static void jsonObjectFinal(sqlite3_context *ctx){
|
||||
if( pStr ){
|
||||
jsonAppendChar(pStr, '}');
|
||||
if( pStr->bErr ){
|
||||
if( pStr->bErr==0 ) sqlite3_result_error_nomem(ctx);
|
||||
if( pStr->bErr==1 ) sqlite3_result_error_nomem(ctx);
|
||||
assert( pStr->bStatic );
|
||||
}else{
|
||||
sqlite3_result_text(ctx, pStr->zBuf, pStr->nUsed,
|
||||
@ -1903,9 +1939,9 @@ static int jsonEachColumn(
|
||||
/* For json_each() path and root are the same so fall through
|
||||
** into the root case */
|
||||
}
|
||||
case JEACH_ROOT: {
|
||||
default: {
|
||||
const char *zRoot = p->zRoot;
|
||||
if( zRoot==0 ) zRoot = "$";
|
||||
if( zRoot==0 ) zRoot = "$";
|
||||
sqlite3_result_text(ctx, zRoot, -1, SQLITE_STATIC);
|
||||
break;
|
||||
}
|
||||
@ -2124,6 +2160,7 @@ int sqlite3Json1Init(sqlite3 *db){
|
||||
{ "json_extract", -1, 0, jsonExtractFunc },
|
||||
{ "json_insert", -1, 0, jsonSetFunc },
|
||||
{ "json_object", -1, 0, jsonObjectFunc },
|
||||
{ "json_quote", 1, 0, jsonQuoteFunc },
|
||||
{ "json_remove", -1, 0, jsonRemoveFunc },
|
||||
{ "json_replace", -1, 0, jsonReplaceFunc },
|
||||
{ "json_set", -1, 1, jsonSetFunc },
|
||||
|
||||
491
ext/misc/memvfs.c
Normal file
491
ext/misc/memvfs.c
Normal file
@ -0,0 +1,491 @@
|
||||
/*
|
||||
** 2016-09-07
|
||||
**
|
||||
** 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 an in-memory read-only VFS implementation. The application
|
||||
** supplies a block of memory which is the database file, and this VFS
|
||||
** uses that block of memory.
|
||||
**
|
||||
** Because there is no place to store journals and no good way to lock
|
||||
** the "file", this VFS is read-only.
|
||||
**
|
||||
** USAGE:
|
||||
**
|
||||
** sqlite3_open_v2("file:/whatever?ptr=0xf05538&sz=14336", &db,
|
||||
** SQLITE_OPEN_READONLY | SQLITE_OPEN_URI,
|
||||
** "memvfs");
|
||||
**
|
||||
** The ptr= and sz= query parameters are required or the open will fail.
|
||||
** The ptr= parameter gives the memory address of the buffer holding the
|
||||
** read-only database and sz= gives the size of the database. The parameter
|
||||
** values may be in hexadecimal or decimal. The filename is ignored.
|
||||
*/
|
||||
#include <sqlite3ext.h>
|
||||
SQLITE_EXTENSION_INIT1
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
/*
|
||||
** Forward declaration of objects used by this utility
|
||||
*/
|
||||
typedef struct sqlite3_vfs MemVfs;
|
||||
typedef struct MemFile MemFile;
|
||||
|
||||
/* Access to a lower-level VFS that (might) implement dynamic loading,
|
||||
** access to randomness, etc.
|
||||
*/
|
||||
#define ORIGVFS(p) ((sqlite3_vfs*)((p)->pAppData))
|
||||
|
||||
/* An open file */
|
||||
struct MemFile {
|
||||
sqlite3_file base; /* IO methods */
|
||||
sqlite3_int64 sz; /* Size of the file */
|
||||
unsigned char *aData; /* content of the file */
|
||||
};
|
||||
|
||||
/*
|
||||
** Methods for MemFile
|
||||
*/
|
||||
static int memClose(sqlite3_file*);
|
||||
static int memRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
|
||||
static int memWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64 iOfst);
|
||||
static int memTruncate(sqlite3_file*, sqlite3_int64 size);
|
||||
static int memSync(sqlite3_file*, int flags);
|
||||
static int memFileSize(sqlite3_file*, sqlite3_int64 *pSize);
|
||||
static int memLock(sqlite3_file*, int);
|
||||
static int memUnlock(sqlite3_file*, int);
|
||||
static int memCheckReservedLock(sqlite3_file*, int *pResOut);
|
||||
static int memFileControl(sqlite3_file*, int op, void *pArg);
|
||||
static int memSectorSize(sqlite3_file*);
|
||||
static int memDeviceCharacteristics(sqlite3_file*);
|
||||
static int memShmMap(sqlite3_file*, int iPg, int pgsz, int, void volatile**);
|
||||
static int memShmLock(sqlite3_file*, int offset, int n, int flags);
|
||||
static void memShmBarrier(sqlite3_file*);
|
||||
static int memShmUnmap(sqlite3_file*, int deleteFlag);
|
||||
static int memFetch(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp);
|
||||
static int memUnfetch(sqlite3_file*, sqlite3_int64 iOfst, void *p);
|
||||
|
||||
/*
|
||||
** Methods for MemVfs
|
||||
*/
|
||||
static int memOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *);
|
||||
static int memDelete(sqlite3_vfs*, const char *zName, int syncDir);
|
||||
static int memAccess(sqlite3_vfs*, const char *zName, int flags, int *);
|
||||
static int memFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut);
|
||||
static void *memDlOpen(sqlite3_vfs*, const char *zFilename);
|
||||
static void memDlError(sqlite3_vfs*, int nByte, char *zErrMsg);
|
||||
static void (*memDlSym(sqlite3_vfs *pVfs, void *p, const char*zSym))(void);
|
||||
static void memDlClose(sqlite3_vfs*, void*);
|
||||
static int memRandomness(sqlite3_vfs*, int nByte, char *zOut);
|
||||
static int memSleep(sqlite3_vfs*, int microseconds);
|
||||
static int memCurrentTime(sqlite3_vfs*, double*);
|
||||
static int memGetLastError(sqlite3_vfs*, int, char *);
|
||||
static int memCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*);
|
||||
|
||||
static sqlite3_vfs mem_vfs = {
|
||||
2, /* iVersion */
|
||||
0, /* szOsFile (set when registered) */
|
||||
1024, /* mxPathname */
|
||||
0, /* pNext */
|
||||
"memvfs", /* zName */
|
||||
0, /* pAppData (set when registered) */
|
||||
memOpen, /* xOpen */
|
||||
memDelete, /* xDelete */
|
||||
memAccess, /* xAccess */
|
||||
memFullPathname, /* xFullPathname */
|
||||
memDlOpen, /* xDlOpen */
|
||||
memDlError, /* xDlError */
|
||||
memDlSym, /* xDlSym */
|
||||
memDlClose, /* xDlClose */
|
||||
memRandomness, /* xRandomness */
|
||||
memSleep, /* xSleep */
|
||||
memCurrentTime, /* xCurrentTime */
|
||||
memGetLastError, /* xGetLastError */
|
||||
memCurrentTimeInt64 /* xCurrentTimeInt64 */
|
||||
};
|
||||
|
||||
static const sqlite3_io_methods mem_io_methods = {
|
||||
3, /* iVersion */
|
||||
memClose, /* xClose */
|
||||
memRead, /* xRead */
|
||||
memWrite, /* xWrite */
|
||||
memTruncate, /* xTruncate */
|
||||
memSync, /* xSync */
|
||||
memFileSize, /* xFileSize */
|
||||
memLock, /* xLock */
|
||||
memUnlock, /* xUnlock */
|
||||
memCheckReservedLock, /* xCheckReservedLock */
|
||||
memFileControl, /* xFileControl */
|
||||
memSectorSize, /* xSectorSize */
|
||||
memDeviceCharacteristics, /* xDeviceCharacteristics */
|
||||
memShmMap, /* xShmMap */
|
||||
memShmLock, /* xShmLock */
|
||||
memShmBarrier, /* xShmBarrier */
|
||||
memShmUnmap, /* xShmUnmap */
|
||||
memFetch, /* xFetch */
|
||||
memUnfetch /* xUnfetch */
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** Close an mem-file.
|
||||
**
|
||||
** The pData pointer is owned by the application, so there is nothing
|
||||
** to free.
|
||||
*/
|
||||
static int memClose(sqlite3_file *pFile){
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Read data from an mem-file.
|
||||
*/
|
||||
static int memRead(
|
||||
sqlite3_file *pFile,
|
||||
void *zBuf,
|
||||
int iAmt,
|
||||
sqlite_int64 iOfst
|
||||
){
|
||||
MemFile *p = (MemFile *)pFile;
|
||||
memcpy(zBuf, p->aData+iOfst, iAmt);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Write data to an mem-file.
|
||||
*/
|
||||
static int memWrite(
|
||||
sqlite3_file *pFile,
|
||||
const void *z,
|
||||
int iAmt,
|
||||
sqlite_int64 iOfst
|
||||
){
|
||||
return SQLITE_READONLY;
|
||||
}
|
||||
|
||||
/*
|
||||
** Truncate an mem-file.
|
||||
*/
|
||||
static int memTruncate(sqlite3_file *pFile, sqlite_int64 size){
|
||||
return SQLITE_READONLY;
|
||||
}
|
||||
|
||||
/*
|
||||
** Sync an mem-file.
|
||||
*/
|
||||
static int memSync(sqlite3_file *pFile, int flags){
|
||||
return SQLITE_READONLY;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the current file-size of an mem-file.
|
||||
*/
|
||||
static int memFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
|
||||
MemFile *p = (MemFile *)pFile;
|
||||
*pSize = p->sz;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Lock an mem-file.
|
||||
*/
|
||||
static int memLock(sqlite3_file *pFile, int eLock){
|
||||
return SQLITE_READONLY;
|
||||
}
|
||||
|
||||
/*
|
||||
** Unlock an mem-file.
|
||||
*/
|
||||
static int memUnlock(sqlite3_file *pFile, int eLock){
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Check if another file-handle holds a RESERVED lock on an mem-file.
|
||||
*/
|
||||
static int memCheckReservedLock(sqlite3_file *pFile, int *pResOut){
|
||||
*pResOut = 0;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** File control method. For custom operations on an mem-file.
|
||||
*/
|
||||
static int memFileControl(sqlite3_file *pFile, int op, void *pArg){
|
||||
MemFile *p = (MemFile *)pFile;
|
||||
int rc = SQLITE_NOTFOUND;
|
||||
if( op==SQLITE_FCNTL_VFSNAME ){
|
||||
*(char**)pArg = sqlite3_mprintf("mem(%p,%lld)", p->aData, p->sz);
|
||||
rc = SQLITE_OK;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the sector-size in bytes for an mem-file.
|
||||
*/
|
||||
static int memSectorSize(sqlite3_file *pFile){
|
||||
return 1024;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the device characteristic flags supported by an mem-file.
|
||||
*/
|
||||
static int memDeviceCharacteristics(sqlite3_file *pFile){
|
||||
return SQLITE_IOCAP_IMMUTABLE;
|
||||
}
|
||||
|
||||
/* Create a shared memory file mapping */
|
||||
static int memShmMap(
|
||||
sqlite3_file *pFile,
|
||||
int iPg,
|
||||
int pgsz,
|
||||
int bExtend,
|
||||
void volatile **pp
|
||||
){
|
||||
return SQLITE_READONLY;
|
||||
}
|
||||
|
||||
/* Perform locking on a shared-memory segment */
|
||||
static int memShmLock(sqlite3_file *pFile, int offset, int n, int flags){
|
||||
return SQLITE_READONLY;
|
||||
}
|
||||
|
||||
/* Memory barrier operation on shared memory */
|
||||
static void memShmBarrier(sqlite3_file *pFile){
|
||||
return;
|
||||
}
|
||||
|
||||
/* Unmap a shared memory segment */
|
||||
static int memShmUnmap(sqlite3_file *pFile, int deleteFlag){
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/* Fetch a page of a memory-mapped file */
|
||||
static int memFetch(
|
||||
sqlite3_file *pFile,
|
||||
sqlite3_int64 iOfst,
|
||||
int iAmt,
|
||||
void **pp
|
||||
){
|
||||
MemFile *p = (MemFile *)pFile;
|
||||
*pp = (void*)(p->aData + iOfst);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/* Release a memory-mapped page */
|
||||
static int memUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Open an mem file handle.
|
||||
*/
|
||||
static int memOpen(
|
||||
sqlite3_vfs *pVfs,
|
||||
const char *zName,
|
||||
sqlite3_file *pFile,
|
||||
int flags,
|
||||
int *pOutFlags
|
||||
){
|
||||
MemFile *p = (MemFile*)pFile;
|
||||
memset(p, 0, sizeof(*p));
|
||||
if( (flags & SQLITE_OPEN_MAIN_DB)==0 ) return SQLITE_CANTOPEN;
|
||||
p->aData = (unsigned char*)sqlite3_uri_int64(zName,"ptr",0);
|
||||
if( p->aData==0 ) return SQLITE_CANTOPEN;
|
||||
p->sz = sqlite3_uri_int64(zName,"sz",0);
|
||||
if( p->sz<0 ) return SQLITE_CANTOPEN;
|
||||
pFile->pMethods = &mem_io_methods;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Delete the file located at zPath. If the dirSync argument is true,
|
||||
** ensure the file-system modifications are synced to disk before
|
||||
** returning.
|
||||
*/
|
||||
static int memDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
|
||||
return SQLITE_READONLY;
|
||||
}
|
||||
|
||||
/*
|
||||
** Test for access permissions. Return true if the requested permission
|
||||
** is available, or false otherwise.
|
||||
*/
|
||||
static int memAccess(
|
||||
sqlite3_vfs *pVfs,
|
||||
const char *zPath,
|
||||
int flags,
|
||||
int *pResOut
|
||||
){
|
||||
/* The spec says there are three possible values for flags. But only
|
||||
** two of them are actually used */
|
||||
assert( flags==SQLITE_ACCESS_EXISTS || flags==SQLITE_ACCESS_READWRITE );
|
||||
if( flags==SQLITE_ACCESS_READWRITE ){
|
||||
*pResOut = 0;
|
||||
}else{
|
||||
*pResOut = 1;
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Populate buffer zOut with the full canonical pathname corresponding
|
||||
** to the pathname in zPath. zOut is guaranteed to point to a buffer
|
||||
** of at least (INST_MAX_PATHNAME+1) bytes.
|
||||
*/
|
||||
static int memFullPathname(
|
||||
sqlite3_vfs *pVfs,
|
||||
const char *zPath,
|
||||
int nOut,
|
||||
char *zOut
|
||||
){
|
||||
sqlite3_snprintf(nOut, zOut, "%s", zPath);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Open the dynamic library located at zPath and return a handle.
|
||||
*/
|
||||
static void *memDlOpen(sqlite3_vfs *pVfs, const char *zPath){
|
||||
return ORIGVFS(pVfs)->xDlOpen(ORIGVFS(pVfs), zPath);
|
||||
}
|
||||
|
||||
/*
|
||||
** Populate the buffer zErrMsg (size nByte bytes) with a human readable
|
||||
** utf-8 string describing the most recent error encountered associated
|
||||
** with dynamic libraries.
|
||||
*/
|
||||
static void memDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
|
||||
ORIGVFS(pVfs)->xDlError(ORIGVFS(pVfs), nByte, zErrMsg);
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a pointer to the symbol zSymbol in the dynamic library pHandle.
|
||||
*/
|
||||
static void (*memDlSym(sqlite3_vfs *pVfs, void *p, const char *zSym))(void){
|
||||
return ORIGVFS(pVfs)->xDlSym(ORIGVFS(pVfs), p, zSym);
|
||||
}
|
||||
|
||||
/*
|
||||
** Close the dynamic library handle pHandle.
|
||||
*/
|
||||
static void memDlClose(sqlite3_vfs *pVfs, void *pHandle){
|
||||
ORIGVFS(pVfs)->xDlClose(ORIGVFS(pVfs), pHandle);
|
||||
}
|
||||
|
||||
/*
|
||||
** Populate the buffer pointed to by zBufOut with nByte bytes of
|
||||
** random data.
|
||||
*/
|
||||
static int memRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
|
||||
return ORIGVFS(pVfs)->xRandomness(ORIGVFS(pVfs), nByte, zBufOut);
|
||||
}
|
||||
|
||||
/*
|
||||
** Sleep for nMicro microseconds. Return the number of microseconds
|
||||
** actually slept.
|
||||
*/
|
||||
static int memSleep(sqlite3_vfs *pVfs, int nMicro){
|
||||
return ORIGVFS(pVfs)->xSleep(ORIGVFS(pVfs), nMicro);
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the current time as a Julian Day number in *pTimeOut.
|
||||
*/
|
||||
static int memCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
|
||||
return ORIGVFS(pVfs)->xCurrentTime(ORIGVFS(pVfs), pTimeOut);
|
||||
}
|
||||
|
||||
static int memGetLastError(sqlite3_vfs *pVfs, int a, char *b){
|
||||
return ORIGVFS(pVfs)->xGetLastError(ORIGVFS(pVfs), a, b);
|
||||
}
|
||||
static int memCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){
|
||||
return ORIGVFS(pVfs)->xCurrentTimeInt64(ORIGVFS(pVfs), p);
|
||||
}
|
||||
|
||||
#ifdef MEMVFS_TEST
|
||||
/*
|
||||
** memload(FILENAME)
|
||||
**
|
||||
** This an SQL function used to help in testing the memvfs VFS. The
|
||||
** function reads the content of a file into memory and then returns
|
||||
** a string that gives the locate and size of the in-memory buffer.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
static void memvfsMemloadFunc(
|
||||
sqlite3_context *context,
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
){
|
||||
unsigned char *p;
|
||||
sqlite3_int64 sz;
|
||||
FILE *in;
|
||||
const char *zFilename = (const char*)sqlite3_value_text(argv[0]);
|
||||
char zReturn[100];
|
||||
|
||||
if( zFilename==0 ) return;
|
||||
in = fopen(zFilename, "rb");
|
||||
if( in==0 ) return;
|
||||
fseek(in, 0, SEEK_END);
|
||||
sz = ftell(in);
|
||||
rewind(in);
|
||||
p = sqlite3_malloc( sz );
|
||||
if( p==0 ){
|
||||
fclose(in);
|
||||
sqlite3_result_error_nomem(context);
|
||||
return;
|
||||
}
|
||||
fread(p, sz, 1, in);
|
||||
fclose(in);
|
||||
sqlite3_snprintf(sizeof(zReturn),zReturn,"ptr=%lld&sz=%lld",
|
||||
(sqlite3_int64)p, sz);
|
||||
sqlite3_result_text(context, zReturn, -1, SQLITE_TRANSIENT);
|
||||
}
|
||||
/* Called for each new database connection */
|
||||
static int memvfsRegister(
|
||||
sqlite3 *db,
|
||||
const char **pzErrMsg,
|
||||
const struct sqlite3_api_routines *pThunk
|
||||
){
|
||||
return sqlite3_create_function(db, "memload", 1, SQLITE_UTF8, 0,
|
||||
memvfsMemloadFunc, 0, 0);
|
||||
}
|
||||
#endif /* MEMVFS_TEST */
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
/*
|
||||
** This routine is called when the extension is loaded.
|
||||
** Register the new VFS.
|
||||
*/
|
||||
int sqlite3_memvfs_init(
|
||||
sqlite3 *db,
|
||||
char **pzErrMsg,
|
||||
const sqlite3_api_routines *pApi
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
SQLITE_EXTENSION_INIT2(pApi);
|
||||
mem_vfs.pAppData = sqlite3_vfs_find(0);
|
||||
mem_vfs.szOsFile = sizeof(MemFile);
|
||||
rc = sqlite3_vfs_register(&mem_vfs, 1);
|
||||
#ifdef MEMVFS_TEST
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_auto_extension((void(*)(void))memvfsRegister);
|
||||
}
|
||||
#endif
|
||||
if( rc==SQLITE_OK ) rc = SQLITE_OK_LOAD_PERMANENTLY;
|
||||
return rc;
|
||||
}
|
||||
@ -167,7 +167,7 @@ static void percentStep(sqlite3_context *pCtx, int argc, sqlite3_value **argv){
|
||||
/*
|
||||
** Compare to doubles for sorting using qsort()
|
||||
*/
|
||||
static int doubleCmp(const void *pA, const void *pB){
|
||||
static int SQLITE_CDECL doubleCmp(const void *pA, const void *pB){
|
||||
double a = *(double*)pA;
|
||||
double b = *(double*)pB;
|
||||
if( a==b ) return 0;
|
||||
|
||||
@ -136,7 +136,7 @@ struct ReCompiled {
|
||||
static void re_add_state(ReStateSet *pSet, int newState){
|
||||
unsigned i;
|
||||
for(i=0; i<pSet->nState; i++) if( pSet->aState[i]==newState ) return;
|
||||
pSet->aState[pSet->nState++] = newState;
|
||||
pSet->aState[pSet->nState++] = (ReStateNumber)newState;
|
||||
}
|
||||
|
||||
/* Extract the next unicode character from *pzIn and return it. Advance
|
||||
@ -358,7 +358,7 @@ static int re_insert(ReCompiled *p, int iBefore, int op, int arg){
|
||||
p->aArg[i] = p->aArg[i-1];
|
||||
}
|
||||
p->nState++;
|
||||
p->aOp[iBefore] = op;
|
||||
p->aOp[iBefore] = (char)op;
|
||||
p->aArg[iBefore] = arg;
|
||||
return iBefore;
|
||||
}
|
||||
@ -677,12 +677,12 @@ const char *re_compile(ReCompiled **ppRe, const char *zIn, int noCase){
|
||||
for(j=0, i=1; j<sizeof(pRe->zInit)-2 && pRe->aOp[i]==RE_OP_MATCH; i++){
|
||||
unsigned x = pRe->aArg[i];
|
||||
if( x<=127 ){
|
||||
pRe->zInit[j++] = x;
|
||||
pRe->zInit[j++] = (unsigned char)x;
|
||||
}else if( x<=0xfff ){
|
||||
pRe->zInit[j++] = 0xc0 | (x>>6);
|
||||
pRe->zInit[j++] = (unsigned char)(0xc0 | (x>>6));
|
||||
pRe->zInit[j++] = 0x80 | (x&0x3f);
|
||||
}else if( x<=0xffff ){
|
||||
pRe->zInit[j++] = 0xd0 | (x>>12);
|
||||
pRe->zInit[j++] = (unsigned char)(0xd0 | (x>>12));
|
||||
pRe->zInit[j++] = 0x80 | ((x>>6)&0x3f);
|
||||
pRe->zInit[j++] = 0x80 | (x&0x3f);
|
||||
}else{
|
||||
|
||||
610
ext/misc/scrub.c
Normal file
610
ext/misc/scrub.c
Normal file
@ -0,0 +1,610 @@
|
||||
/*
|
||||
** 2016-05-05
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
******************************************************************************
|
||||
**
|
||||
** This file implements a utility function (and a utility program) that
|
||||
** makes a copy of an SQLite database while simultaneously zeroing out all
|
||||
** deleted content.
|
||||
**
|
||||
** Normally (when PRAGMA secure_delete=OFF, which is the default) when SQLite
|
||||
** deletes content, it does not overwrite the deleted content but rather marks
|
||||
** the region of the file that held that content as being reusable. This can
|
||||
** cause deleted content to recoverable from the database file. This stale
|
||||
** content is removed by the VACUUM command, but VACUUM can be expensive for
|
||||
** large databases. When in PRAGMA secure_delete=ON mode, the deleted content
|
||||
** is zeroed, but secure_delete=ON has overhead as well.
|
||||
**
|
||||
** This utility attempts to make a copy of a complete SQLite database where
|
||||
** all of the deleted content is zeroed out in the copy, and it attempts to
|
||||
** do so while being faster than running VACUUM.
|
||||
**
|
||||
** Usage:
|
||||
**
|
||||
** int sqlite3_scrub_backup(
|
||||
** const char *zSourceFile, // Source database filename
|
||||
** const char *zDestFile, // Destination database filename
|
||||
** char **pzErrMsg // Write error message here
|
||||
** );
|
||||
**
|
||||
** Simply call the API above specifying the filename of the source database
|
||||
** and the name of the backup copy. The source database must already exist
|
||||
** and can be in active use. (A read lock is held during the backup.) The
|
||||
** destination file should not previously exist. If the pzErrMsg parameter
|
||||
** is non-NULL and if an error occurs, then an error message might be written
|
||||
** into memory obtained from sqlite3_malloc() and *pzErrMsg made to point to
|
||||
** that error message. But if the error is an OOM, the error might not be
|
||||
** reported. The routine always returns non-zero if there is an error.
|
||||
**
|
||||
** If compiled with -DSCRUB_STANDALONE then a main() procedure is added and
|
||||
** this file becomes a standalone program that can be run as follows:
|
||||
**
|
||||
** ./sqlite3scrub SOURCE DEST
|
||||
*/
|
||||
#include "sqlite3.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct ScrubState ScrubState;
|
||||
typedef unsigned char u8;
|
||||
typedef unsigned short u16;
|
||||
typedef unsigned int u32;
|
||||
|
||||
|
||||
/* State information for a scrub-and-backup operation */
|
||||
struct ScrubState {
|
||||
const char *zSrcFile; /* Name of the source file */
|
||||
const char *zDestFile; /* Name of the destination file */
|
||||
int rcErr; /* Error code */
|
||||
char *zErr; /* Error message text */
|
||||
sqlite3 *dbSrc; /* Source database connection */
|
||||
sqlite3_file *pSrc; /* Source file handle */
|
||||
sqlite3 *dbDest; /* Destination database connection */
|
||||
sqlite3_file *pDest; /* Destination file handle */
|
||||
u32 szPage; /* Page size */
|
||||
u32 szUsable; /* Usable bytes on each page */
|
||||
u32 nPage; /* Number of pages */
|
||||
u32 iLastPage; /* Page number of last page written so far*/
|
||||
u8 *page1; /* Content of page 1 */
|
||||
};
|
||||
|
||||
/* Store an error message */
|
||||
static void scrubBackupErr(ScrubState *p, const char *zFormat, ...){
|
||||
va_list ap;
|
||||
sqlite3_free(p->zErr);
|
||||
va_start(ap, zFormat);
|
||||
p->zErr = sqlite3_vmprintf(zFormat, ap);
|
||||
va_end(ap);
|
||||
if( p->rcErr==0 ) p->rcErr = SQLITE_ERROR;
|
||||
}
|
||||
|
||||
/* Allocate memory to hold a single page of content */
|
||||
static u8 *scrubBackupAllocPage(ScrubState *p){
|
||||
u8 *pPage;
|
||||
if( p->rcErr ) return 0;
|
||||
pPage = sqlite3_malloc( p->szPage );
|
||||
if( pPage==0 ) p->rcErr = SQLITE_NOMEM;
|
||||
return pPage;
|
||||
}
|
||||
|
||||
/* Read a page from the source database into memory. Use the memory
|
||||
** provided by pBuf if not NULL or allocate a new page if pBuf==NULL.
|
||||
*/
|
||||
static u8 *scrubBackupRead(ScrubState *p, int pgno, u8 *pBuf){
|
||||
int rc;
|
||||
sqlite3_int64 iOff;
|
||||
u8 *pOut = pBuf;
|
||||
if( p->rcErr ) return 0;
|
||||
if( pOut==0 ){
|
||||
pOut = scrubBackupAllocPage(p);
|
||||
if( pOut==0 ) return 0;
|
||||
}
|
||||
iOff = (pgno-1)*(sqlite3_int64)p->szPage;
|
||||
rc = p->pSrc->pMethods->xRead(p->pSrc, pOut, p->szPage, iOff);
|
||||
if( rc!=SQLITE_OK ){
|
||||
if( pBuf==0 ) sqlite3_free(pOut);
|
||||
pOut = 0;
|
||||
scrubBackupErr(p, "read failed for page %d", pgno);
|
||||
p->rcErr = SQLITE_IOERR;
|
||||
}
|
||||
return pOut;
|
||||
}
|
||||
|
||||
/* Write a page to the destination database */
|
||||
static void scrubBackupWrite(ScrubState *p, int pgno, const u8 *pData){
|
||||
int rc;
|
||||
sqlite3_int64 iOff;
|
||||
if( p->rcErr ) return;
|
||||
iOff = (pgno-1)*(sqlite3_int64)p->szPage;
|
||||
rc = p->pDest->pMethods->xWrite(p->pDest, pData, p->szPage, iOff);
|
||||
if( rc!=SQLITE_OK ){
|
||||
scrubBackupErr(p, "write failed for page %d", pgno);
|
||||
p->rcErr = SQLITE_IOERR;
|
||||
}
|
||||
if( pgno>p->iLastPage ) p->iLastPage = pgno;
|
||||
}
|
||||
|
||||
/* Prepare a statement against the "db" database. */
|
||||
static sqlite3_stmt *scrubBackupPrepare(
|
||||
ScrubState *p, /* Backup context */
|
||||
sqlite3 *db, /* Database to prepare against */
|
||||
const char *zSql /* SQL statement */
|
||||
){
|
||||
sqlite3_stmt *pStmt;
|
||||
if( p->rcErr ) return 0;
|
||||
p->rcErr = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
|
||||
if( p->rcErr ){
|
||||
scrubBackupErr(p, "SQL error \"%s\" on \"%s\"",
|
||||
sqlite3_errmsg(db), zSql);
|
||||
sqlite3_finalize(pStmt);
|
||||
return 0;
|
||||
}
|
||||
return pStmt;
|
||||
}
|
||||
|
||||
|
||||
/* Open the source database file */
|
||||
static void scrubBackupOpenSrc(ScrubState *p){
|
||||
sqlite3_stmt *pStmt;
|
||||
int rc;
|
||||
/* Open the source database file */
|
||||
p->rcErr = sqlite3_open_v2(p->zSrcFile, &p->dbSrc,
|
||||
SQLITE_OPEN_READWRITE |
|
||||
SQLITE_OPEN_URI | SQLITE_OPEN_PRIVATECACHE, 0);
|
||||
if( p->rcErr ){
|
||||
scrubBackupErr(p, "cannot open source database: %s",
|
||||
sqlite3_errmsg(p->dbSrc));
|
||||
return;
|
||||
}
|
||||
p->rcErr = sqlite3_exec(p->dbSrc, "SELECT 1 FROM sqlite_master; BEGIN;",
|
||||
0, 0, 0);
|
||||
if( p->rcErr ){
|
||||
scrubBackupErr(p,
|
||||
"cannot start a read transaction on the source database: %s",
|
||||
sqlite3_errmsg(p->dbSrc));
|
||||
return;
|
||||
}
|
||||
rc = sqlite3_wal_checkpoint_v2(p->dbSrc, "main", SQLITE_CHECKPOINT_FULL,
|
||||
0, 0);
|
||||
if( rc ){
|
||||
scrubBackupErr(p, "cannot checkpoint the source database");
|
||||
return;
|
||||
}
|
||||
pStmt = scrubBackupPrepare(p, p->dbSrc, "PRAGMA page_size");
|
||||
if( pStmt==0 ) return;
|
||||
rc = sqlite3_step(pStmt);
|
||||
if( rc==SQLITE_ROW ){
|
||||
p->szPage = sqlite3_column_int(pStmt, 0);
|
||||
}else{
|
||||
scrubBackupErr(p, "unable to determine the page size");
|
||||
}
|
||||
sqlite3_finalize(pStmt);
|
||||
if( p->rcErr ) return;
|
||||
pStmt = scrubBackupPrepare(p, p->dbSrc, "PRAGMA page_count");
|
||||
if( pStmt==0 ) return;
|
||||
rc = sqlite3_step(pStmt);
|
||||
if( rc==SQLITE_ROW ){
|
||||
p->nPage = sqlite3_column_int(pStmt, 0);
|
||||
}else{
|
||||
scrubBackupErr(p, "unable to determine the size of the source database");
|
||||
}
|
||||
sqlite3_finalize(pStmt);
|
||||
sqlite3_file_control(p->dbSrc, "main", SQLITE_FCNTL_FILE_POINTER, &p->pSrc);
|
||||
if( p->pSrc==0 || p->pSrc->pMethods==0 ){
|
||||
scrubBackupErr(p, "cannot get the source file handle");
|
||||
p->rcErr = SQLITE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create and open the destination file */
|
||||
static void scrubBackupOpenDest(ScrubState *p){
|
||||
sqlite3_stmt *pStmt;
|
||||
int rc;
|
||||
char *zSql;
|
||||
if( p->rcErr ) return;
|
||||
p->rcErr = sqlite3_open_v2(p->zDestFile, &p->dbDest,
|
||||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
|
||||
SQLITE_OPEN_URI | SQLITE_OPEN_PRIVATECACHE, 0);
|
||||
if( p->rcErr ){
|
||||
scrubBackupErr(p, "cannot open destination database: %s",
|
||||
sqlite3_errmsg(p->dbDest));
|
||||
return;
|
||||
}
|
||||
zSql = sqlite3_mprintf("PRAGMA page_size(%u);", p->szPage);
|
||||
if( zSql==0 ){
|
||||
p->rcErr = SQLITE_NOMEM;
|
||||
return;
|
||||
}
|
||||
p->rcErr = sqlite3_exec(p->dbDest, zSql, 0, 0, 0);
|
||||
sqlite3_free(zSql);
|
||||
if( p->rcErr ){
|
||||
scrubBackupErr(p,
|
||||
"cannot set the page size on the destination database: %s",
|
||||
sqlite3_errmsg(p->dbDest));
|
||||
return;
|
||||
}
|
||||
sqlite3_exec(p->dbDest, "PRAGMA journal_mode=OFF;", 0, 0, 0);
|
||||
p->rcErr = sqlite3_exec(p->dbDest, "BEGIN EXCLUSIVE;", 0, 0, 0);
|
||||
if( p->rcErr ){
|
||||
scrubBackupErr(p,
|
||||
"cannot start a write transaction on the destination database: %s",
|
||||
sqlite3_errmsg(p->dbDest));
|
||||
return;
|
||||
}
|
||||
pStmt = scrubBackupPrepare(p, p->dbDest, "PRAGMA page_count;");
|
||||
if( pStmt==0 ) return;
|
||||
rc = sqlite3_step(pStmt);
|
||||
if( rc!=SQLITE_ROW ){
|
||||
scrubBackupErr(p, "cannot measure the size of the destination");
|
||||
}else if( sqlite3_column_int(pStmt, 0)>1 ){
|
||||
scrubBackupErr(p, "destination database is not empty - holds %d pages",
|
||||
sqlite3_column_int(pStmt, 0));
|
||||
}
|
||||
sqlite3_finalize(pStmt);
|
||||
sqlite3_file_control(p->dbDest, "main", SQLITE_FCNTL_FILE_POINTER, &p->pDest);
|
||||
if( p->pDest==0 || p->pDest->pMethods==0 ){
|
||||
scrubBackupErr(p, "cannot get the destination file handle");
|
||||
p->rcErr = SQLITE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
/* Read a 32-bit big-endian integer */
|
||||
static u32 scrubBackupInt32(const u8 *a){
|
||||
u32 v = a[3];
|
||||
v += ((u32)a[2])<<8;
|
||||
v += ((u32)a[1])<<16;
|
||||
v += ((u32)a[0])<<24;
|
||||
return v;
|
||||
}
|
||||
|
||||
/* Read a 16-bit big-endian integer */
|
||||
static u32 scrubBackupInt16(const u8 *a){
|
||||
return (a[0]<<8) + a[1];
|
||||
}
|
||||
|
||||
/*
|
||||
** Read a varint. Put the value in *pVal and return the number of bytes.
|
||||
*/
|
||||
static int scrubBackupVarint(const u8 *z, sqlite3_int64 *pVal){
|
||||
sqlite3_int64 v = 0;
|
||||
int i;
|
||||
for(i=0; i<8; i++){
|
||||
v = (v<<7) + (z[i]&0x7f);
|
||||
if( (z[i]&0x80)==0 ){ *pVal = v; return i+1; }
|
||||
}
|
||||
v = (v<<8) + (z[i]&0xff);
|
||||
*pVal = v;
|
||||
return 9;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the number of bytes in a varint.
|
||||
*/
|
||||
static int scrubBackupVarintSize(const u8 *z){
|
||||
int i;
|
||||
for(i=0; i<8; i++){
|
||||
if( (z[i]&0x80)==0 ){ return i+1; }
|
||||
}
|
||||
return 9;
|
||||
}
|
||||
|
||||
/*
|
||||
** Copy the freelist trunk page given, and all its descendents,
|
||||
** zeroing out as much as possible in the process.
|
||||
*/
|
||||
static void scrubBackupFreelist(ScrubState *p, int pgno, u32 nFree){
|
||||
u8 *a, *aBuf;
|
||||
u32 n, mx;
|
||||
|
||||
if( p->rcErr ) return;
|
||||
aBuf = scrubBackupAllocPage(p);
|
||||
if( aBuf==0 ) return;
|
||||
|
||||
while( pgno && nFree){
|
||||
a = scrubBackupRead(p, pgno, aBuf);
|
||||
if( a==0 ) break;
|
||||
n = scrubBackupInt32(&a[4]);
|
||||
mx = p->szUsable/4 - 2;
|
||||
if( n<mx ){
|
||||
memset(&a[n*4+8], 0, 4*(mx-n));
|
||||
}
|
||||
scrubBackupWrite(p, pgno, a);
|
||||
pgno = scrubBackupInt32(a);
|
||||
#if 0
|
||||
/* There is really no point in copying the freelist leaf pages.
|
||||
** Simply leave them uninitialized in the destination database. The
|
||||
** OS filesystem should zero those pages for us automatically.
|
||||
*/
|
||||
for(i=0; i<n && nFree; i++){
|
||||
u32 iLeaf = scrubBackupInt32(&a[i*4+8]);
|
||||
if( aZero==0 ){
|
||||
aZero = scrubBackupAllocPage(p);
|
||||
if( aZero==0 ){ pgno = 0; break; }
|
||||
memset(aZero, 0, p->szPage);
|
||||
}
|
||||
scrubBackupWrite(p, iLeaf, aZero);
|
||||
nFree--;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
sqlite3_free(aBuf);
|
||||
}
|
||||
|
||||
/*
|
||||
** Copy an overflow chain from source to destination. Zero out any
|
||||
** unused tail at the end of the overflow chain.
|
||||
*/
|
||||
static void scrubBackupOverflow(ScrubState *p, int pgno, u32 nByte){
|
||||
u8 *a, *aBuf;
|
||||
|
||||
aBuf = scrubBackupAllocPage(p);
|
||||
if( aBuf==0 ) return;
|
||||
while( nByte>0 && pgno!=0 ){
|
||||
a = scrubBackupRead(p, pgno, aBuf);
|
||||
if( a==0 ) break;
|
||||
if( nByte >= (p->szUsable)-4 ){
|
||||
nByte -= (p->szUsable) - 4;
|
||||
}else{
|
||||
u32 x = (p->szUsable - 4) - nByte;
|
||||
u32 i = p->szUsable - x;
|
||||
memset(&a[i], 0, x);
|
||||
nByte = 0;
|
||||
}
|
||||
scrubBackupWrite(p, pgno, a);
|
||||
pgno = scrubBackupInt32(a);
|
||||
}
|
||||
sqlite3_free(aBuf);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Copy B-Tree page pgno, and all of its children, from source to destination.
|
||||
** Zero out deleted content during the copy.
|
||||
*/
|
||||
static void scrubBackupBtree(ScrubState *p, int pgno, int iDepth){
|
||||
u8 *a;
|
||||
u32 i, n, pc;
|
||||
u32 nCell;
|
||||
u32 nPrefix;
|
||||
u32 szHdr;
|
||||
u32 iChild;
|
||||
u8 *aTop;
|
||||
u8 *aCell;
|
||||
u32 x, y;
|
||||
int ln = 0;
|
||||
|
||||
|
||||
if( p->rcErr ) return;
|
||||
if( iDepth>50 ){
|
||||
scrubBackupErr(p, "corrupt: b-tree too deep at page %d", pgno);
|
||||
return;
|
||||
}
|
||||
if( pgno==1 ){
|
||||
a = p->page1;
|
||||
}else{
|
||||
a = scrubBackupRead(p, pgno, 0);
|
||||
if( a==0 ) return;
|
||||
}
|
||||
nPrefix = pgno==1 ? 100 : 0;
|
||||
aTop = &a[nPrefix];
|
||||
szHdr = 8 + 4*(aTop[0]==0x02 || aTop[0]==0x05);
|
||||
aCell = aTop + szHdr;
|
||||
nCell = scrubBackupInt16(&aTop[3]);
|
||||
|
||||
/* Zero out the gap between the cell index and the start of the
|
||||
** cell content area */
|
||||
x = scrubBackupInt16(&aTop[5]); /* First byte of cell content area */
|
||||
if( x>p->szUsable ){ ln=__LINE__; goto btree_corrupt; }
|
||||
y = szHdr + nPrefix + nCell*2;
|
||||
if( y>x ){ ln=__LINE__; goto btree_corrupt; }
|
||||
if( y<x ) memset(a+y, 0, x-y); /* Zero the gap */
|
||||
|
||||
/* Zero out all the free blocks */
|
||||
pc = scrubBackupInt16(&aTop[1]);
|
||||
if( pc>0 && pc<x ){ ln=__LINE__; goto btree_corrupt; }
|
||||
while( pc ){
|
||||
if( pc>(p->szUsable)-4 ){ ln=__LINE__; goto btree_corrupt; }
|
||||
n = scrubBackupInt16(&a[pc+2]);
|
||||
if( pc+n>(p->szUsable) ){ ln=__LINE__; goto btree_corrupt; }
|
||||
if( n>4 ) memset(&a[pc+4], 0, n-4);
|
||||
x = scrubBackupInt16(&a[pc]);
|
||||
if( x<pc+4 && x>0 ){ ln=__LINE__; goto btree_corrupt; }
|
||||
pc = x;
|
||||
}
|
||||
|
||||
/* Write this one page */
|
||||
scrubBackupWrite(p, pgno, a);
|
||||
|
||||
/* Walk the tree and process child pages */
|
||||
for(i=0; i<nCell; i++){
|
||||
u32 X, M, K, nLocal;
|
||||
sqlite3_int64 P;
|
||||
pc = scrubBackupInt16(&aCell[i*2]);
|
||||
if( pc <= szHdr ){ ln=__LINE__; goto btree_corrupt; }
|
||||
if( pc > p->szUsable-3 ){ ln=__LINE__; goto btree_corrupt; }
|
||||
if( aTop[0]==0x05 || aTop[0]==0x02 ){
|
||||
if( pc+4 > p->szUsable ){ ln=__LINE__; goto btree_corrupt; }
|
||||
iChild = scrubBackupInt32(&a[pc]);
|
||||
pc += 4;
|
||||
scrubBackupBtree(p, iChild, iDepth+1);
|
||||
if( aTop[0]==0x05 ) continue;
|
||||
}
|
||||
pc += scrubBackupVarint(&a[pc], &P);
|
||||
if( pc >= p->szUsable ){ ln=__LINE__; goto btree_corrupt; }
|
||||
if( aTop[0]==0x0d ){
|
||||
X = p->szUsable - 35;
|
||||
}else{
|
||||
X = ((p->szUsable - 12)*64/255) - 23;
|
||||
}
|
||||
if( P<=X ){
|
||||
/* All content is local. No overflow */
|
||||
continue;
|
||||
}
|
||||
M = ((p->szUsable - 12)*32/255)-23;
|
||||
K = M + ((P-M)%(p->szUsable-4));
|
||||
if( aTop[0]==0x0d ){
|
||||
pc += scrubBackupVarintSize(&a[pc]);
|
||||
if( pc > (p->szUsable-4) ){ ln=__LINE__; goto btree_corrupt; }
|
||||
}
|
||||
nLocal = K<=X ? K : M;
|
||||
if( pc+nLocal > p->szUsable-4 ){ ln=__LINE__; goto btree_corrupt; }
|
||||
iChild = scrubBackupInt32(&a[pc+nLocal]);
|
||||
scrubBackupOverflow(p, iChild, P-nLocal);
|
||||
}
|
||||
|
||||
/* Walk the right-most tree */
|
||||
if( aTop[0]==0x05 || aTop[0]==0x02 ){
|
||||
iChild = scrubBackupInt32(&aTop[8]);
|
||||
scrubBackupBtree(p, iChild, iDepth+1);
|
||||
}
|
||||
|
||||
/* All done */
|
||||
if( pgno>1 ) sqlite3_free(a);
|
||||
return;
|
||||
|
||||
btree_corrupt:
|
||||
scrubBackupErr(p, "corruption on page %d of source database (errid=%d)",
|
||||
pgno, ln);
|
||||
if( pgno>1 ) sqlite3_free(a);
|
||||
}
|
||||
|
||||
/*
|
||||
** Copy all ptrmap pages from source to destination.
|
||||
** This routine is only called if the source database is in autovacuum
|
||||
** or incremental vacuum mode.
|
||||
*/
|
||||
static void scrubBackupPtrmap(ScrubState *p){
|
||||
u32 pgno = 2;
|
||||
u32 J = p->szUsable/5;
|
||||
u32 iLock = (1073742335/p->szPage)+1;
|
||||
u8 *a, *pBuf;
|
||||
if( p->rcErr ) return;
|
||||
pBuf = scrubBackupAllocPage(p);
|
||||
if( pBuf==0 ) return;
|
||||
while( pgno<=p->nPage ){
|
||||
a = scrubBackupRead(p, pgno, pBuf);
|
||||
if( a==0 ) break;
|
||||
scrubBackupWrite(p, pgno, a);
|
||||
pgno += J+1;
|
||||
if( pgno==iLock ) pgno++;
|
||||
}
|
||||
sqlite3_free(pBuf);
|
||||
}
|
||||
|
||||
int sqlite3_scrub_backup(
|
||||
const char *zSrcFile, /* Source file */
|
||||
const char *zDestFile, /* Destination file */
|
||||
char **pzErr /* Write error here if non-NULL */
|
||||
){
|
||||
ScrubState s;
|
||||
u32 n, i;
|
||||
sqlite3_stmt *pStmt;
|
||||
|
||||
memset(&s, 0, sizeof(s));
|
||||
s.zSrcFile = zSrcFile;
|
||||
s.zDestFile = zDestFile;
|
||||
|
||||
/* Open both source and destination databases */
|
||||
scrubBackupOpenSrc(&s);
|
||||
scrubBackupOpenDest(&s);
|
||||
|
||||
/* Read in page 1 */
|
||||
s.page1 = scrubBackupRead(&s, 1, 0);
|
||||
if( s.page1==0 ) goto scrub_abort;
|
||||
s.szUsable = s.szPage - s.page1[20];
|
||||
|
||||
/* Copy the freelist */
|
||||
n = scrubBackupInt32(&s.page1[36]);
|
||||
i = scrubBackupInt32(&s.page1[32]);
|
||||
if( n ) scrubBackupFreelist(&s, i, n);
|
||||
|
||||
/* Copy ptrmap pages */
|
||||
n = scrubBackupInt32(&s.page1[52]);
|
||||
if( n ) scrubBackupPtrmap(&s);
|
||||
|
||||
/* Copy all of the btrees */
|
||||
scrubBackupBtree(&s, 1, 0);
|
||||
pStmt = scrubBackupPrepare(&s, s.dbSrc,
|
||||
"SELECT rootpage FROM sqlite_master WHERE coalesce(rootpage,0)>0");
|
||||
if( pStmt==0 ) goto scrub_abort;
|
||||
while( sqlite3_step(pStmt)==SQLITE_ROW ){
|
||||
i = (u32)sqlite3_column_int(pStmt, 0);
|
||||
scrubBackupBtree(&s, i, 0);
|
||||
}
|
||||
sqlite3_finalize(pStmt);
|
||||
|
||||
/* If the last page of the input db file is a free-list leaf, then the
|
||||
** backup file on disk is still smaller than the size indicated within
|
||||
** the database header. In this case, write a page of zeroes to the
|
||||
** last page of the backup database so that SQLite does not mistakenly
|
||||
** think the db is corrupt. */
|
||||
if( s.iLastPage<s.nPage ){
|
||||
u8 *aZero = scrubBackupAllocPage(&s);
|
||||
if( aZero ){
|
||||
memset(aZero, 0, s.szPage);
|
||||
scrubBackupWrite(&s, s.nPage, aZero);
|
||||
sqlite3_free(aZero);
|
||||
}
|
||||
}
|
||||
|
||||
scrub_abort:
|
||||
/* Close the destination database without closing the transaction. If we
|
||||
** commit, page zero will be overwritten. */
|
||||
sqlite3_close(s.dbDest);
|
||||
|
||||
/* But do close out the read-transaction on the source database */
|
||||
sqlite3_exec(s.dbSrc, "COMMIT;", 0, 0, 0);
|
||||
sqlite3_close(s.dbSrc);
|
||||
sqlite3_free(s.page1);
|
||||
if( pzErr ){
|
||||
*pzErr = s.zErr;
|
||||
}else{
|
||||
sqlite3_free(s.zErr);
|
||||
}
|
||||
return s.rcErr;
|
||||
}
|
||||
|
||||
#ifdef SCRUB_STANDALONE
|
||||
/* Error and warning log */
|
||||
static void errorLogCallback(void *pNotUsed, int iErr, const char *zMsg){
|
||||
const char *zType;
|
||||
switch( iErr&0xff ){
|
||||
case SQLITE_WARNING: zType = "WARNING"; break;
|
||||
case SQLITE_NOTICE: zType = "NOTICE"; break;
|
||||
default: zType = "ERROR"; break;
|
||||
}
|
||||
fprintf(stderr, "%s: %s\n", zType, zMsg);
|
||||
}
|
||||
|
||||
/* The main() routine when this utility is run as a stand-alone program */
|
||||
int main(int argc, char **argv){
|
||||
char *zErr = 0;
|
||||
int rc;
|
||||
if( argc!=3 ){
|
||||
fprintf(stderr,"Usage: %s SOURCE DESTINATION\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
sqlite3_config(SQLITE_CONFIG_LOG, errorLogCallback, 0);
|
||||
rc = sqlite3_scrub_backup(argv[1], argv[2], &zErr);
|
||||
if( rc==SQLITE_NOMEM ){
|
||||
fprintf(stderr, "%s: out of memory\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
if( zErr ){
|
||||
fprintf(stderr, "%s: %s\n", argv[0], zErr);
|
||||
sqlite3_free(zErr);
|
||||
exit(1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@ -217,6 +217,14 @@ static int seriesEof(sqlite3_vtab_cursor *cur){
|
||||
}
|
||||
}
|
||||
|
||||
/* True to cause run-time checking of the start=, stop=, and/or step=
|
||||
** parameters. The only reason to do this is for testing the
|
||||
** constraint checking logic for virtual tables in the SQLite core.
|
||||
*/
|
||||
#ifndef SQLITE_SERIES_CONSTRAINT_VERIFY
|
||||
# define SQLITE_SERIES_CONSTRAINT_VERIFY 0
|
||||
#endif
|
||||
|
||||
/*
|
||||
** This method is called to "rewind" the series_cursor object back
|
||||
** to the first row of output. This method is always called at least
|
||||
@ -324,20 +332,20 @@ static int seriesBestIndex(
|
||||
}
|
||||
if( startIdx>=0 ){
|
||||
pIdxInfo->aConstraintUsage[startIdx].argvIndex = ++nArg;
|
||||
pIdxInfo->aConstraintUsage[startIdx].omit = 1;
|
||||
pIdxInfo->aConstraintUsage[startIdx].omit= !SQLITE_SERIES_CONSTRAINT_VERIFY;
|
||||
}
|
||||
if( stopIdx>=0 ){
|
||||
pIdxInfo->aConstraintUsage[stopIdx].argvIndex = ++nArg;
|
||||
pIdxInfo->aConstraintUsage[stopIdx].omit = 1;
|
||||
pIdxInfo->aConstraintUsage[stopIdx].omit = !SQLITE_SERIES_CONSTRAINT_VERIFY;
|
||||
}
|
||||
if( stepIdx>=0 ){
|
||||
pIdxInfo->aConstraintUsage[stepIdx].argvIndex = ++nArg;
|
||||
pIdxInfo->aConstraintUsage[stepIdx].omit = 1;
|
||||
pIdxInfo->aConstraintUsage[stepIdx].omit = !SQLITE_SERIES_CONSTRAINT_VERIFY;
|
||||
}
|
||||
if( (idxNum & 3)==3 ){
|
||||
/* Both start= and stop= boundaries are available. This is the
|
||||
** the preferred case */
|
||||
pIdxInfo->estimatedCost = (double)1;
|
||||
pIdxInfo->estimatedCost = (double)(2 - ((idxNum&4)!=0));
|
||||
pIdxInfo->estimatedRows = 1000;
|
||||
if( pIdxInfo->nOrderBy==1 ){
|
||||
if( pIdxInfo->aOrderBy[0].desc ) idxNum |= 8;
|
||||
|
||||
@ -390,7 +390,7 @@ static int editdist1(const char *zA, const char *zB, int *pnMatch){
|
||||
|
||||
/* Special processing if either string is empty */
|
||||
if( nA==0 ){
|
||||
cBprev = dc;
|
||||
cBprev = (char)dc;
|
||||
for(xB=res=0; (cB = zB[xB])!=0; xB++){
|
||||
res += insertOrDeleteCost(cBprev, cB, zB[xB+1])/FINAL_INS_COST_DIV;
|
||||
cBprev = cB;
|
||||
@ -398,7 +398,7 @@ static int editdist1(const char *zA, const char *zB, int *pnMatch){
|
||||
return res;
|
||||
}
|
||||
if( nB==0 ){
|
||||
cAprev = dc;
|
||||
cAprev = (char)dc;
|
||||
for(xA=res=0; (cA = zA[xA])!=0; xA++){
|
||||
res += insertOrDeleteCost(cAprev, cA, zA[xA+1]);
|
||||
cAprev = cA;
|
||||
@ -420,8 +420,8 @@ static int editdist1(const char *zA, const char *zB, int *pnMatch){
|
||||
|
||||
/* Compute the Wagner edit distance */
|
||||
m[0] = 0;
|
||||
cx[0] = dc;
|
||||
cBprev = dc;
|
||||
cx[0] = (char)dc;
|
||||
cBprev = (char)dc;
|
||||
for(xB=1; xB<=nB; xB++){
|
||||
cBnext = zB[xB];
|
||||
cB = zB[xB-1];
|
||||
@ -429,7 +429,7 @@ static int editdist1(const char *zA, const char *zB, int *pnMatch){
|
||||
m[xB] = m[xB-1] + insertOrDeleteCost(cBprev, cB, cBnext);
|
||||
cBprev = cB;
|
||||
}
|
||||
cAprev = dc;
|
||||
cAprev = (char)dc;
|
||||
for(xA=1; xA<=nA; xA++){
|
||||
int lastA = (xA==nA);
|
||||
cA = zA[xA-1];
|
||||
@ -476,7 +476,7 @@ static int editdist1(const char *zA, const char *zB, int *pnMatch){
|
||||
d = m[xB];
|
||||
dc = cx[xB];
|
||||
m[xB] = totalCost;
|
||||
cx[xB] = ncx;
|
||||
cx[xB] = (char)ncx;
|
||||
cBprev = cB;
|
||||
}
|
||||
cAprev = cA;
|
||||
@ -711,9 +711,9 @@ static int editDist3ConfigLoad(
|
||||
if( nExtra<0 ) nExtra = 0;
|
||||
pCost = sqlite3_malloc64( sizeof(*pCost) + nExtra );
|
||||
if( pCost==0 ){ rc = SQLITE_NOMEM; break; }
|
||||
pCost->nFrom = nFrom;
|
||||
pCost->nTo = nTo;
|
||||
pCost->iCost = iCost;
|
||||
pCost->nFrom = (u8)nFrom;
|
||||
pCost->nTo = (u8)nTo;
|
||||
pCost->iCost = (u16)iCost;
|
||||
memcpy(pCost->a, zFrom, nFrom);
|
||||
memcpy(pCost->a + nFrom, zTo, nTo);
|
||||
pCost->pNext = pLang->pCost;
|
||||
@ -1616,7 +1616,7 @@ static unsigned char *transliterate(const unsigned char *zIn, int nIn){
|
||||
zIn += sz;
|
||||
nIn -= sz;
|
||||
if( c<=127 ){
|
||||
zOut[nOut++] = c;
|
||||
zOut[nOut++] = (unsigned char)c;
|
||||
}else{
|
||||
int xTop, xBtm, x;
|
||||
xTop = sizeof(translit)/sizeof(translit[0]) - 1;
|
||||
@ -1734,6 +1734,7 @@ static void scriptCodeSqlFunc(
|
||||
int c, sz;
|
||||
int scriptMask = 0;
|
||||
int res;
|
||||
int seenDigit = 0;
|
||||
# define SCRIPT_LATIN 0x0001
|
||||
# define SCRIPT_CYRILLIC 0x0002
|
||||
# define SCRIPT_GREEK 0x0004
|
||||
@ -1744,8 +1745,12 @@ static void scriptCodeSqlFunc(
|
||||
c = utf8Read(zIn, nIn, &sz);
|
||||
zIn += sz;
|
||||
nIn -= sz;
|
||||
if( c<0x02af && (c>=0x80 || midClass[c&0x7f]<CCLASS_DIGIT) ){
|
||||
scriptMask |= SCRIPT_LATIN;
|
||||
if( c<0x02af ){
|
||||
if( c>=0x80 || midClass[c&0x7f]<CCLASS_DIGIT ){
|
||||
scriptMask |= SCRIPT_LATIN;
|
||||
}else if( c>='0' && c<='9' ){
|
||||
seenDigit = 1;
|
||||
}
|
||||
}else if( c>=0x0400 && c<=0x04ff ){
|
||||
scriptMask |= SCRIPT_CYRILLIC;
|
||||
}else if( c>=0x0386 && c<=0x03ce ){
|
||||
@ -1756,6 +1761,7 @@ static void scriptCodeSqlFunc(
|
||||
scriptMask |= SCRIPT_ARABIC;
|
||||
}
|
||||
}
|
||||
if( scriptMask==0 && seenDigit ) scriptMask = SCRIPT_LATIN;
|
||||
switch( scriptMask ){
|
||||
case 0: res = 999; break;
|
||||
case SCRIPT_LATIN: res = 215; break;
|
||||
@ -1775,7 +1781,7 @@ static void scriptCodeSqlFunc(
|
||||
*/
|
||||
|
||||
/* Maximum length of a phonehash used for querying the shadow table */
|
||||
#define SPELLFIX_MX_HASH 8
|
||||
#define SPELLFIX_MX_HASH 32
|
||||
|
||||
/* Maximum number of hash strings to examine per query */
|
||||
#define SPELLFIX_MX_RUN 1
|
||||
@ -2225,7 +2231,7 @@ static int spellfix1Score(int iDistance, int iRank){
|
||||
** Compare two spellfix1_row objects for sorting purposes in qsort() such
|
||||
** that they sort in order of increasing distance.
|
||||
*/
|
||||
static int spellfix1RowCompare(const void *A, const void *B){
|
||||
static int SQLITE_CDECL spellfix1RowCompare(const void *A, const void *B){
|
||||
const struct spellfix1_row *a = (const struct spellfix1_row*)A;
|
||||
const struct spellfix1_row *b = (const struct spellfix1_row*)B;
|
||||
return a->iScore - b->iScore;
|
||||
|
||||
816
ext/misc/vfsstat.c
Normal file
816
ext/misc/vfsstat.c
Normal file
@ -0,0 +1,816 @@
|
||||
/*
|
||||
** 2016-05-27
|
||||
**
|
||||
** 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 contains the implementation of an SQLite vfs shim that
|
||||
** tracks I/O. Access to the accumulated status counts is provided using
|
||||
** an eponymous virtual table.
|
||||
*/
|
||||
#include <sqlite3ext.h>
|
||||
SQLITE_EXTENSION_INIT1
|
||||
|
||||
/*
|
||||
** This module contains code for a wrapper VFS that cause stats for
|
||||
** most VFS calls to be recorded.
|
||||
**
|
||||
** To use this module, first compile it as a loadable extension. See
|
||||
** https://www.sqlite.org/loadext.html#build for compilations instructions.
|
||||
**
|
||||
** After compliing, load this extension, then open database connections to be
|
||||
** measured. Query usages status using the vfsstat virtual table:
|
||||
**
|
||||
** SELECT * FROM vfsstat;
|
||||
**
|
||||
** Reset counters using UPDATE statements against vfsstat:
|
||||
**
|
||||
** UPDATE vfsstat SET count=0;
|
||||
**
|
||||
** EXAMPLE SCRIPT:
|
||||
**
|
||||
** .load ./vfsstat
|
||||
** .open test.db
|
||||
** DROP TABLE IF EXISTS t1;
|
||||
** CREATE TABLE t1(x,y);
|
||||
** INSERT INTO t1 VALUES(123, randomblob(5000));
|
||||
** CREATE INDEX t1x ON t1(x);
|
||||
** DROP TABLE t1;
|
||||
** VACUUM;
|
||||
** SELECT * FROM vfsstat WHERE count>0;
|
||||
**
|
||||
** LIMITATIONS:
|
||||
**
|
||||
** This module increments counters without using mutex protection. So if
|
||||
** two or more threads try to use this module at the same time, race conditions
|
||||
** may occur which mess up the counts. This is harmless, other than giving
|
||||
** incorrect statistics.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
/*
|
||||
** File types
|
||||
*/
|
||||
#define VFSSTAT_MAIN 0 /* Main database file */
|
||||
#define VFSSTAT_JOURNAL 1 /* Rollback journal */
|
||||
#define VFSSTAT_WAL 2 /* Write-ahead log file */
|
||||
#define VFSSTAT_MASTERJRNL 3 /* Master journal */
|
||||
#define VFSSTAT_SUBJRNL 4 /* Subjournal */
|
||||
#define VFSSTAT_TEMPDB 5 /* TEMP database */
|
||||
#define VFSSTAT_TEMPJRNL 6 /* Journal for TEMP database */
|
||||
#define VFSSTAT_TRANSIENT 7 /* Transient database */
|
||||
#define VFSSTAT_ANY 8 /* Unspecified file type */
|
||||
#define VFSSTAT_nFile 9 /* This many file types */
|
||||
|
||||
/* Names of the file types. These are allowed values for the
|
||||
** first column of the vfsstat virtual table.
|
||||
*/
|
||||
static const char *azFile[] = {
|
||||
"database", "journal", "wal", "master-journal", "sub-journal",
|
||||
"temp-database", "temp-journal", "transient-db", "*"
|
||||
};
|
||||
|
||||
/*
|
||||
** Stat types
|
||||
*/
|
||||
#define VFSSTAT_BYTESIN 0 /* Bytes read in */
|
||||
#define VFSSTAT_BYTESOUT 1 /* Bytes written out */
|
||||
#define VFSSTAT_READ 2 /* Read requests */
|
||||
#define VFSSTAT_WRITE 3 /* Write requests */
|
||||
#define VFSSTAT_SYNC 4 /* Syncs */
|
||||
#define VFSSTAT_OPEN 5 /* File opens */
|
||||
#define VFSSTAT_LOCK 6 /* Lock requests */
|
||||
#define VFSSTAT_ACCESS 0 /* xAccess calls. filetype==ANY only */
|
||||
#define VFSSTAT_DELETE 1 /* xDelete calls. filetype==ANY only */
|
||||
#define VFSSTAT_FULLPATH 2 /* xFullPathname calls. ANY only */
|
||||
#define VFSSTAT_RANDOM 3 /* xRandomness calls. ANY only */
|
||||
#define VFSSTAT_SLEEP 4 /* xSleep calls. ANY only */
|
||||
#define VFSSTAT_CURTIME 5 /* xCurrentTime calls. ANY only */
|
||||
#define VFSSTAT_nStat 7 /* This many stat types */
|
||||
|
||||
|
||||
/* Names for the second column of the vfsstat virtual table for all
|
||||
** cases except when the first column is "*" or VFSSTAT_ANY. */
|
||||
static const char *azStat[] = {
|
||||
"bytes-in", "bytes-out", "read", "write", "sync", "open", "lock",
|
||||
};
|
||||
static const char *azStatAny[] = {
|
||||
"access", "delete", "fullpathname", "randomness", "sleep", "currenttimestamp",
|
||||
"not-used"
|
||||
};
|
||||
|
||||
/* Total number of counters */
|
||||
#define VFSSTAT_MXCNT (VFSSTAT_nStat*VFSSTAT_nFile)
|
||||
|
||||
/*
|
||||
** Performance stats are collected in an instance of the following
|
||||
** global array.
|
||||
*/
|
||||
static sqlite3_uint64 aVfsCnt[VFSSTAT_MXCNT];
|
||||
|
||||
/*
|
||||
** Access to a specific counter
|
||||
*/
|
||||
#define STATCNT(filetype,stat) (aVfsCnt[(filetype)*VFSSTAT_nStat+(stat)])
|
||||
|
||||
/*
|
||||
** Forward declaration of objects used by this utility
|
||||
*/
|
||||
typedef struct VStatVfs VStatVfs;
|
||||
typedef struct VStatFile VStatFile;
|
||||
|
||||
/* An instance of the VFS */
|
||||
struct VStatVfs {
|
||||
sqlite3_vfs base; /* VFS methods */
|
||||
sqlite3_vfs *pVfs; /* Parent VFS */
|
||||
};
|
||||
|
||||
/* An open file */
|
||||
struct VStatFile {
|
||||
sqlite3_file base; /* IO methods */
|
||||
sqlite3_file *pReal; /* Underlying file handle */
|
||||
unsigned char eFiletype; /* What type of file is this */
|
||||
};
|
||||
|
||||
#define REALVFS(p) (((VStatVfs*)(p))->pVfs)
|
||||
|
||||
/*
|
||||
** Methods for VStatFile
|
||||
*/
|
||||
static int vstatClose(sqlite3_file*);
|
||||
static int vstatRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
|
||||
static int vstatWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64 iOfst);
|
||||
static int vstatTruncate(sqlite3_file*, sqlite3_int64 size);
|
||||
static int vstatSync(sqlite3_file*, int flags);
|
||||
static int vstatFileSize(sqlite3_file*, sqlite3_int64 *pSize);
|
||||
static int vstatLock(sqlite3_file*, int);
|
||||
static int vstatUnlock(sqlite3_file*, int);
|
||||
static int vstatCheckReservedLock(sqlite3_file*, int *pResOut);
|
||||
static int vstatFileControl(sqlite3_file*, int op, void *pArg);
|
||||
static int vstatSectorSize(sqlite3_file*);
|
||||
static int vstatDeviceCharacteristics(sqlite3_file*);
|
||||
static int vstatShmMap(sqlite3_file*, int iPg, int pgsz, int, void volatile**);
|
||||
static int vstatShmLock(sqlite3_file*, int offset, int n, int flags);
|
||||
static void vstatShmBarrier(sqlite3_file*);
|
||||
static int vstatShmUnmap(sqlite3_file*, int deleteFlag);
|
||||
static int vstatFetch(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp);
|
||||
static int vstatUnfetch(sqlite3_file*, sqlite3_int64 iOfst, void *p);
|
||||
|
||||
/*
|
||||
** Methods for VStatVfs
|
||||
*/
|
||||
static int vstatOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *);
|
||||
static int vstatDelete(sqlite3_vfs*, const char *zName, int syncDir);
|
||||
static int vstatAccess(sqlite3_vfs*, const char *zName, int flags, int *);
|
||||
static int vstatFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut);
|
||||
static void *vstatDlOpen(sqlite3_vfs*, const char *zFilename);
|
||||
static void vstatDlError(sqlite3_vfs*, int nByte, char *zErrMsg);
|
||||
static void (*vstatDlSym(sqlite3_vfs *pVfs, void *p, const char*zSym))(void);
|
||||
static void vstatDlClose(sqlite3_vfs*, void*);
|
||||
static int vstatRandomness(sqlite3_vfs*, int nByte, char *zOut);
|
||||
static int vstatSleep(sqlite3_vfs*, int microseconds);
|
||||
static int vstatCurrentTime(sqlite3_vfs*, double*);
|
||||
static int vstatGetLastError(sqlite3_vfs*, int, char *);
|
||||
static int vstatCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*);
|
||||
|
||||
static VStatVfs vstat_vfs = {
|
||||
{
|
||||
2, /* iVersion */
|
||||
0, /* szOsFile (set by register_vstat()) */
|
||||
1024, /* mxPathname */
|
||||
0, /* pNext */
|
||||
"vfslog", /* zName */
|
||||
0, /* pAppData */
|
||||
vstatOpen, /* xOpen */
|
||||
vstatDelete, /* xDelete */
|
||||
vstatAccess, /* xAccess */
|
||||
vstatFullPathname, /* xFullPathname */
|
||||
vstatDlOpen, /* xDlOpen */
|
||||
vstatDlError, /* xDlError */
|
||||
vstatDlSym, /* xDlSym */
|
||||
vstatDlClose, /* xDlClose */
|
||||
vstatRandomness, /* xRandomness */
|
||||
vstatSleep, /* xSleep */
|
||||
vstatCurrentTime, /* xCurrentTime */
|
||||
vstatGetLastError, /* xGetLastError */
|
||||
vstatCurrentTimeInt64 /* xCurrentTimeInt64 */
|
||||
},
|
||||
0
|
||||
};
|
||||
|
||||
static const sqlite3_io_methods vstat_io_methods = {
|
||||
3, /* iVersion */
|
||||
vstatClose, /* xClose */
|
||||
vstatRead, /* xRead */
|
||||
vstatWrite, /* xWrite */
|
||||
vstatTruncate, /* xTruncate */
|
||||
vstatSync, /* xSync */
|
||||
vstatFileSize, /* xFileSize */
|
||||
vstatLock, /* xLock */
|
||||
vstatUnlock, /* xUnlock */
|
||||
vstatCheckReservedLock, /* xCheckReservedLock */
|
||||
vstatFileControl, /* xFileControl */
|
||||
vstatSectorSize, /* xSectorSize */
|
||||
vstatDeviceCharacteristics, /* xDeviceCharacteristics */
|
||||
vstatShmMap, /* xShmMap */
|
||||
vstatShmLock, /* xShmLock */
|
||||
vstatShmBarrier, /* xShmBarrier */
|
||||
vstatShmUnmap, /* xShmUnmap */
|
||||
vstatFetch, /* xFetch */
|
||||
vstatUnfetch /* xUnfetch */
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** Close an vstat-file.
|
||||
*/
|
||||
static int vstatClose(sqlite3_file *pFile){
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
if( p->pReal->pMethods ){
|
||||
rc = p->pReal->pMethods->xClose(p->pReal);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Read data from an vstat-file.
|
||||
*/
|
||||
static int vstatRead(
|
||||
sqlite3_file *pFile,
|
||||
void *zBuf,
|
||||
int iAmt,
|
||||
sqlite_int64 iOfst
|
||||
){
|
||||
int rc;
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
|
||||
rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst);
|
||||
STATCNT(p->eFiletype,VFSSTAT_READ)++;
|
||||
if( rc==SQLITE_OK ){
|
||||
STATCNT(p->eFiletype,VFSSTAT_BYTESIN) += iAmt;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Write data to an vstat-file.
|
||||
*/
|
||||
static int vstatWrite(
|
||||
sqlite3_file *pFile,
|
||||
const void *z,
|
||||
int iAmt,
|
||||
sqlite_int64 iOfst
|
||||
){
|
||||
int rc;
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
|
||||
rc = p->pReal->pMethods->xWrite(p->pReal, z, iAmt, iOfst);
|
||||
STATCNT(p->eFiletype,VFSSTAT_WRITE)++;
|
||||
if( rc==SQLITE_OK ){
|
||||
STATCNT(p->eFiletype,VFSSTAT_BYTESOUT) += iAmt;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Truncate an vstat-file.
|
||||
*/
|
||||
static int vstatTruncate(sqlite3_file *pFile, sqlite_int64 size){
|
||||
int rc;
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
rc = p->pReal->pMethods->xTruncate(p->pReal, size);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Sync an vstat-file.
|
||||
*/
|
||||
static int vstatSync(sqlite3_file *pFile, int flags){
|
||||
int rc;
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
rc = p->pReal->pMethods->xSync(p->pReal, flags);
|
||||
STATCNT(p->eFiletype,VFSSTAT_SYNC)++;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the current file-size of an vstat-file.
|
||||
*/
|
||||
static int vstatFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
|
||||
int rc;
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
rc = p->pReal->pMethods->xFileSize(p->pReal, pSize);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Lock an vstat-file.
|
||||
*/
|
||||
static int vstatLock(sqlite3_file *pFile, int eLock){
|
||||
int rc;
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
rc = p->pReal->pMethods->xLock(p->pReal, eLock);
|
||||
STATCNT(p->eFiletype,VFSSTAT_LOCK)++;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Unlock an vstat-file.
|
||||
*/
|
||||
static int vstatUnlock(sqlite3_file *pFile, int eLock){
|
||||
int rc;
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
rc = p->pReal->pMethods->xUnlock(p->pReal, eLock);
|
||||
STATCNT(p->eFiletype,VFSSTAT_LOCK)++;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Check if another file-handle holds a RESERVED lock on an vstat-file.
|
||||
*/
|
||||
static int vstatCheckReservedLock(sqlite3_file *pFile, int *pResOut){
|
||||
int rc;
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
rc = p->pReal->pMethods->xCheckReservedLock(p->pReal, pResOut);
|
||||
STATCNT(p->eFiletype,VFSSTAT_LOCK)++;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** File control method. For custom operations on an vstat-file.
|
||||
*/
|
||||
static int vstatFileControl(sqlite3_file *pFile, int op, void *pArg){
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
int rc;
|
||||
rc = p->pReal->pMethods->xFileControl(p->pReal, op, pArg);
|
||||
if( op==SQLITE_FCNTL_VFSNAME && rc==SQLITE_OK ){
|
||||
*(char**)pArg = sqlite3_mprintf("vstat/%z", *(char**)pArg);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the sector-size in bytes for an vstat-file.
|
||||
*/
|
||||
static int vstatSectorSize(sqlite3_file *pFile){
|
||||
int rc;
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
rc = p->pReal->pMethods->xSectorSize(p->pReal);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the device characteristic flags supported by an vstat-file.
|
||||
*/
|
||||
static int vstatDeviceCharacteristics(sqlite3_file *pFile){
|
||||
int rc;
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
rc = p->pReal->pMethods->xDeviceCharacteristics(p->pReal);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Create a shared memory file mapping */
|
||||
static int vstatShmMap(
|
||||
sqlite3_file *pFile,
|
||||
int iPg,
|
||||
int pgsz,
|
||||
int bExtend,
|
||||
void volatile **pp
|
||||
){
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
return p->pReal->pMethods->xShmMap(p->pReal, iPg, pgsz, bExtend, pp);
|
||||
}
|
||||
|
||||
/* Perform locking on a shared-memory segment */
|
||||
static int vstatShmLock(sqlite3_file *pFile, int offset, int n, int flags){
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
return p->pReal->pMethods->xShmLock(p->pReal, offset, n, flags);
|
||||
}
|
||||
|
||||
/* Memory barrier operation on shared memory */
|
||||
static void vstatShmBarrier(sqlite3_file *pFile){
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
p->pReal->pMethods->xShmBarrier(p->pReal);
|
||||
}
|
||||
|
||||
/* Unmap a shared memory segment */
|
||||
static int vstatShmUnmap(sqlite3_file *pFile, int deleteFlag){
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
return p->pReal->pMethods->xShmUnmap(p->pReal, deleteFlag);
|
||||
}
|
||||
|
||||
/* Fetch a page of a memory-mapped file */
|
||||
static int vstatFetch(
|
||||
sqlite3_file *pFile,
|
||||
sqlite3_int64 iOfst,
|
||||
int iAmt,
|
||||
void **pp
|
||||
){
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
return p->pReal->pMethods->xFetch(p->pReal, iOfst, iAmt, pp);
|
||||
}
|
||||
|
||||
/* Release a memory-mapped page */
|
||||
static int vstatUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
return p->pReal->pMethods->xUnfetch(p->pReal, iOfst, pPage);
|
||||
}
|
||||
|
||||
/*
|
||||
** Open an vstat file handle.
|
||||
*/
|
||||
static int vstatOpen(
|
||||
sqlite3_vfs *pVfs,
|
||||
const char *zName,
|
||||
sqlite3_file *pFile,
|
||||
int flags,
|
||||
int *pOutFlags
|
||||
){
|
||||
int rc;
|
||||
VStatFile *p = (VStatFile*)pFile;
|
||||
|
||||
p->pReal = (sqlite3_file*)&p[1];
|
||||
rc = REALVFS(pVfs)->xOpen(REALVFS(pVfs), zName, p->pReal, flags, pOutFlags);
|
||||
if( flags & SQLITE_OPEN_MAIN_DB ){
|
||||
p->eFiletype = VFSSTAT_MAIN;
|
||||
}else if( flags & SQLITE_OPEN_MAIN_JOURNAL ){
|
||||
p->eFiletype = VFSSTAT_JOURNAL;
|
||||
}else if( flags & SQLITE_OPEN_WAL ){
|
||||
p->eFiletype = VFSSTAT_WAL;
|
||||
}else if( flags & SQLITE_OPEN_MASTER_JOURNAL ){
|
||||
p->eFiletype = VFSSTAT_MASTERJRNL;
|
||||
}else if( flags & SQLITE_OPEN_SUBJOURNAL ){
|
||||
p->eFiletype = VFSSTAT_SUBJRNL;
|
||||
}else if( flags & SQLITE_OPEN_TEMP_DB ){
|
||||
p->eFiletype = VFSSTAT_TEMPDB;
|
||||
}else if( flags & SQLITE_OPEN_TEMP_JOURNAL ){
|
||||
p->eFiletype = VFSSTAT_TEMPJRNL;
|
||||
}else{
|
||||
p->eFiletype = VFSSTAT_TRANSIENT;
|
||||
}
|
||||
STATCNT(p->eFiletype,VFSSTAT_OPEN)++;
|
||||
pFile->pMethods = rc ? 0 : &vstat_io_methods;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Delete the file located at zPath. If the dirSync argument is true,
|
||||
** ensure the file-system modifications are synced to disk before
|
||||
** returning.
|
||||
*/
|
||||
static int vstatDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
|
||||
int rc;
|
||||
rc = REALVFS(pVfs)->xDelete(REALVFS(pVfs), zPath, dirSync);
|
||||
STATCNT(VFSSTAT_ANY,VFSSTAT_DELETE)++;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Test for access permissions. Return true if the requested permission
|
||||
** is available, or false otherwise.
|
||||
*/
|
||||
static int vstatAccess(
|
||||
sqlite3_vfs *pVfs,
|
||||
const char *zPath,
|
||||
int flags,
|
||||
int *pResOut
|
||||
){
|
||||
int rc;
|
||||
rc = REALVFS(pVfs)->xAccess(REALVFS(pVfs), zPath, flags, pResOut);
|
||||
STATCNT(VFSSTAT_ANY,VFSSTAT_ACCESS)++;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Populate buffer zOut with the full canonical pathname corresponding
|
||||
** to the pathname in zPath. zOut is guaranteed to point to a buffer
|
||||
** of at least (INST_MAX_PATHNAME+1) bytes.
|
||||
*/
|
||||
static int vstatFullPathname(
|
||||
sqlite3_vfs *pVfs,
|
||||
const char *zPath,
|
||||
int nOut,
|
||||
char *zOut
|
||||
){
|
||||
STATCNT(VFSSTAT_ANY,VFSSTAT_FULLPATH)++;
|
||||
return REALVFS(pVfs)->xFullPathname(REALVFS(pVfs), zPath, nOut, zOut);
|
||||
}
|
||||
|
||||
/*
|
||||
** Open the dynamic library located at zPath and return a handle.
|
||||
*/
|
||||
static void *vstatDlOpen(sqlite3_vfs *pVfs, const char *zPath){
|
||||
return REALVFS(pVfs)->xDlOpen(REALVFS(pVfs), zPath);
|
||||
}
|
||||
|
||||
/*
|
||||
** Populate the buffer zErrMsg (size nByte bytes) with a human readable
|
||||
** utf-8 string describing the most recent error encountered associated
|
||||
** with dynamic libraries.
|
||||
*/
|
||||
static void vstatDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
|
||||
REALVFS(pVfs)->xDlError(REALVFS(pVfs), nByte, zErrMsg);
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a pointer to the symbol zSymbol in the dynamic library pHandle.
|
||||
*/
|
||||
static void (*vstatDlSym(sqlite3_vfs *pVfs, void *p, const char *zSym))(void){
|
||||
return REALVFS(pVfs)->xDlSym(REALVFS(pVfs), p, zSym);
|
||||
}
|
||||
|
||||
/*
|
||||
** Close the dynamic library handle pHandle.
|
||||
*/
|
||||
static void vstatDlClose(sqlite3_vfs *pVfs, void *pHandle){
|
||||
REALVFS(pVfs)->xDlClose(REALVFS(pVfs), pHandle);
|
||||
}
|
||||
|
||||
/*
|
||||
** Populate the buffer pointed to by zBufOut with nByte bytes of
|
||||
** random data.
|
||||
*/
|
||||
static int vstatRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
|
||||
STATCNT(VFSSTAT_ANY,VFSSTAT_RANDOM)++;
|
||||
return REALVFS(pVfs)->xRandomness(REALVFS(pVfs), nByte, zBufOut);
|
||||
}
|
||||
|
||||
/*
|
||||
** Sleep for nMicro microseconds. Return the number of microseconds
|
||||
** actually slept.
|
||||
*/
|
||||
static int vstatSleep(sqlite3_vfs *pVfs, int nMicro){
|
||||
STATCNT(VFSSTAT_ANY,VFSSTAT_SLEEP)++;
|
||||
return REALVFS(pVfs)->xSleep(REALVFS(pVfs), nMicro);
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the current time as a Julian Day number in *pTimeOut.
|
||||
*/
|
||||
static int vstatCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
|
||||
STATCNT(VFSSTAT_ANY,VFSSTAT_CURTIME)++;
|
||||
return REALVFS(pVfs)->xCurrentTime(REALVFS(pVfs), pTimeOut);
|
||||
}
|
||||
|
||||
static int vstatGetLastError(sqlite3_vfs *pVfs, int a, char *b){
|
||||
return REALVFS(pVfs)->xGetLastError(REALVFS(pVfs), a, b);
|
||||
}
|
||||
static int vstatCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){
|
||||
STATCNT(VFSSTAT_ANY,VFSSTAT_CURTIME)++;
|
||||
return REALVFS(pVfs)->xCurrentTimeInt64(REALVFS(pVfs), p);
|
||||
}
|
||||
|
||||
/*
|
||||
** A virtual table for accessing the stats collected by this VFS shim
|
||||
*/
|
||||
static int vstattabConnect(sqlite3*, void*, int, const char*const*,
|
||||
sqlite3_vtab**,char**);
|
||||
static int vstattabBestIndex(sqlite3_vtab*,sqlite3_index_info*);
|
||||
static int vstattabDisconnect(sqlite3_vtab*);
|
||||
static int vstattabOpen(sqlite3_vtab*, sqlite3_vtab_cursor**);
|
||||
static int vstattabClose(sqlite3_vtab_cursor*);
|
||||
static int vstattabFilter(sqlite3_vtab_cursor*, int idxNum, const char *idxStr,
|
||||
int argc, sqlite3_value **argv);
|
||||
static int vstattabNext(sqlite3_vtab_cursor*);
|
||||
static int vstattabEof(sqlite3_vtab_cursor*);
|
||||
static int vstattabColumn(sqlite3_vtab_cursor*,sqlite3_context*,int);
|
||||
static int vstattabRowid(sqlite3_vtab_cursor*,sqlite3_int64*);
|
||||
static int vstattabUpdate(sqlite3_vtab*,int,sqlite3_value**,sqlite3_int64*);
|
||||
|
||||
/* A cursor for the vfsstat virtual table */
|
||||
typedef struct VfsStatCursor {
|
||||
sqlite3_vtab_cursor base; /* Base class. Must be first */
|
||||
int i; /* Pointing to this aVfsCnt[] value */
|
||||
} VfsStatCursor;
|
||||
|
||||
|
||||
static int vstattabConnect(
|
||||
sqlite3 *db,
|
||||
void *pAux,
|
||||
int argc, const char *const*argv,
|
||||
sqlite3_vtab **ppVtab,
|
||||
char **pzErr
|
||||
){
|
||||
sqlite3_vtab *pNew;
|
||||
int rc;
|
||||
|
||||
/* Column numbers */
|
||||
#define VSTAT_COLUMN_FILE 0
|
||||
#define VSTAT_COLUMN_STAT 1
|
||||
#define VSTAT_COLUMN_COUNT 2
|
||||
|
||||
rc = sqlite3_declare_vtab(db,"CREATE TABLE x(file,stat,count)");
|
||||
if( rc==SQLITE_OK ){
|
||||
pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) );
|
||||
if( pNew==0 ) return SQLITE_NOMEM;
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** This method is the destructor for vstat table object.
|
||||
*/
|
||||
static int vstattabDisconnect(sqlite3_vtab *pVtab){
|
||||
sqlite3_free(pVtab);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Constructor for a new vstat table cursor object.
|
||||
*/
|
||||
static int vstattabOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
|
||||
VfsStatCursor *pCur;
|
||||
pCur = sqlite3_malloc( sizeof(*pCur) );
|
||||
if( pCur==0 ) return SQLITE_NOMEM;
|
||||
memset(pCur, 0, sizeof(*pCur));
|
||||
*ppCursor = &pCur->base;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Destructor for a VfsStatCursor.
|
||||
*/
|
||||
static int vstattabClose(sqlite3_vtab_cursor *cur){
|
||||
sqlite3_free(cur);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Advance a VfsStatCursor to its next row of output.
|
||||
*/
|
||||
static int vstattabNext(sqlite3_vtab_cursor *cur){
|
||||
((VfsStatCursor*)cur)->i++;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return values of columns for the row at which the VfsStatCursor
|
||||
** is currently pointing.
|
||||
*/
|
||||
static int vstattabColumn(
|
||||
sqlite3_vtab_cursor *cur, /* The cursor */
|
||||
sqlite3_context *ctx, /* First argument to sqlite3_result_...() */
|
||||
int i /* Which column to return */
|
||||
){
|
||||
VfsStatCursor *pCur = (VfsStatCursor*)cur;
|
||||
switch( i ){
|
||||
case VSTAT_COLUMN_FILE: {
|
||||
sqlite3_result_text(ctx, azFile[pCur->i/VFSSTAT_nStat], -1, SQLITE_STATIC);
|
||||
break;
|
||||
}
|
||||
case VSTAT_COLUMN_STAT: {
|
||||
const char **az;
|
||||
az = (pCur->i/VFSSTAT_nStat)==VFSSTAT_ANY ? azStatAny : azStat;
|
||||
sqlite3_result_text(ctx, az[pCur->i%VFSSTAT_nStat], -1, SQLITE_STATIC);
|
||||
break;
|
||||
}
|
||||
case VSTAT_COLUMN_COUNT: {
|
||||
sqlite3_result_int64(ctx, aVfsCnt[pCur->i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the rowid for the current row.
|
||||
*/
|
||||
static int vstattabRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
|
||||
VfsStatCursor *pCur = (VfsStatCursor*)cur;
|
||||
*pRowid = pCur->i;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return TRUE if the cursor has been moved off of the last
|
||||
** row of output.
|
||||
*/
|
||||
static int vstattabEof(sqlite3_vtab_cursor *cur){
|
||||
VfsStatCursor *pCur = (VfsStatCursor*)cur;
|
||||
return pCur->i >= VFSSTAT_MXCNT;
|
||||
}
|
||||
|
||||
/*
|
||||
** Only a full table scan is supported. So xFilter simply rewinds to
|
||||
** the beginning.
|
||||
*/
|
||||
static int vstattabFilter(
|
||||
sqlite3_vtab_cursor *pVtabCursor,
|
||||
int idxNum, const char *idxStr,
|
||||
int argc, sqlite3_value **argv
|
||||
){
|
||||
VfsStatCursor *pCur = (VfsStatCursor*)pVtabCursor;
|
||||
pCur->i = 0;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Only a forwards full table scan is supported. xBestIndex is a no-op.
|
||||
*/
|
||||
static int vstattabBestIndex(
|
||||
sqlite3_vtab *tab,
|
||||
sqlite3_index_info *pIdxInfo
|
||||
){
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Any VSTAT_COLUMN_COUNT can be changed to a positive integer.
|
||||
** No deletions or insertions are allowed. No changes to other
|
||||
** columns are allowed.
|
||||
*/
|
||||
static int vstattabUpdate(
|
||||
sqlite3_vtab *tab,
|
||||
int argc, sqlite3_value **argv,
|
||||
sqlite3_int64 *pRowid
|
||||
){
|
||||
sqlite3_int64 iRowid, x;
|
||||
if( argc==1 ) return SQLITE_ERROR;
|
||||
if( sqlite3_value_type(argv[0])!=SQLITE_INTEGER ) return SQLITE_ERROR;
|
||||
iRowid = sqlite3_value_int64(argv[0]);
|
||||
if( iRowid!=sqlite3_value_int64(argv[1]) ) return SQLITE_ERROR;
|
||||
if( iRowid<0 || iRowid>=VFSSTAT_MXCNT ) return SQLITE_ERROR;
|
||||
if( sqlite3_value_type(argv[VSTAT_COLUMN_COUNT+2])!=SQLITE_INTEGER ){
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
x = sqlite3_value_int64(argv[VSTAT_COLUMN_COUNT+2]);
|
||||
if( x<0 ) return SQLITE_ERROR;
|
||||
aVfsCnt[iRowid] = x;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
static sqlite3_module VfsStatModule = {
|
||||
0, /* iVersion */
|
||||
0, /* xCreate */
|
||||
vstattabConnect, /* xConnect */
|
||||
vstattabBestIndex, /* xBestIndex */
|
||||
vstattabDisconnect, /* xDisconnect */
|
||||
0, /* xDestroy */
|
||||
vstattabOpen, /* xOpen - open a cursor */
|
||||
vstattabClose, /* xClose - close a cursor */
|
||||
vstattabFilter, /* xFilter - configure scan constraints */
|
||||
vstattabNext, /* xNext - advance a cursor */
|
||||
vstattabEof, /* xEof - check for end of scan */
|
||||
vstattabColumn, /* xColumn - read data */
|
||||
vstattabRowid, /* xRowid - read data */
|
||||
vstattabUpdate, /* xUpdate */
|
||||
0, /* xBegin */
|
||||
0, /* xSync */
|
||||
0, /* xCommit */
|
||||
0, /* xRollback */
|
||||
0, /* xFindMethod */
|
||||
0, /* xRename */
|
||||
};
|
||||
|
||||
/*
|
||||
** This routine is an sqlite3_auto_extension() callback, invoked to register
|
||||
** the vfsstat virtual table for all new database connections.
|
||||
*/
|
||||
static int vstatRegister(
|
||||
sqlite3 *db,
|
||||
const char **pzErrMsg,
|
||||
const struct sqlite3_api_routines *pThunk
|
||||
){
|
||||
return sqlite3_create_module(db, "vfsstat", &VfsStatModule, 0);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
/*
|
||||
** This routine is called when the extension is loaded.
|
||||
**
|
||||
** Register the new VFS. Make arrangement to register the virtual table
|
||||
** for each new database connection.
|
||||
*/
|
||||
int sqlite3_vfsstat_init(
|
||||
sqlite3 *db,
|
||||
char **pzErrMsg,
|
||||
const sqlite3_api_routines *pApi
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
SQLITE_EXTENSION_INIT2(pApi);
|
||||
vstat_vfs.pVfs = sqlite3_vfs_find(0);
|
||||
vstat_vfs.base.szOsFile = sizeof(VStatFile) + vstat_vfs.pVfs->szOsFile;
|
||||
rc = sqlite3_vfs_register(&vstat_vfs.base, 1);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_auto_extension(vstatRegister);
|
||||
}
|
||||
if( rc==SQLITE_OK ) rc = SQLITE_OK_LOAD_PERMANENTLY;
|
||||
return rc;
|
||||
}
|
||||
@ -24,12 +24,22 @@
|
||||
*/
|
||||
void usage(const char *zArgv0){
|
||||
fprintf(stderr,
|
||||
"Usage: %s [-step NSTEP] TARGET-DB RBU-DB\n"
|
||||
"Usage: %s ?OPTIONS? TARGET-DB RBU-DB\n"
|
||||
"\n"
|
||||
" Argument RBU-DB must be an RBU database containing an update suitable for\n"
|
||||
" target database TARGET-DB. If NSTEP is set to less than or equal to zero\n"
|
||||
" (the default value), this program attempts to apply the entire update to\n"
|
||||
" the target database.\n"
|
||||
"Where options are:\n"
|
||||
"\n"
|
||||
" -step NSTEP\n"
|
||||
" -vacuum\n"
|
||||
"\n"
|
||||
" If the -vacuum switch is not present, argument RBU-DB must be an RBU\n"
|
||||
" database containing an update suitable for target database TARGET-DB.\n"
|
||||
" Or, if -vacuum is specified, then TARGET-DB is a database to vacuum using\n"
|
||||
" RBU, and RBU-DB is used as the state database for the vacuum (refer to\n"
|
||||
" API documentation for details).\n"
|
||||
"\n"
|
||||
" If NSTEP is set to less than or equal to zero (the default value), this \n"
|
||||
" program attempts to perform the entire update or vacuum operation before\n"
|
||||
" exiting\n"
|
||||
"\n"
|
||||
" If NSTEP is greater than zero, then a maximum of NSTEP calls are made\n"
|
||||
" to sqlite3rbu_step(). If the RBU update has not been completely applied\n"
|
||||
@ -69,29 +79,43 @@ int main(int argc, char **argv){
|
||||
char *zErrmsg; /* Error message, if any */
|
||||
sqlite3rbu *pRbu; /* RBU handle */
|
||||
int nStep = 0; /* Maximum number of step() calls */
|
||||
int bVacuum = 0;
|
||||
int rc;
|
||||
sqlite3_int64 nProgress = 0;
|
||||
int nArg = argc-2;
|
||||
|
||||
/* Process command line arguments. Following this block local variables
|
||||
** zTarget, zRbu and nStep are all set. */
|
||||
if( argc==5 ){
|
||||
size_t nArg1 = strlen(argv[1]);
|
||||
if( nArg1>5 || nArg1<2 || memcmp("-step", argv[1], nArg1) ) usage(argv[0]);
|
||||
nStep = atoi(argv[2]);
|
||||
}else if( argc!=3 ){
|
||||
usage(argv[0]);
|
||||
if( argc<3 ) usage(argv[0]);
|
||||
for(i=1; i<nArg; i++){
|
||||
const char *zArg = argv[i];
|
||||
int nArg = strlen(zArg);
|
||||
if( nArg>1 && nArg<=8 && 0==memcmp(zArg, "-vacuum", nArg) ){
|
||||
bVacuum = 1;
|
||||
}else if( nArg>1 && nArg<=5 && 0==memcmp(zArg, "-step", nArg) && i<nArg-1 ){
|
||||
i++;
|
||||
nStep = atoi(argv[i]);
|
||||
}else{
|
||||
usage(argv[0]);
|
||||
}
|
||||
}
|
||||
|
||||
zTarget = argv[argc-2];
|
||||
zRbu = argv[argc-1];
|
||||
|
||||
report_default_vfs();
|
||||
|
||||
/* Open an RBU handle. If nStep is less than or equal to zero, call
|
||||
/* Open an RBU handle. A vacuum handle if -vacuum was specified, or a
|
||||
** regular RBU update handle otherwise. */
|
||||
if( bVacuum ){
|
||||
pRbu = sqlite3rbu_vacuum(zTarget, zRbu);
|
||||
}else{
|
||||
pRbu = sqlite3rbu_open(zTarget, zRbu, 0);
|
||||
}
|
||||
report_rbu_vfs(pRbu);
|
||||
|
||||
/* If nStep is less than or equal to zero, call
|
||||
** sqlite3rbu_step() until either the RBU has been completely applied
|
||||
** or an error occurs. Or, if nStep is greater than zero, call
|
||||
** sqlite3rbu_step() a maximum of nStep times. */
|
||||
pRbu = sqlite3rbu_open(zTarget, zRbu, 0);
|
||||
report_rbu_vfs(pRbu);
|
||||
for(i=0; (nStep<=0 || i<nStep) && sqlite3rbu_step(pRbu)==SQLITE_OK; i++);
|
||||
nProgress = sqlite3rbu_progress(pRbu);
|
||||
rc = sqlite3rbu_close(pRbu, &zErrmsg);
|
||||
|
||||
@ -10,10 +10,7 @@
|
||||
#***********************************************************************
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source $testdir/tester.tcl
|
||||
source [file join [file dirname [info script]] rbu_common.tcl]
|
||||
set ::testprefix rbu1
|
||||
|
||||
db close
|
||||
@ -96,26 +93,6 @@ proc create_rbu5 {filename} {
|
||||
return $filename
|
||||
}
|
||||
|
||||
# Run the RBU in file $rbu on target database $target until completion.
|
||||
#
|
||||
proc run_rbu {target rbu} {
|
||||
sqlite3rbu rbu $target $rbu
|
||||
while 1 {
|
||||
set rc [rbu step]
|
||||
if {$rc!="SQLITE_OK"} break
|
||||
}
|
||||
rbu close
|
||||
}
|
||||
|
||||
proc step_rbu {target rbu} {
|
||||
while 1 {
|
||||
sqlite3rbu rbu $target $rbu
|
||||
set rc [rbu step]
|
||||
rbu close
|
||||
if {$rc != "SQLITE_OK"} break
|
||||
}
|
||||
set rc
|
||||
}
|
||||
|
||||
# Same as [step_rbu], except using a URI to open the target db.
|
||||
#
|
||||
@ -611,7 +588,7 @@ foreach {tn3 create_vfs destroy_vfs} {
|
||||
9 {
|
||||
CREATE TABLE t1(a, b PRIMARY KEY) WITHOUT ROWID;
|
||||
CREATE TABLE rbu.data_t1(a, b, rbu_control);
|
||||
INSERT INTO rbu.data_t1 VALUES(1, 2, 2);
|
||||
INSERT INTO rbu.data_t1 VALUES(1, 2, 3);
|
||||
} {SQLITE_ERROR - invalid rbu_control value}
|
||||
|
||||
10 {
|
||||
@ -641,10 +618,25 @@ foreach {tn3 create_vfs destroy_vfs} {
|
||||
# correctly.
|
||||
reset_db
|
||||
forcedelete rbu.db
|
||||
do_test $tn3.8 {
|
||||
do_test $tn3.8.1 {
|
||||
list [catch { run_rbu test.db rbu.db } msg] $msg
|
||||
} {0 SQLITE_DONE}
|
||||
|
||||
|
||||
# Test that an RBU database containing only empty data_xxx tables is
|
||||
# also handled correctly.
|
||||
reset_db
|
||||
forcedelete rbu.db
|
||||
do_execsql_test $tn3.8.2.1 {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
ATTACH 'rbu.db' AS rbu;
|
||||
CREATE TABLE data_t1(a, b, rbu_control);
|
||||
DETACH rbu;
|
||||
}
|
||||
do_test $tn3.8.2.1 {
|
||||
list [catch { run_rbu test.db rbu.db } msg] $msg
|
||||
} {0 SQLITE_DONE}
|
||||
|
||||
# Test that RBU can update indexes containing NULL values.
|
||||
#
|
||||
reset_db
|
||||
|
||||
@ -12,35 +12,10 @@
|
||||
# Test some properties of the pager_rbu_mode and rbu_mode pragmas.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source $testdir/tester.tcl
|
||||
source [file join [file dirname [info script]] rbu_common.tcl]
|
||||
set ::testprefix rbu5
|
||||
|
||||
|
||||
# Run the RBU in file $rbu on target database $target until completion.
|
||||
#
|
||||
proc run_rbu {target rbu} {
|
||||
sqlite3rbu rbu $target $rbu
|
||||
while { [rbu step]=="SQLITE_OK" } {}
|
||||
rbu close
|
||||
}
|
||||
|
||||
|
||||
# Run the RBU in file $rbu on target database $target one step at a
|
||||
# time until completion.
|
||||
#
|
||||
proc step_rbu {target rbu} {
|
||||
while 1 {
|
||||
sqlite3rbu rbu $target $rbu
|
||||
set rc [rbu step]
|
||||
rbu close
|
||||
if {$rc != "SQLITE_OK"} break
|
||||
}
|
||||
set rc
|
||||
}
|
||||
|
||||
# Return a list of the primary key columns for table $tbl in the database
|
||||
# opened by database handle $db.
|
||||
#
|
||||
|
||||
142
ext/rbu/rbuC.test
Normal file
142
ext/rbu/rbuC.test
Normal file
@ -0,0 +1,142 @@
|
||||
# 2016 March 7
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#***********************************************************************
|
||||
# Tests for RBU focused on the REPLACE operation (rbu_control column
|
||||
# contains integer value 2).
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] rbu_common.tcl]
|
||||
set ::testprefix rbuC
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# This test is actually of an UPDATE directive. Just to establish that
|
||||
# these work with UNIQUE indexes before preceding to REPLACE.
|
||||
#
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(i INTEGER PRIMARY KEY, a, b, c UNIQUE);
|
||||
INSERT INTO t1 VALUES(1, 'a', 'b', 'c');
|
||||
}
|
||||
|
||||
forcedelete rbu.db
|
||||
do_execsql_test 1.1 {
|
||||
ATTACH 'rbu.db' AS rbu;
|
||||
CREATE TABLE rbu.data_t1(i, a, b, c, rbu_control);
|
||||
INSERT INTO data_t1 VALUES(1, 'a', 'b', 'c', '.xxx');
|
||||
}
|
||||
|
||||
do_test 1.2 {
|
||||
step_rbu test.db rbu.db
|
||||
} {SQLITE_DONE}
|
||||
|
||||
do_execsql_test 1.3 {
|
||||
SELECT * FROM t1
|
||||
} {
|
||||
1 a b c
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
foreach {tn schema} {
|
||||
1 {
|
||||
CREATE TABLE t1(i INTEGER PRIMARY KEY, a, b, c UNIQUE);
|
||||
CREATE INDEX t1a ON t1(a);
|
||||
}
|
||||
2 {
|
||||
CREATE TABLE t1(i PRIMARY KEY, a, b, c UNIQUE);
|
||||
CREATE INDEX t1a ON t1(a);
|
||||
}
|
||||
3 {
|
||||
CREATE TABLE t1(i PRIMARY KEY, a, b, c UNIQUE) WITHOUT ROWID;
|
||||
CREATE INDEX t1a ON t1(a);
|
||||
}
|
||||
} {
|
||||
reset_db
|
||||
forcedelete rbu.db
|
||||
execsql $schema
|
||||
|
||||
do_execsql_test 2.$tn.0 {
|
||||
INSERT INTO t1 VALUES(1, 'a', 'b', 'c');
|
||||
INSERT INTO t1 VALUES(2, 'b', 'c', 'd');
|
||||
INSERT INTO t1 VALUES(3, 'c', 'd', 'e');
|
||||
}
|
||||
|
||||
do_execsql_test 2.$tn.1 {
|
||||
ATTACH 'rbu.db' AS rbu;
|
||||
CREATE TABLE rbu.data_t1(i, a, b, c, rbu_control);
|
||||
INSERT INTO data_t1 VALUES(1, 1, 2, 3, 2);
|
||||
INSERT INTO data_t1 VALUES(3, 'c', 'd', 'e', 2);
|
||||
INSERT INTO data_t1 VALUES(4, 'd', 'e', 'f', 2);
|
||||
}
|
||||
|
||||
do_test 2.$tn.2 {
|
||||
step_rbu test.db rbu.db
|
||||
} {SQLITE_DONE}
|
||||
|
||||
do_execsql_test 2.$tn.3 {
|
||||
SELECT * FROM t1 ORDER BY i
|
||||
} {
|
||||
1 1 2 3
|
||||
2 b c d
|
||||
3 c d e
|
||||
4 d e f
|
||||
}
|
||||
|
||||
integrity_check 2.$tn.4
|
||||
}
|
||||
|
||||
foreach {tn schema} {
|
||||
1 {
|
||||
CREATE TABLE t1(a, b, c UNIQUE);
|
||||
CREATE INDEX t1a ON t1(a);
|
||||
}
|
||||
|
||||
2 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(a, b, c);
|
||||
}
|
||||
} {
|
||||
if {$tn==2} { ifcapable !fts5 break }
|
||||
reset_db
|
||||
forcedelete rbu.db
|
||||
execsql $schema
|
||||
|
||||
do_execsql_test 3.$tn.0 {
|
||||
INSERT INTO t1 VALUES('a', 'b', 'c');
|
||||
INSERT INTO t1 VALUES('b', 'c', 'd');
|
||||
INSERT INTO t1 VALUES('c', 'd', 'e');
|
||||
}
|
||||
|
||||
do_execsql_test 3.$tn.1 {
|
||||
ATTACH 'rbu.db' AS rbu;
|
||||
CREATE TABLE rbu.data_t1(rbu_rowid, a, b, c, rbu_control);
|
||||
INSERT INTO data_t1 VALUES(1, 1, 2, 3, 2);
|
||||
INSERT INTO data_t1 VALUES(3, 'c', 'd', 'e', 2);
|
||||
INSERT INTO data_t1 VALUES(4, 'd', 'e', 'f', 2);
|
||||
}
|
||||
|
||||
do_test 3.$tn.2 {
|
||||
step_rbu test.db rbu.db
|
||||
} {SQLITE_DONE}
|
||||
|
||||
do_execsql_test 3.$tn.3 {
|
||||
SELECT rowid, * FROM t1 ORDER BY 1
|
||||
} {
|
||||
1 1 2 3
|
||||
2 b c d
|
||||
3 c d e
|
||||
4 d e f
|
||||
}
|
||||
|
||||
integrity_check 3.$tn.4
|
||||
}
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
@ -15,12 +15,43 @@ if {![info exists testdir]} {
|
||||
}
|
||||
source $testdir/tester.tcl
|
||||
|
||||
proc check_prestep_state {target state} {
|
||||
set oal_exists [file exists $target-oal]
|
||||
set wal_exists [file exists $target-wal]
|
||||
set progress [rbu progress]
|
||||
|
||||
if {($progress==0 && $state!="oal" && $state!="done")
|
||||
|| ($oal_exists && $wal_exists)
|
||||
|| ($progress>0 && $state=="oal" && (!$oal_exists || $wal_exists))
|
||||
|| ($state=="move" && (!$oal_exists || $wal_exists))
|
||||
|| ($state=="checkpoint" && ($oal_exists || !$wal_exists))
|
||||
|| ($state=="done" && ($oal_exists && $progress!=0))
|
||||
} {
|
||||
error "B: state=$state progress=$progress oal=$oal_exists wal=$wal_exists"
|
||||
}
|
||||
}
|
||||
|
||||
proc check_poststep_state {rc target state} {
|
||||
if {$rc=="SQLITE_OK" || $rc=="SQLITE_DONE"} {
|
||||
set oal_exists [file exists $target-oal]
|
||||
set wal_exists [file exists $target-wal]
|
||||
if {$state=="move" && ($oal_exists || !$wal_exists)} {
|
||||
error "A: state=$state progress=$progress oal=$oal_exists wal=$wal_exists"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Run the RBU in file $rbu on target database $target until completion.
|
||||
#
|
||||
proc run_rbu {target rbu} {
|
||||
sqlite3rbu rbu $target $rbu
|
||||
while 1 {
|
||||
set state [rbu state]
|
||||
|
||||
check_prestep_state $target $state
|
||||
set rc [rbu step]
|
||||
check_poststep_state $rc $target $state
|
||||
|
||||
if {$rc!="SQLITE_OK"} break
|
||||
}
|
||||
rbu close
|
||||
@ -29,10 +60,33 @@ proc run_rbu {target rbu} {
|
||||
proc step_rbu {target rbu} {
|
||||
while 1 {
|
||||
sqlite3rbu rbu $target $rbu
|
||||
set state [rbu state]
|
||||
check_prestep_state $target $state
|
||||
set rc [rbu step]
|
||||
check_poststep_state $rc $target $state
|
||||
rbu close
|
||||
if {$rc != "SQLITE_OK"} break
|
||||
}
|
||||
set rc
|
||||
}
|
||||
|
||||
proc do_rbu_vacuum_test {tn step} {
|
||||
uplevel [list do_test $tn.1 {
|
||||
if {$step==0} { sqlite3rbu_vacuum rbu test.db state.db }
|
||||
while 1 {
|
||||
if {$step==1} { sqlite3rbu_vacuum rbu test.db state.db }
|
||||
set state [rbu state]
|
||||
check_prestep_state test.db $state
|
||||
set rc [rbu step]
|
||||
check_poststep_state $rc test.db $state
|
||||
if {$rc!="SQLITE_OK"} break
|
||||
if {$step==1} { rbu close }
|
||||
}
|
||||
rbu close
|
||||
} {SQLITE_DONE}]
|
||||
|
||||
uplevel [list do_execsql_test $tn.2 {
|
||||
PRAGMA integrity_check
|
||||
} ok]
|
||||
}
|
||||
|
||||
|
||||
@ -18,22 +18,17 @@ if {![info exists testdir]} {
|
||||
source $testdir/tester.tcl
|
||||
set testprefix rbudiff
|
||||
|
||||
if {$tcl_platform(platform)=="windows"} {
|
||||
set PROG "sqldiff.exe"
|
||||
} else {
|
||||
set PROG "./sqldiff"
|
||||
}
|
||||
if {![file exe $PROG]} {
|
||||
puts "rbudiff.test cannot run because $PROG is not available"
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
set PROG [test_find_sqldiff]
|
||||
db close
|
||||
|
||||
proc get_rbudiff_sql {db1 db2} {
|
||||
exec $::PROG --rbu $db1 $db2
|
||||
}
|
||||
|
||||
proc get_vtab_rbudiff_sql {db1 db2} {
|
||||
exec $::PROG --vtab --rbu $db1 $db2
|
||||
}
|
||||
|
||||
proc step_rbu {target rbu} {
|
||||
while 1 {
|
||||
sqlite3rbu rbu $target $rbu
|
||||
@ -45,6 +40,7 @@ proc step_rbu {target rbu} {
|
||||
}
|
||||
|
||||
proc apply_rbudiff {sql target} {
|
||||
test_rbucount $sql
|
||||
forcedelete rbu.db
|
||||
sqlite3 rbudb rbu.db
|
||||
rbudb eval $sql
|
||||
@ -52,15 +48,43 @@ proc apply_rbudiff {sql target} {
|
||||
step_rbu $target rbu.db
|
||||
}
|
||||
|
||||
proc sqlesc {id} {
|
||||
set ret "'[string map {' ''} $id]'"
|
||||
set ret
|
||||
}
|
||||
|
||||
# The only argument is the output of an [sqldiff -rbu] run. This command
|
||||
# tests that the contents of the rbu_count table is correct. An exception
|
||||
# is thrown if it is not.
|
||||
#
|
||||
proc test_rbucount {sql} {
|
||||
sqlite3 tmpdb ""
|
||||
tmpdb eval $sql
|
||||
tmpdb eval {
|
||||
SELECT name FROM sqlite_master WHERE name LIKE 'data%' AND type='table'
|
||||
} {
|
||||
set a [tmpdb eval "SELECT count(*) FROM [sqlesc $name]"]
|
||||
set b [tmpdb eval {SELECT cnt FROM rbu_count WHERE tbl = $name}]
|
||||
if {$a != $b} {
|
||||
tmpdb close
|
||||
error "rbu_count error - tbl = $name"
|
||||
}
|
||||
}
|
||||
tmpdb close
|
||||
return ""
|
||||
}
|
||||
|
||||
proc rbudiff_cksum {db1} {
|
||||
set txt ""
|
||||
|
||||
sqlite3 dbtmp $db1
|
||||
foreach tbl [dbtmp eval {SELECT name FROM sqlite_master WHERE type='table'}] {
|
||||
set cols [list]
|
||||
dbtmp eval "PRAGMA table_info = $tbl" { lappend cols "quote( $name )" }
|
||||
dbtmp eval "PRAGMA table_info = [sqlesc $tbl]" {
|
||||
lappend cols "quote( $name )"
|
||||
}
|
||||
append txt [dbtmp eval \
|
||||
"SELECT [join $cols {||'.'||}] FROM $tbl ORDER BY 1"
|
||||
"SELECT [join $cols {||'.'||}] FROM [sqlesc $tbl] ORDER BY 1"
|
||||
]
|
||||
}
|
||||
dbtmp close
|
||||
@ -116,6 +140,24 @@ foreach {tn init mod} {
|
||||
);
|
||||
}
|
||||
|
||||
4 {
|
||||
CREATE TABLE x1(a, b, c, PRIMARY KEY(a, b, c));
|
||||
INSERT INTO x1 VALUES('u', 'v', NULL);
|
||||
INSERT INTO x1 VALUES('x', 'y', 'z');
|
||||
INSERT INTO x1 VALUES('a', NULL, 'b');
|
||||
} {
|
||||
INSERT INTO x1 VALUES('a', 'b', 'c');
|
||||
}
|
||||
|
||||
5 {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
INSERT INTO t1 VALUES(1, NULL);
|
||||
INSERT INTO t1 VALUES(2, X'');
|
||||
} {
|
||||
UPDATE t1 SET b = X'' WHERE a=1;
|
||||
UPDATE t1 SET b = NULL WHERE a=2;
|
||||
}
|
||||
|
||||
} {
|
||||
catch { db close }
|
||||
|
||||
@ -146,5 +188,116 @@ foreach {tn init mod} {
|
||||
do_test 1.$tn.5 { rbudiff_cksum test.db } [rbudiff_cksum test.db2]
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that if the --vtab switch is present, [sqldiff] handles virtual
|
||||
# table types fts[345] and rtree correctly.
|
||||
#
|
||||
ifcapable fts3&&fts5&&rtree {
|
||||
|
||||
foreach {tn init mod} {
|
||||
1 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(c);
|
||||
INSERT INTO t1 VALUES('a b c');
|
||||
INSERT INTO t1 VALUES('a b c');
|
||||
} {
|
||||
DELETE FROM t1 WHERE rowid = 1;
|
||||
INSERT INTO t1 VALUES('a b c');
|
||||
}
|
||||
|
||||
2 {
|
||||
CREATE VIRTUAL TABLE "x y" USING 'rtree'(id, x1, x2);
|
||||
INSERT INTO "x y" VALUES(1, 2, 3);
|
||||
INSERT INTO "x y" VALUES(2, 4, 6);
|
||||
} {
|
||||
DELETE FROM "x y" WHERE rowid = 1;
|
||||
INSERT INTO "x y" VALUES(3, 6, 9);
|
||||
}
|
||||
|
||||
3 {
|
||||
CREATE VIRTUAL TABLE 'x''y' USING fts3;
|
||||
INSERT INTO 'x''y' VALUES('one two three');
|
||||
INSERT INTO 'x''y' VALUES('four five six');
|
||||
} {
|
||||
DELETE FROM 'x''y' WHERE rowid = 1;
|
||||
INSERT INTO 'x''y' VALUES('one two three');
|
||||
}
|
||||
} {
|
||||
|
||||
forcedelete test.db test.db2
|
||||
sqlite3 db test.db
|
||||
db eval "$init"
|
||||
sqlite3 db test.db2
|
||||
db eval "$init ; $mod"
|
||||
db close
|
||||
|
||||
do_test 2.$tn.1 {
|
||||
set sql [get_vtab_rbudiff_sql test.db test.db2]
|
||||
apply_rbudiff $sql test.db
|
||||
} {SQLITE_DONE}
|
||||
do_test 2.$tn.2 { rbudiff_cksum test.db } [rbudiff_cksum test.db2]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ifcapable fts5 {
|
||||
foreach {tn init mod} {
|
||||
1 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(c);
|
||||
INSERT INTO t1 VALUES('a b c');
|
||||
INSERT INTO t1 VALUES('a b c');
|
||||
} {
|
||||
DELETE FROM t1 WHERE rowid = 1;
|
||||
INSERT INTO t1 VALUES('a b c');
|
||||
}
|
||||
|
||||
2 {
|
||||
CREATE VIRTUAL TABLE t1 USING FTs5(c);
|
||||
INSERT INTO t1 VALUES('a b c');
|
||||
INSERT INTO t1 VALUES('a b c');
|
||||
} {
|
||||
DELETE FROM t1 WHERE rowid = 1;
|
||||
INSERT INTO t1 VALUES('a b c');
|
||||
}
|
||||
|
||||
3 {
|
||||
creAte virTUal
|
||||
tablE t1 USING FTs5(c);
|
||||
INSERT INTO t1 VALUES('a b c');
|
||||
INSERT INTO t1 VALUES('a b c');
|
||||
} {
|
||||
DELETE FROM t1 WHERE rowid = 1;
|
||||
INSERT INTO t1 VALUES('a b c');
|
||||
}
|
||||
|
||||
} {
|
||||
forcedelete test.db test.db2
|
||||
sqlite3 db test.db
|
||||
db eval "$init"
|
||||
sqlite3 db test.db2
|
||||
db eval "$init ; $mod"
|
||||
db eval { INSERT INTO t1(t1) VALUES('optimize') }
|
||||
db close
|
||||
|
||||
do_test 3.$tn.1 {
|
||||
set sql [get_vtab_rbudiff_sql test.db test.db2]
|
||||
apply_rbudiff $sql test.db
|
||||
} {SQLITE_DONE}
|
||||
|
||||
sqlite3 db test.db
|
||||
sqlite3 db2 test.db2
|
||||
do_test 3.$tn.2 {
|
||||
db2 eval { SELECT * FROM t1 ORDER BY rowid }
|
||||
} [db eval { SELECT * FROM t1 ORDER BY rowid }]
|
||||
|
||||
do_test 3.$tn.3 {
|
||||
db2 eval { INSERT INTO t1(t1) VALUES('integrity-check') }
|
||||
} {}
|
||||
|
||||
db close
|
||||
db2 close
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
59
ext/rbu/rbudor.test
Normal file
59
ext/rbu/rbudor.test
Normal file
@ -0,0 +1,59 @@
|
||||
# 2016 October 21
|
||||
#
|
||||
# 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 test file focuses on interactions between RBU and the feature
|
||||
# enabled by SQLITE_DIRECT_OVERFLOW_READ - Direct Overflow Read.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source $testdir/tester.tcl
|
||||
set ::testprefix rbudor
|
||||
|
||||
set bigA [string repeat a 5000]
|
||||
set bigB [string repeat b 5000]
|
||||
do_execsql_test 1.0 {
|
||||
PRAGMA page_size = 1024;
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b BLOB);
|
||||
INSERT INTO t1 VALUES(1, $bigA);
|
||||
} {}
|
||||
|
||||
do_test 1.1 {
|
||||
forcedelete rbu.db
|
||||
sqlite3 rbu rbu.db
|
||||
rbu eval {
|
||||
CREATE TABLE data_t1(a, b, rbu_control);
|
||||
INSERT INTO data_t1 VALUES(2, $bigB, 0);
|
||||
}
|
||||
rbu close
|
||||
} {}
|
||||
|
||||
do_test 1.2 {
|
||||
sqlite3rbu rbu test.db rbu.db
|
||||
while {[rbu state]!="checkpoint"} {
|
||||
rbu step
|
||||
}
|
||||
rbu step
|
||||
db eval { SELECT * FROM t1 }
|
||||
} [list 1 $bigA 2 $bigB]
|
||||
|
||||
do_test 1.3 {
|
||||
while {[rbu step]=="SQLITE_OK"} {}
|
||||
rbu close
|
||||
} {SQLITE_DONE}
|
||||
|
||||
do_execsql_test 1.4 {
|
||||
SELECT * FROM t1
|
||||
} [list 1 $bigA 2 $bigB]
|
||||
|
||||
finish_test
|
||||
|
||||
98
ext/rbu/rbufault3.test
Normal file
98
ext/rbu/rbufault3.test
Normal file
@ -0,0 +1,98 @@
|
||||
# 2016 April 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 file contains fault injection tests for RBU vacuum operations.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] rbu_common.tcl]
|
||||
source $testdir/malloc_common.tcl
|
||||
set ::testprefix rbufault3
|
||||
|
||||
foreach {fault errlist} {
|
||||
oom-* {
|
||||
{1 SQLITE_NOMEM}
|
||||
{1 SQLITE_IOERR_NOMEM}
|
||||
{1 {SQLITE_NOMEM - out of memory}}
|
||||
}
|
||||
|
||||
ioerr-* {
|
||||
{1 {SQLITE_IOERR - disk I/O error}}
|
||||
{1 SQLITE_IOERR}
|
||||
{1 SQLITE_IOERR_WRITE}
|
||||
{1 SQLITE_IOERR_FSYNC}
|
||||
{1 SQLITE_IOERR_READ}
|
||||
{1 {SQLITE_IOERR - unable to open database: test.db2}}
|
||||
{1 {SQLITE_ERROR - unable to open database: test.db2}}
|
||||
{1 {SQLITE_ERROR - SQL logic error or missing database}}
|
||||
}
|
||||
|
||||
cantopen* {
|
||||
{1 {SQLITE_CANTOPEN - unable to open database: test.db2}}
|
||||
{1 {SQLITE_CANTOPEN - unable to open database: test.db2}}
|
||||
{1 {SQLITE_CANTOPEN - unable to open database file}}
|
||||
{1 SQLITE_CANTOPEN}
|
||||
}
|
||||
|
||||
} {
|
||||
|
||||
reset_db
|
||||
do_execsql_test 0 {
|
||||
CREATE TABLE target(x UNIQUE, y, z, PRIMARY KEY(y));
|
||||
INSERT INTO target VALUES(1, 2, 3);
|
||||
INSERT INTO target VALUES(4, 5, 6);
|
||||
INSERT INTO target VALUES(7, 8, 9);
|
||||
CREATE INDEX i1 ON target(z);
|
||||
}
|
||||
faultsim_save_and_close
|
||||
|
||||
do_faultsim_test 1 -faults $fault -prep {
|
||||
faultsim_restore_and_reopen
|
||||
forcedelete test.db2
|
||||
} -body {
|
||||
sqlite3rbu_vacuum rbu test.db test.db2
|
||||
while {[rbu step]=="SQLITE_OK"} {}
|
||||
rbu close
|
||||
} -test {
|
||||
eval [list faultsim_test_result {0 SQLITE_DONE} {*}$::errlist]
|
||||
}
|
||||
|
||||
do_faultsim_test 2 -faults $fault -prep {
|
||||
faultsim_restore_and_reopen
|
||||
forcedelete test.db2
|
||||
} -body {
|
||||
sqlite3rbu_vacuum rbu test.db test.db2
|
||||
rbu step
|
||||
rbu close
|
||||
} -test {
|
||||
eval [list faultsim_test_result {0 SQLITE_OK} {*}$::errlist]
|
||||
}
|
||||
|
||||
forcedelete test.db2
|
||||
sqlite3rbu_vacuum rbu test.db test.db2
|
||||
rbu step
|
||||
rbu close
|
||||
faultsim_save_and_close
|
||||
|
||||
do_faultsim_test 3 -faults $fault -prep {
|
||||
faultsim_restore_and_reopen
|
||||
forcedelete test.db2
|
||||
} -body {
|
||||
sqlite3rbu_vacuum rbu test.db test.db2
|
||||
rbu step
|
||||
rbu close
|
||||
} -test {
|
||||
eval [list faultsim_test_result {0 SQLITE_OK} {*}$::errlist]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
417
ext/rbu/rbuprogress.test
Normal file
417
ext/rbu/rbuprogress.test
Normal file
@ -0,0 +1,417 @@
|
||||
# 2016 March 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.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] rbu_common.tcl]
|
||||
set ::testprefix rbuprogress
|
||||
|
||||
|
||||
proc create_db_file {filename sql} {
|
||||
forcedelete $filename
|
||||
sqlite3 tmpdb $filename
|
||||
tmpdb eval $sql
|
||||
tmpdb close
|
||||
}
|
||||
|
||||
# Create a simple RBU database. That expects to write to a table:
|
||||
#
|
||||
# CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
|
||||
#
|
||||
proc create_rbu1 {filename} {
|
||||
create_db_file $filename {
|
||||
CREATE TABLE data_t1(a, b, c, rbu_control);
|
||||
INSERT INTO data_t1 VALUES(1, 2, 3, 0);
|
||||
INSERT INTO data_t1 VALUES(2, 'two', 'three', 0);
|
||||
INSERT INTO data_t1 VALUES(3, NULL, 8.2, 0);
|
||||
|
||||
CREATE TABLE rbu_count(tbl, cnt);
|
||||
INSERT INTO rbu_count VALUES('data_t1', 3);
|
||||
}
|
||||
return $filename
|
||||
}
|
||||
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
|
||||
}
|
||||
|
||||
do_test 1.1 {
|
||||
create_rbu1 rbu.db
|
||||
sqlite3rbu rbu test.db rbu.db
|
||||
rbu bp_progress
|
||||
} {0 0}
|
||||
do_test 1.2 { rbu step ; rbu bp_progress } {3333 0}
|
||||
do_test 1.3 { rbu step ; rbu bp_progress } {6666 0}
|
||||
do_test 1.4 { rbu step ; rbu bp_progress } {10000 0}
|
||||
do_test 1.5 { rbu step ; rbu bp_progress } {10000 0}
|
||||
do_test 1.6 { rbu step ; rbu bp_progress } {10000 0}
|
||||
do_test 1.7 { rbu step ; rbu bp_progress } {10000 5000}
|
||||
do_test 1.8 { rbu step ; rbu bp_progress } {10000 10000}
|
||||
do_test 1.9 { rbu step ; rbu bp_progress } {10000 10000}
|
||||
|
||||
do_test 1.10 {
|
||||
rbu close
|
||||
} {SQLITE_DONE}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
proc do_sp_test {tn bReopen target rbu reslist} {
|
||||
uplevel [list do_test $tn [subst -nocommands {
|
||||
if {$bReopen==0} { sqlite3rbu rbu $target $rbu }
|
||||
set res [list]
|
||||
while 1 {
|
||||
if {$bReopen} { sqlite3rbu rbu $target $rbu }
|
||||
set rc [rbu step]
|
||||
if {[set rc] != "SQLITE_OK"} { rbu close ; error "error 1" }
|
||||
lappend res [lindex [rbu bp_progress] 0]
|
||||
if {[lindex [set res] end]==10000} break
|
||||
if {$bReopen} { rbu close }
|
||||
}
|
||||
if {[set res] != [list $reslist]} {
|
||||
rbu close
|
||||
error "1. reslist incorrect (expect=$reslist got=[set res])"
|
||||
}
|
||||
|
||||
# One step to clean up the temporary tables used to update the only
|
||||
# target table in the rbu database. And one more to move the *-oal
|
||||
# file to *-wal. After each of these steps, the progress remains
|
||||
# at "10000 0".
|
||||
#
|
||||
if {[lindex [list $reslist] 0]!=-1} {
|
||||
rbu step
|
||||
set res [rbu bp_progress]
|
||||
if {[set res] != [list 10000 0]} {
|
||||
rbu close
|
||||
error "2. reslist incorrect (expect=10000 0 got=[set res])"
|
||||
}
|
||||
}
|
||||
|
||||
rbu step
|
||||
set res [rbu bp_progress]
|
||||
if {[set res] != [list 10000 0]} {
|
||||
rbu close
|
||||
error "3. reslist incorrect (expect=10000 0 got=[set res])"
|
||||
}
|
||||
|
||||
# Do the checkpoint.
|
||||
while {[rbu step]=="SQLITE_OK"} {
|
||||
foreach {a b} [rbu bp_progress] {}
|
||||
if {[set a]!=10000 || [set b]<=0 || [set b]>10000} {
|
||||
rbu close
|
||||
error "4. reslist incorrect (expect=10000 1..10000 got=[set a] [set b])"
|
||||
}
|
||||
}
|
||||
|
||||
set res [rbu bp_progress]
|
||||
if {[set res] != [list 10000 10000]} {
|
||||
rbu close
|
||||
error "5. reslist is incorrect (expect=10000 10000 got=[set res])"
|
||||
}
|
||||
|
||||
rbu close
|
||||
}] {SQLITE_DONE}]
|
||||
}
|
||||
|
||||
foreach {bReopen} { 0 1 } {
|
||||
reset_db
|
||||
do_test 2.$bReopen.1.0 {
|
||||
execsql {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
|
||||
}
|
||||
create_db_file rbu.db {
|
||||
CREATE TABLE data_t1(a, b, c, rbu_control);
|
||||
INSERT INTO data_t1 VALUES(4, 4, 4, 0);
|
||||
INSERT INTO data_t1 VALUES(5, 5, 5, 0);
|
||||
|
||||
CREATE TABLE rbu_count(tbl, cnt);
|
||||
INSERT INTO rbu_count VALUES('data_t1', 2);
|
||||
}
|
||||
} {}
|
||||
do_sp_test 2.$bReopen.1.1 $bReopen test.db rbu.db {5000 10000}
|
||||
|
||||
reset_db
|
||||
do_test 2.$bReopen.2.0 {
|
||||
execsql { CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c) }
|
||||
create_rbu1 rbu.db
|
||||
} {rbu.db}
|
||||
do_sp_test 2.$bReopen.2.1 $bReopen test.db rbu.db {3333 6666 10000}
|
||||
|
||||
reset_db
|
||||
do_test 2.$bReopen.3.0 {
|
||||
execsql {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
|
||||
CREATE INDEX i1 ON t1(b);
|
||||
INSERT INTO t1 VALUES(1, 1, 1);
|
||||
INSERT INTO t1 VALUES(2, 2, 2);
|
||||
INSERT INTO t1 VALUES(3, 3, 3);
|
||||
}
|
||||
create_db_file rbu.db {
|
||||
CREATE TABLE data_t1(a, b, c, rbu_control);
|
||||
INSERT INTO data_t1 VALUES(4, 4, 4, 0);
|
||||
INSERT INTO data_t1 VALUES(2, NULL, NULL, 1);
|
||||
INSERT INTO data_t1 VALUES(5, NULL, NULL, 1);
|
||||
|
||||
CREATE TABLE rbu_count(tbl, cnt);
|
||||
INSERT INTO rbu_count VALUES('data_t1', 3);
|
||||
}
|
||||
} {}
|
||||
do_sp_test 2.$bReopen.3.1 $bReopen test.db rbu.db {1666 3333 6000 8000 10000}
|
||||
|
||||
reset_db
|
||||
do_test 2.$bReopen.4.0 {
|
||||
execsql {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
|
||||
CREATE INDEX i1 ON t1(b);
|
||||
INSERT INTO t1 VALUES(1, 1, 1);
|
||||
INSERT INTO t1 VALUES(2, 2, 2);
|
||||
INSERT INTO t1 VALUES(3, 3, 3);
|
||||
}
|
||||
create_db_file rbu.db {
|
||||
CREATE TABLE data_t1(a, b, c, rbu_control);
|
||||
INSERT INTO data_t1 VALUES(2, 4, 4, '.xx');
|
||||
|
||||
CREATE TABLE rbu_count(tbl, cnt);
|
||||
INSERT INTO rbu_count VALUES('data_t1', 1);
|
||||
}
|
||||
} {}
|
||||
do_sp_test 2.$bReopen.4.1 $bReopen test.db rbu.db {3333 6666 10000}
|
||||
|
||||
reset_db
|
||||
do_test 2.$bReopen.5.0 {
|
||||
execsql {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
|
||||
CREATE INDEX i1 ON t1(b);
|
||||
INSERT INTO t1 VALUES(1, 1, 1);
|
||||
INSERT INTO t1 VALUES(2, 2, 2);
|
||||
INSERT INTO t1 VALUES(3, 3, 3);
|
||||
}
|
||||
create_db_file rbu.db {
|
||||
CREATE TABLE data_t1(a, b, c, rbu_control);
|
||||
INSERT INTO data_t1 VALUES(4, NULL, 4, '.xx');
|
||||
|
||||
CREATE TABLE rbu_count(tbl, cnt);
|
||||
INSERT INTO rbu_count VALUES('data_t1', 1);
|
||||
}
|
||||
} {}
|
||||
do_sp_test 2.$bReopen.5.1 $bReopen test.db rbu.db {10000}
|
||||
|
||||
reset_db
|
||||
do_test 2.$bReopen.6.0 {
|
||||
execsql {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
|
||||
CREATE INDEX i1 ON t1(b);
|
||||
INSERT INTO t1 VALUES(1, 1, 1);
|
||||
INSERT INTO t1 VALUES(2, 2, 2);
|
||||
INSERT INTO t1 VALUES(3, 3, 3);
|
||||
}
|
||||
create_db_file rbu.db {
|
||||
CREATE TABLE data_t1(a, b, c, rbu_control);
|
||||
INSERT INTO data_t1 VALUES(4, 4, 4, 0);
|
||||
INSERT INTO data_t1 VALUES(2, NULL, NULL, 1);
|
||||
INSERT INTO data_t1 VALUES(5, NULL, NULL, 1);
|
||||
}
|
||||
} {}
|
||||
do_sp_test 2.$bReopen.6.1 $bReopen test.db rbu.db {-1 -1 -1 -1 -1 10000}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# The following tests verify that the API works when resuming an update
|
||||
# during the incremental checkpoint stage.
|
||||
#
|
||||
proc do_phase2_test {tn bReopen target rbu nStep} {
|
||||
uplevel [list do_test $tn [subst -nocommands {
|
||||
|
||||
# Build the OAL/WAL file:
|
||||
sqlite3rbu rbu $target $rbu
|
||||
while {[lindex [rbu bp_progress] 0]<10000} {
|
||||
set rc [rbu step]
|
||||
if {"SQLITE_OK" != [set rc]} { rbu close }
|
||||
}
|
||||
|
||||
# Clean up the temp tables and move the *-oal file to *-wal.
|
||||
rbu step
|
||||
rbu step
|
||||
|
||||
for {set i 0} {[set i] < $nStep} {incr i} {
|
||||
if {$bReopen} {
|
||||
rbu close
|
||||
sqlite3rbu rbu $target $rbu
|
||||
}
|
||||
rbu step
|
||||
set res [rbu bp_progress]
|
||||
set expect [expr (1 + [set i]) * 10000 / $nStep]
|
||||
if {[lindex [set res] 1] != [set expect]} {
|
||||
error "Have [set res], expected 10000 [set expect]"
|
||||
}
|
||||
}
|
||||
|
||||
set rc [rbu step]
|
||||
if {[set rc] != "SQLITE_DONE"} {
|
||||
error "Have [set rc], expected SQLITE_DONE"
|
||||
}
|
||||
|
||||
rbu close
|
||||
}] {SQLITE_DONE}]
|
||||
}
|
||||
|
||||
foreach bReopen {0 1} {
|
||||
do_test 3.$bReopen.1.0 {
|
||||
reset_db
|
||||
execsql {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
|
||||
CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
|
||||
CREATE TABLE t3(a INTEGER PRIMARY KEY, b);
|
||||
CREATE TABLE t4(a INTEGER PRIMARY KEY, b);
|
||||
}
|
||||
create_db_file rbu.db {
|
||||
CREATE TABLE data_t1(a, b, rbu_control);
|
||||
CREATE TABLE data_t2(a, b, rbu_control);
|
||||
CREATE TABLE data_t3(a, b, rbu_control);
|
||||
CREATE TABLE data_t4(a, b, rbu_control);
|
||||
INSERT INTO data_t1 VALUES(1, 2, 0);
|
||||
INSERT INTO data_t2 VALUES(1, 2, 0);
|
||||
INSERT INTO data_t3 VALUES(1, 2, 0);
|
||||
INSERT INTO data_t4 VALUES(1, 2, 0);
|
||||
|
||||
CREATE TABLE rbu_count(tbl, cnt);
|
||||
INSERT INTO rbu_count VALUES('data_t1', 1);
|
||||
INSERT INTO rbu_count VALUES('data_t2', 1);
|
||||
INSERT INTO rbu_count VALUES('data_t3', 1);
|
||||
INSERT INTO rbu_count VALUES('data_t4', 1);
|
||||
}
|
||||
} {}
|
||||
do_phase2_test 3.$bReopen.1.1 $bReopen test.db rbu.db 5
|
||||
}
|
||||
|
||||
|
||||
foreach {bReopen} { 0 1 } {
|
||||
foreach {tn tbl} {
|
||||
ipk { CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c) }
|
||||
wr { CREATE TABLE t1(a INT PRIMARY KEY, b, c) WITHOUT ROWID }
|
||||
pk { CREATE TABLE t1(a INT PRIMARY KEY, b, c) }
|
||||
} {
|
||||
|
||||
foreach {tn2 rbusql r1 r3} {
|
||||
1 {
|
||||
CREATE TABLE data0_t1(a, b, c, rbu_control);
|
||||
INSERT INTO data0_t1 VALUES(15, 15, 15, 0);
|
||||
INSERT INTO data0_t1 VALUES(20, 20, 20, 0);
|
||||
CREATE TABLE rbu_count(tbl, cnt);
|
||||
INSERT INTO rbu_count VALUES('data0_t1', 2);
|
||||
}
|
||||
{2500 5000 7500 10000}
|
||||
{1666 3333 5000 6666 8333 10000}
|
||||
|
||||
2 {
|
||||
CREATE TABLE data0_t1(a, b, c, rbu_control);
|
||||
INSERT INTO data0_t1 VALUES(10, 10, 10, 2);
|
||||
CREATE TABLE rbu_count(tbl, cnt);
|
||||
INSERT INTO rbu_count VALUES('data0_t1', 1);
|
||||
}
|
||||
{3333 6666 10000}
|
||||
{2000 4000 6000 8000 10000}
|
||||
|
||||
3 {
|
||||
CREATE TABLE data0_t1(a, b, c, rbu_control);
|
||||
INSERT INTO data0_t1 VALUES(7, 7, 7, 2);
|
||||
INSERT INTO data0_t1 VALUES(10, 10, 10, 2);
|
||||
CREATE TABLE rbu_count(tbl, cnt);
|
||||
INSERT INTO rbu_count VALUES('data0_t1', 2);
|
||||
}
|
||||
{2500 4000 6000 8000 10000}
|
||||
{1666 2500 3750 5000 6250 7500 8750 10000}
|
||||
|
||||
} {
|
||||
|
||||
reset_db ; execsql $tbl
|
||||
do_test 4.$tn.$bReopen.$tn2.0 {
|
||||
execsql {
|
||||
CREATE INDEX t1c ON t1(c);
|
||||
INSERT INTO t1 VALUES(1, 1, 1);
|
||||
INSERT INTO t1 VALUES(5, 5, 5);
|
||||
INSERT INTO t1 VALUES(10, 10, 10);
|
||||
}
|
||||
create_db_file rbu.db $rbusql
|
||||
} {}
|
||||
|
||||
set R(ipk) $r1
|
||||
set R(wr) $r1
|
||||
set R(pk) $r3
|
||||
do_sp_test 4.$tn.$bReopen.$tn2.1 $bReopen test.db rbu.db $R($tn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach {bReopen} { 0 1 } {
|
||||
foreach {tn tbl} {
|
||||
nopk {
|
||||
CREATE TABLE t1(a, b, c);
|
||||
CREATE INDEX t1c ON t1(c);
|
||||
}
|
||||
vtab {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(a, b, c);
|
||||
}
|
||||
} {
|
||||
|
||||
if {$tn=="vtab"} { ifcapable !fts5 break }
|
||||
|
||||
foreach {tn2 rbusql r1 r2} {
|
||||
1 {
|
||||
CREATE TABLE data0_t1(a, b, c, rbu_rowid, rbu_control);
|
||||
INSERT INTO data0_t1 VALUES(15, 15, 15, 4, 0);
|
||||
INSERT INTO data0_t1 VALUES(20, 20, 20, 5, 0);
|
||||
CREATE TABLE rbu_count(tbl, cnt);
|
||||
INSERT INTO rbu_count VALUES('data0_t1', 2);
|
||||
}
|
||||
{2500 5000 7500 10000}
|
||||
{5000 10000}
|
||||
|
||||
2 {
|
||||
CREATE TABLE data0_t1(rbu_rowid, a, b, c, rbu_control);
|
||||
INSERT INTO data0_t1 VALUES(0, 7, 7, 7, 2);
|
||||
INSERT INTO data0_t1 VALUES(2, 10, 10, 10, 2);
|
||||
CREATE TABLE rbu_count(tbl, cnt);
|
||||
INSERT INTO rbu_count VALUES('data0_t1', 2);
|
||||
}
|
||||
{2500 4000 6000 8000 10000}
|
||||
{5000 10000}
|
||||
|
||||
3 {
|
||||
CREATE TABLE data0_t1(rbu_rowid, a, b, c, rbu_control);
|
||||
INSERT INTO data0_t1 VALUES(1, NULL, NULL, NULL, 1);
|
||||
INSERT INTO data0_t1 VALUES(2, NULL, NULL, 7, '..x');
|
||||
CREATE TABLE rbu_count(tbl, cnt);
|
||||
INSERT INTO rbu_count VALUES('data0_t1', 2);
|
||||
}
|
||||
{2500 4000 6000 8000 10000}
|
||||
{5000 10000}
|
||||
} {
|
||||
|
||||
reset_db ; execsql $tbl
|
||||
do_test 5.$tn.$bReopen.$tn2.0 {
|
||||
execsql {
|
||||
INSERT INTO t1 VALUES(1, 1, 1);
|
||||
INSERT INTO t1 VALUES(5, 5, 5);
|
||||
INSERT INTO t1 VALUES(10, 10, 10);
|
||||
}
|
||||
create_db_file rbu.db $rbusql
|
||||
} {}
|
||||
|
||||
set R(nopk) $r1
|
||||
set R(vtab) $r2
|
||||
do_sp_test 5.$tn.$bReopen.$tn2.1 $bReopen test.db rbu.db $R($tn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
392
ext/rbu/rbuvacuum.test
Normal file
392
ext/rbu/rbuvacuum.test
Normal file
@ -0,0 +1,392 @@
|
||||
# 2016 April 15
|
||||
#
|
||||
# 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 contains tests for the RBU module. More specifically, it
|
||||
# contains tests to ensure that the sqlite3rbu_vacuum() API works as
|
||||
# expected.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] rbu_common.tcl]
|
||||
set ::testprefix rbuvacuum
|
||||
|
||||
foreach step {0 1} {
|
||||
|
||||
set ::testprefix rbuvacuum-step=$step
|
||||
reset_db
|
||||
|
||||
# Simplest possible vacuum.
|
||||
do_execsql_test 1.0 {
|
||||
PRAGMA page_size = 1024;
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
|
||||
INSERT INTO t1 VALUES(1, 2, 3);
|
||||
INSERT INTO t1 VALUES(4, 5, 6);
|
||||
INSERT INTO t1 VALUES(7, 8, 9);
|
||||
PRAGMA integrity_check;
|
||||
} {ok}
|
||||
do_rbu_vacuum_test 1.1 $step
|
||||
|
||||
# A vacuum that actually reclaims space.
|
||||
do_execsql_test 1.2.1 {
|
||||
INSERT INTO t1 VALUES(8, randomblob(900), randomblob(900));
|
||||
INSERT INTO t1 VALUES(9, randomblob(900), randomblob(900));
|
||||
INSERT INTO t1 VALUES(10, randomblob(900), randomblob(900));
|
||||
INSERT INTO t1 VALUES(11, randomblob(900), randomblob(900));
|
||||
INSERT INTO t1 VALUES(12, randomblob(900), randomblob(900));
|
||||
PRAGMA page_count;
|
||||
} {12}
|
||||
do_execsql_test 1.2.2 {
|
||||
DELETE FROM t1 WHERE rowid BETWEEN 8 AND 11;
|
||||
PRAGMA page_count;
|
||||
} {12}
|
||||
do_rbu_vacuum_test 1.2.3 $step
|
||||
do_execsql_test 1.2.4 {
|
||||
PRAGMA page_count;
|
||||
} {3}
|
||||
|
||||
# Add an index to the table.
|
||||
do_execsql_test 1.3.1 {
|
||||
CREATE INDEX t1b ON t1(b);
|
||||
INSERT INTO t1 VALUES(13, randomblob(900), randomblob(900));
|
||||
INSERT INTO t1 VALUES(14, randomblob(900), randomblob(900));
|
||||
INSERT INTO t1 VALUES(15, randomblob(900), randomblob(900));
|
||||
INSERT INTO t1 VALUES(16, randomblob(900), randomblob(900));
|
||||
PRAGMA page_count;
|
||||
} {18}
|
||||
do_execsql_test 1.3.2 {
|
||||
DELETE FROM t1 WHERE rowid BETWEEN 12 AND 15;
|
||||
PRAGMA page_count;
|
||||
} {18}
|
||||
do_rbu_vacuum_test 1.3.3 $step
|
||||
do_execsql_test 1.3.4 {
|
||||
PRAGMA page_count;
|
||||
} {5}
|
||||
|
||||
# WITHOUT ROWID table.
|
||||
do_execsql_test 1.4.1 {
|
||||
CREATE TABLE t2(a, b, c, PRIMARY KEY(a, b)) WITHOUT ROWID;
|
||||
|
||||
INSERT INTO t2 VALUES(randomblob(900), 1, randomblob(900));
|
||||
INSERT INTO t2 VALUES(randomblob(900), 2, randomblob(900));
|
||||
INSERT INTO t2 VALUES(randomblob(900), 3, randomblob(900));
|
||||
INSERT INTO t2 VALUES(randomblob(900), 4, randomblob(900));
|
||||
INSERT INTO t2 VALUES(randomblob(900), 6, randomblob(900));
|
||||
INSERT INTO t2 VALUES(randomblob(900), 7, randomblob(900));
|
||||
INSERT INTO t2 VALUES(randomblob(900), 8, randomblob(900));
|
||||
|
||||
DELETE FROM t2 WHERE b BETWEEN 2 AND 7;
|
||||
PRAGMA page_count;
|
||||
} {20}
|
||||
do_rbu_vacuum_test 1.4.2 $step
|
||||
do_execsql_test 1.4.3 {
|
||||
PRAGMA page_count;
|
||||
} {10}
|
||||
|
||||
# WITHOUT ROWID table with an index.
|
||||
do_execsql_test 1.4.1 {
|
||||
CREATE INDEX t2c ON t2(c);
|
||||
|
||||
INSERT INTO t2 VALUES(randomblob(900), 9, randomblob(900));
|
||||
INSERT INTO t2 VALUES(randomblob(900), 10, randomblob(900));
|
||||
INSERT INTO t2 VALUES(randomblob(900), 11, randomblob(900));
|
||||
INSERT INTO t2 VALUES(randomblob(900), 12, randomblob(900));
|
||||
INSERT INTO t2 VALUES(randomblob(900), 13, randomblob(900));
|
||||
|
||||
DELETE FROM t2 WHERE b BETWEEN 8 AND 12;
|
||||
PRAGMA page_count;
|
||||
} {35}
|
||||
do_rbu_vacuum_test 1.4.2 $step
|
||||
do_execsql_test 1.4.3 {
|
||||
PRAGMA page_count;
|
||||
} {15}
|
||||
do_execsql_test 1.4.4 {
|
||||
VACUUM;
|
||||
PRAGMA page_count;
|
||||
} {15}
|
||||
|
||||
do_execsql_test 1.5.1 {
|
||||
CREATE TABLE t3(a, b, c);
|
||||
INSERT INTO t3 VALUES('a', 'b', 'c');
|
||||
INSERT INTO t3 VALUES('d', 'e', 'f');
|
||||
INSERT INTO t3 VALUES('g', 'h', 'i');
|
||||
}
|
||||
do_rbu_vacuum_test 1.5.2 $step
|
||||
do_execsql_test 1.5.3 {
|
||||
SELECT * FROM t3
|
||||
} {a b c d e f g h i}
|
||||
do_execsql_test 1.5.4 {
|
||||
CREATE INDEX t3a ON t3(a);
|
||||
CREATE INDEX t3b ON t3(b);
|
||||
CREATE INDEX t3c ON t3(c);
|
||||
INSERT INTO t3 VALUES('j', 'k', 'l');
|
||||
DELETE FROM t3 WHERE a = 'g';
|
||||
}
|
||||
do_rbu_vacuum_test 1.5.5 $step
|
||||
do_execsql_test 1.5.6 {
|
||||
SELECT rowid, * FROM t3 ORDER BY b
|
||||
} {1 a b c 2 d e f 4 j k l}
|
||||
|
||||
do_execsql_test 1.6.1 {
|
||||
CREATE TABLE t4(a PRIMARY KEY, b, c);
|
||||
INSERT INTO t4 VALUES('a', 'b', 'c');
|
||||
INSERT INTO t4 VALUES('d', 'e', 'f');
|
||||
INSERT INTO t4 VALUES('g', 'h', 'i');
|
||||
}
|
||||
do_rbu_vacuum_test 1.6.2 $step
|
||||
do_execsql_test 1.6.3 {
|
||||
SELECT * FROM t4
|
||||
} {a b c d e f g h i}
|
||||
do_execsql_test 1.6.4 {
|
||||
CREATE INDEX t4a ON t4(a);
|
||||
CREATE INDEX t4b ON t4(b);
|
||||
CREATE INDEX t4c ON t4(c);
|
||||
|
||||
INSERT INTO t4 VALUES('j', 'k', 'l');
|
||||
DELETE FROM t4 WHERE a='g';
|
||||
}
|
||||
do_rbu_vacuum_test 1.6.5 $step
|
||||
do_execsql_test 1.6.6 {
|
||||
SELECT * FROM t4 ORDER BY b
|
||||
} {a b c d e f j k l}
|
||||
|
||||
reset_db
|
||||
do_execsql_test 1.7.0 {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY AUTOINCREMENT, b);
|
||||
INSERT INTO t1 VALUES(NULL, 'one');
|
||||
INSERT INTO t1 VALUES(NULL, 'two');
|
||||
DELETE FROM t1 WHERE a=2;
|
||||
INSERT INTO t1 VALUES(NULL, 'three');
|
||||
INSERT INTO t1 VALUES(NULL, 'four');
|
||||
DELETE FROM t1 WHERE a=4;
|
||||
INSERT INTO t1 VALUES(NULL, 'five');
|
||||
INSERT INTO t1 VALUES(NULL, 'six');
|
||||
DELETE FROM t1 WHERE a=6;
|
||||
SELECT * FROM t1;
|
||||
} {1 one 3 three 5 five}
|
||||
do_rbu_vacuum_test 1.7.1 $step
|
||||
do_execsql_test 1.7.2 {
|
||||
INSERT INTO t1 VALUES(NULL, 'seven');
|
||||
SELECT * FROM t1;
|
||||
} {1 one 3 three 5 five 7 seven}
|
||||
|
||||
reset_db
|
||||
do_execsql_test 1.8.0 {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY AUTOINCREMENT, b);
|
||||
CREATE INDEX i1 ON t1(b);
|
||||
INSERT INTO t1 VALUES(NULL, 'one');
|
||||
INSERT INTO t1 VALUES(NULL, 'two');
|
||||
INSERT INTO t1 VALUES(NULL, 'three');
|
||||
INSERT INTO t1 VALUES(NULL, 'four');
|
||||
INSERT INTO t1 VALUES(NULL, 'five');
|
||||
INSERT INTO t1 VALUES(NULL, 'six');
|
||||
ANALYZE;
|
||||
SELECT * FROM sqlite_stat1;
|
||||
} {t1 i1 {6 1}}
|
||||
do_rbu_vacuum_test 1.8.1 $step
|
||||
do_execsql_test 1.7.2 {
|
||||
SELECT * FROM sqlite_stat1;
|
||||
} {t1 i1 {6 1}}
|
||||
|
||||
reset_db
|
||||
do_execsql_test 1.9.0 {
|
||||
PRAGMA page_size = 8192;
|
||||
PRAGMA auto_vacuum = 2;
|
||||
PRAGMA user_version = 412;
|
||||
PRAGMA application_id = 413;
|
||||
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY AUTOINCREMENT, b);
|
||||
CREATE INDEX i1 ON t1(b);
|
||||
INSERT INTO t1 VALUES(NULL, 'one');
|
||||
INSERT INTO t1 VALUES(NULL, 'two');
|
||||
INSERT INTO t1 VALUES(NULL, 'three');
|
||||
INSERT INTO t1 VALUES(NULL, 'four');
|
||||
INSERT INTO t1 VALUES(NULL, 'five');
|
||||
INSERT INTO t1 VALUES(NULL, 'six');
|
||||
|
||||
PRAGMA main.page_size;
|
||||
PRAGMA main.auto_vacuum;
|
||||
PRAGMA main.user_version;
|
||||
PRAGMA main.application_id;
|
||||
} {8192 2 412 413}
|
||||
|
||||
do_rbu_vacuum_test 1.9.1 $step
|
||||
do_execsql_test 1.9.2 {
|
||||
PRAGMA main.page_size;
|
||||
PRAGMA main.auto_vacuum;
|
||||
PRAGMA main.user_version;
|
||||
PRAGMA main.application_id;
|
||||
} {8192 2 412 413}
|
||||
|
||||
# Vacuum a database with a large sqlite_master table.
|
||||
#
|
||||
reset_db
|
||||
do_test 1.10.1 {
|
||||
for {set i 1} {$i < 50} {incr i} {
|
||||
execsql "PRAGMA page_size = 1024"
|
||||
execsql "CREATE TABLE t$i (a, b, c, PRIMARY KEY(a, b));"
|
||||
execsql "
|
||||
INSERT INTO t$i VALUES(1, 2, 3);
|
||||
INSERT INTO t$i VALUES(4, 5, 6);
|
||||
"
|
||||
}
|
||||
} {}
|
||||
do_rbu_vacuum_test 1.10.2 $step
|
||||
|
||||
# Database with empty tables.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 1.11.1 {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
|
||||
CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
|
||||
CREATE TABLE t3(a INTEGER PRIMARY KEY, b);
|
||||
CREATE TABLE t4(a INTEGER PRIMARY KEY, b);
|
||||
INSERT INTO t4 VALUES(1, 2);
|
||||
}
|
||||
do_rbu_vacuum_test 1.11.2 $step
|
||||
do_execsql_test 1.11.3 {
|
||||
SELECT * FROM t1;
|
||||
SELECT * FROM t2;
|
||||
SELECT * FROM t3;
|
||||
SELECT * FROM t4;
|
||||
} {1 2}
|
||||
reset_db
|
||||
do_execsql_test 1.12.1 {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
|
||||
CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
|
||||
CREATE TABLE t3(a INTEGER PRIMARY KEY, b);
|
||||
CREATE TABLE t4(a INTEGER PRIMARY KEY, b);
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
}
|
||||
do_rbu_vacuum_test 1.12.2 $step
|
||||
do_execsql_test 1.12.3 {
|
||||
SELECT * FROM t1;
|
||||
SELECT * FROM t2;
|
||||
SELECT * FROM t3;
|
||||
SELECT * FROM t4;
|
||||
} {1 2}
|
||||
}
|
||||
set ::testprefix rbuvacuum
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test some error cases:
|
||||
#
|
||||
# 2.1.* the db being vacuumed being in wal mode already.
|
||||
# 2.2.* database modified mid vacuum.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 2.1.0 {
|
||||
CREATE TABLE t1(a, b);
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
INSERT INTO t1 VALUES(3, 4);
|
||||
INSERT INTO t1 VALUES(5, 6);
|
||||
INSERT INTO t1 VALUES(7, 8);
|
||||
PRAGMA journal_mode = wal;
|
||||
INSERT INTO t1 VALUES(9, 10);
|
||||
} wal
|
||||
do_test 2.1.1 {
|
||||
sqlite3rbu_vacuum rbu test.db state.db
|
||||
rbu step
|
||||
} {SQLITE_ERROR}
|
||||
do_test 2.1.2 {
|
||||
list [catch { rbu close } msg] $msg
|
||||
} {1 {SQLITE_ERROR - cannot vacuum wal mode database}}
|
||||
|
||||
reset_db
|
||||
do_execsql_test 2.2.0 {
|
||||
CREATE TABLE tx(a PRIMARY KEY, b BLOB);
|
||||
INSERT INTO tx VALUES(1, randomblob(900));
|
||||
INSERT INTO tx SELECT a+1, randomblob(900) FROM tx;
|
||||
INSERT INTO tx SELECT a+2, randomblob(900) FROM tx;
|
||||
INSERT INTO tx SELECT a+4, randomblob(900) FROM tx;
|
||||
INSERT INTO tx SELECT a+8, randomblob(900) FROM tx;
|
||||
}
|
||||
db_save_and_close
|
||||
for {set i 1} 1 {incr i} {
|
||||
db_restore_and_reopen
|
||||
|
||||
sqlite3rbu_vacuum rbu test.db state.db
|
||||
for {set step 0} {$step<$i} {incr step} { rbu step }
|
||||
rbu close
|
||||
if {[file exists test.db-wal]} break
|
||||
|
||||
execsql { INSERT INTO tx VALUES(20, 20) }
|
||||
|
||||
do_test 2.2.$i.1 {
|
||||
sqlite3rbu_vacuum rbu test.db state.db
|
||||
rbu step
|
||||
} {SQLITE_BUSY}
|
||||
do_test 2.2.$i.2 {
|
||||
list [catch { rbu close } msg] $msg
|
||||
} {1 {SQLITE_BUSY - database modified during rbu vacuum}}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that a database that uses custom collation sequences can be RBU
|
||||
# vacuumed.
|
||||
#
|
||||
reset_db
|
||||
forcedelete state.db
|
||||
proc noop {args} {}
|
||||
proc length_cmp {x y} {
|
||||
set n1 [string length $x]
|
||||
set n2 [string length $y]
|
||||
return [expr $n1 - $n2]
|
||||
}
|
||||
sqlite3_create_collation_v2 db length length_cmp noop
|
||||
|
||||
do_execsql_test 3.0 {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
|
||||
INSERT INTO t1 VALUES(1, 'i');
|
||||
INSERT INTO t1 VALUES(2, 'iiii');
|
||||
INSERT INTO t1 VALUES(3, 'ii');
|
||||
INSERT INTO t1 VALUES(4, 'iii');
|
||||
SELECT a FROM t1 ORDER BY b COLLATE length;
|
||||
} {1 3 4 2}
|
||||
do_execsql_test 3.1 {
|
||||
CREATE INDEX i1 ON t1(b COLLATE length);
|
||||
}
|
||||
|
||||
do_test 3.2 {
|
||||
sqlite3rbu_vacuum rbu test.db state.db
|
||||
while {[rbu step]=="SQLITE_OK"} {}
|
||||
list [catch { rbu close } msg] $msg
|
||||
} {1 {SQLITE_ERROR - no such collation sequence: length}}
|
||||
|
||||
do_test 3.3 {
|
||||
sqlite3rbu_vacuum rbu test.db state.db
|
||||
set db1 [rbu db 0]
|
||||
sqlite3_create_collation_v2 $db1 length length_cmp noop
|
||||
while {[rbu step]=="SQLITE_OK"} {}
|
||||
list [catch { rbu close } msg] $msg
|
||||
} {1 {SQLITE_ERROR - no such collation sequence: length}}
|
||||
|
||||
do_test 3.4 {
|
||||
sqlite3rbu_vacuum rbu test.db state.db
|
||||
set db1 [rbu db 1]
|
||||
sqlite3_create_collation_v2 $db1 length length_cmp noop
|
||||
while {[rbu step]=="SQLITE_OK"} {}
|
||||
list [catch { rbu close } msg] $msg
|
||||
} {1 {SQLITE_ERROR - no such collation sequence: length}}
|
||||
|
||||
do_test 3.5 {
|
||||
sqlite3rbu_vacuum rbu test.db state.db
|
||||
set db1 [rbu db 0]
|
||||
set db2 [rbu db 1]
|
||||
|
||||
sqlite3_create_collation_v2 $db1 length length_cmp noop
|
||||
sqlite3_create_collation_v2 $db2 length length_cmp noop
|
||||
|
||||
while {[rbu step]=="SQLITE_OK"} {}
|
||||
list [catch { rbu close } msg] $msg
|
||||
} {0 SQLITE_DONE}
|
||||
|
||||
catch { db close }
|
||||
finish_test
|
||||
|
||||
204
ext/rbu/rbuvacuum2.test
Normal file
204
ext/rbu/rbuvacuum2.test
Normal file
@ -0,0 +1,204 @@
|
||||
# 2016 June 1
|
||||
#
|
||||
# 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 contains tests for the RBU module. More specifically, it
|
||||
# contains tests to ensure that the sqlite3rbu_vacuum() API works as
|
||||
# expected.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] rbu_common.tcl]
|
||||
|
||||
foreach step {0 1} {
|
||||
set ::testprefix rbuvacuum2-$step
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that a database that contains fts3 tables can be vacuumed.
|
||||
#
|
||||
ifcapable fts3 {
|
||||
reset_db
|
||||
do_execsql_test 1.1 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts3(z, y);
|
||||
INSERT INTO t1 VALUES('fix this issue', 'at some point');
|
||||
}
|
||||
|
||||
do_rbu_vacuum_test 1.2 $step
|
||||
|
||||
do_execsql_test 1.3 {
|
||||
SELECT * FROM t1;
|
||||
} {{fix this issue} {at some point}}
|
||||
|
||||
do_execsql_test 1.4 {
|
||||
SELECT rowid FROM t1 WHERE t1 MATCH 'fix';
|
||||
} {1}
|
||||
|
||||
do_execsql_test 1.5 {
|
||||
INSERT INTO t1 VALUES('a b c', 'd e f');
|
||||
INSERT INTO t1 VALUES('l h i', 'd e f');
|
||||
DELETE FROM t1 WHERE docid = 2;
|
||||
INSERT INTO t1 VALUES('a b c', 'x y z');
|
||||
}
|
||||
|
||||
do_rbu_vacuum_test 1.6 $step
|
||||
do_execsql_test 1.7 {
|
||||
INSERT INTO t1(t1) VALUES('integrity-check');
|
||||
SELECT * FROM t1;
|
||||
} {
|
||||
{fix this issue} {at some point}
|
||||
{l h i} {d e f}
|
||||
{a b c} {x y z}
|
||||
}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that a database that contains fts5 tables can be vacuumed.
|
||||
#
|
||||
ifcapable fts5 {
|
||||
reset_db
|
||||
do_execsql_test 2.1 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(z, y);
|
||||
INSERT INTO t1 VALUES('fix this issue', 'at some point');
|
||||
}
|
||||
|
||||
do_rbu_vacuum_test 2.2 $step
|
||||
|
||||
do_execsql_test 2.3 {
|
||||
SELECT * FROM t1;
|
||||
} {{fix this issue} {at some point}}
|
||||
|
||||
do_execsql_test 2.4 {
|
||||
SELECT rowid FROM t1 ('fix');
|
||||
} {1}
|
||||
|
||||
do_execsql_test 2.5 {
|
||||
INSERT INTO t1 VALUES('a b c', 'd e f');
|
||||
INSERT INTO t1 VALUES('l h i', 'd e f');
|
||||
DELETE FROM t1 WHERE rowid = 2;
|
||||
INSERT INTO t1 VALUES('a b c', 'x y z');
|
||||
}
|
||||
|
||||
do_rbu_vacuum_test 2.6 $step
|
||||
do_execsql_test 2.7 {
|
||||
INSERT INTO t1(t1) VALUES('integrity-check');
|
||||
SELECT * FROM t1;
|
||||
} {
|
||||
{fix this issue} {at some point}
|
||||
{l h i} {d e f}
|
||||
{a b c} {x y z}
|
||||
}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that a database that contains an rtree table can be vacuumed.
|
||||
#
|
||||
ifcapable rtree {
|
||||
reset_db
|
||||
do_execsql_test 3.1 {
|
||||
CREATE VIRTUAL TABLE rt USING rtree(id, x1, x2);
|
||||
INSERT INTO rt VALUES(1, 45, 55);
|
||||
INSERT INTO rt VALUES(2, 50, 60);
|
||||
INSERT INTO rt VALUES(3, 55, 65);
|
||||
}
|
||||
|
||||
do_rbu_vacuum_test 3.2 $step
|
||||
|
||||
do_execsql_test 3.3 {
|
||||
SELECT * FROM rt;
|
||||
} {1 45.0 55.0 2 50.0 60.0 3 55.0 65.0}
|
||||
|
||||
do_execsql_test 3.4.1 {
|
||||
SELECT rowid FROM rt WHERE x2>51 AND x1 < 51
|
||||
} {1 2}
|
||||
do_execsql_test 3.4.2 {
|
||||
SELECT rowid FROM rt WHERE x2>59 AND x1 < 59
|
||||
} {2 3}
|
||||
|
||||
do_rbu_vacuum_test 3.5 $step
|
||||
|
||||
do_execsql_test 3.6.1 {
|
||||
SELECT rowid FROM rt WHERE x2>51 AND x1 < 51
|
||||
} {1 2}
|
||||
do_execsql_test 3.6.2 {
|
||||
SELECT rowid FROM rt WHERE x2>59 AND x1 < 59
|
||||
} {2 3}
|
||||
}
|
||||
|
||||
ifcapable trigger {
|
||||
reset_db
|
||||
do_execsql_test 4.1 {
|
||||
CREATE TABLE t1(a, b, c);
|
||||
INSERT INTO t1 VALUES(1, 2, 3);
|
||||
CREATE VIEW v1 AS SELECT * FROM t1;
|
||||
CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN SELECT 1; END;
|
||||
}
|
||||
|
||||
do_execsql_test 4.2 {
|
||||
SELECT * FROM sqlite_master;
|
||||
} {
|
||||
table t1 t1 2 {CREATE TABLE t1(a, b, c)}
|
||||
view v1 v1 0 {CREATE VIEW v1 AS SELECT * FROM t1}
|
||||
trigger tr1 t1 0 {CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN SELECT 1; END}
|
||||
}
|
||||
|
||||
do_rbu_vacuum_test 4.3 $step
|
||||
do_execsql_test 4.4 {
|
||||
SELECT * FROM sqlite_master;
|
||||
} {
|
||||
table t1 t1 2 {CREATE TABLE t1(a, b, c)}
|
||||
view v1 v1 0 {CREATE VIEW v1 AS SELECT * FROM t1}
|
||||
trigger tr1 t1 0 {CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN SELECT 1; END}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that passing a NULL value as the second argument to
|
||||
# sqlite3rbu_vacuum() causes it to:
|
||||
#
|
||||
# * Use <database>-vacuum as the state db, and
|
||||
# * Set the state db permissions to the same as those on the db file.
|
||||
#
|
||||
db close
|
||||
if {$::tcl_platform(platform)=="unix"} {
|
||||
forcedelete test.db
|
||||
|
||||
sqlite3 db test.db
|
||||
do_execsql_test 5.0 {
|
||||
CREATE TABLE t1(a, b);
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
INSERT INTO t1 VALUES(3, 4);
|
||||
INSERT INTO t1 VALUES(5, 6);
|
||||
INSERT INTO t1 VALUES(7, 8);
|
||||
}
|
||||
db close
|
||||
|
||||
foreach {tn perm} {
|
||||
1 00755
|
||||
2 00666
|
||||
3 00644
|
||||
4 00444
|
||||
} {
|
||||
forcedelete test.db-vacuum
|
||||
|
||||
do_test 5.$tn.1 {
|
||||
file attributes test.db -permissions $perm
|
||||
sqlite3rbu_vacuum rbu test.db
|
||||
rbu step
|
||||
} {SQLITE_OK}
|
||||
|
||||
do_test 5.$tn.2 { file exists test.db-vacuum } 1
|
||||
do_test 5.$tn.3 { file attributes test.db-vacuum -permissions} $perm
|
||||
rbu close
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
1225
ext/rbu/sqlite3rbu.c
1225
ext/rbu/sqlite3rbu.c
File diff suppressed because it is too large
Load Diff
@ -104,7 +104,7 @@
|
||||
** may also be named data<integer>_<target>, where <integer> is any sequence
|
||||
** of zero or more numeric characters (0-9). This can be significant because
|
||||
** tables within the RBU database are always processed in order sorted by
|
||||
** name. By judicious selection of the the <integer> portion of the names
|
||||
** name. By judicious selection of the <integer> portion of the names
|
||||
** of the RBU tables the user can therefore control the order in which they
|
||||
** are processed. This can be useful, for example, to ensure that "external
|
||||
** content" FTS4 tables are updated before their underlying content tables.
|
||||
@ -314,6 +314,44 @@ sqlite3rbu *sqlite3rbu_open(
|
||||
const char *zState
|
||||
);
|
||||
|
||||
/*
|
||||
** Open an RBU handle to perform an RBU vacuum on database file zTarget.
|
||||
** An RBU vacuum is similar to SQLite's built-in VACUUM command, except
|
||||
** that it can be suspended and resumed like an RBU update.
|
||||
**
|
||||
** The second argument to this function identifies a database in which
|
||||
** to store the state of the RBU vacuum operation if it is suspended. The
|
||||
** first time sqlite3rbu_vacuum() is called, to start an RBU vacuum
|
||||
** operation, the state database should either not exist or be empty
|
||||
** (contain no tables). If an RBU vacuum is suspended by calling
|
||||
** sqlite3rbu_close() on the RBU handle before sqlite3rbu_step() has
|
||||
** returned SQLITE_DONE, the vacuum state is stored in the state database.
|
||||
** The vacuum can be resumed by calling this function to open a new RBU
|
||||
** handle specifying the same target and state databases.
|
||||
**
|
||||
** If the second argument passed to this function is NULL, then the
|
||||
** name of the state database is "<database>-vacuum", where <database>
|
||||
** is the name of the target database file. In this case, on UNIX, if the
|
||||
** state database is not already present in the file-system, it is created
|
||||
** with the same permissions as the target db is made.
|
||||
**
|
||||
** This function does not delete the state database after an RBU vacuum
|
||||
** is completed, even if it created it. However, if the call to
|
||||
** sqlite3rbu_close() returns any value other than SQLITE_OK, the contents
|
||||
** of the state tables within the state database are zeroed. This way,
|
||||
** the next call to sqlite3rbu_vacuum() opens a handle that starts a
|
||||
** new RBU vacuum operation.
|
||||
**
|
||||
** As with sqlite3rbu_open(), Zipvfs users should rever to the comment
|
||||
** describing the sqlite3rbu_create_vfs() API function below for
|
||||
** a description of the complications associated with using RBU with
|
||||
** zipvfs databases.
|
||||
*/
|
||||
sqlite3rbu *sqlite3rbu_vacuum(
|
||||
const char *zTarget,
|
||||
const char *zState
|
||||
);
|
||||
|
||||
/*
|
||||
** Internally, each RBU connection uses a separate SQLite database
|
||||
** connection to access the target and rbu update databases. This
|
||||
@ -400,6 +438,86 @@ int sqlite3rbu_close(sqlite3rbu *pRbu, char **pzErrmsg);
|
||||
*/
|
||||
sqlite3_int64 sqlite3rbu_progress(sqlite3rbu *pRbu);
|
||||
|
||||
/*
|
||||
** Obtain permyriadage (permyriadage is to 10000 as percentage is to 100)
|
||||
** progress indications for the two stages of an RBU update. This API may
|
||||
** be useful for driving GUI progress indicators and similar.
|
||||
**
|
||||
** An RBU update is divided into two stages:
|
||||
**
|
||||
** * Stage 1, in which changes are accumulated in an oal/wal file, and
|
||||
** * Stage 2, in which the contents of the wal file are copied into the
|
||||
** main database.
|
||||
**
|
||||
** The update is visible to non-RBU clients during stage 2. During stage 1
|
||||
** non-RBU reader clients may see the original database.
|
||||
**
|
||||
** If this API is called during stage 2 of the update, output variable
|
||||
** (*pnOne) is set to 10000 to indicate that stage 1 has finished and (*pnTwo)
|
||||
** to a value between 0 and 10000 to indicate the permyriadage progress of
|
||||
** stage 2. A value of 5000 indicates that stage 2 is half finished,
|
||||
** 9000 indicates that it is 90% finished, and so on.
|
||||
**
|
||||
** If this API is called during stage 1 of the update, output variable
|
||||
** (*pnTwo) is set to 0 to indicate that stage 2 has not yet started. The
|
||||
** value to which (*pnOne) is set depends on whether or not the RBU
|
||||
** database contains an "rbu_count" table. The rbu_count table, if it
|
||||
** exists, must contain the same columns as the following:
|
||||
**
|
||||
** CREATE TABLE rbu_count(tbl TEXT PRIMARY KEY, cnt INTEGER) WITHOUT ROWID;
|
||||
**
|
||||
** There must be one row in the table for each source (data_xxx) table within
|
||||
** the RBU database. The 'tbl' column should contain the name of the source
|
||||
** table. The 'cnt' column should contain the number of rows within the
|
||||
** source table.
|
||||
**
|
||||
** If the rbu_count table is present and populated correctly and this
|
||||
** API is called during stage 1, the *pnOne output variable is set to the
|
||||
** permyriadage progress of the same stage. If the rbu_count table does
|
||||
** not exist, then (*pnOne) is set to -1 during stage 1. If the rbu_count
|
||||
** table exists but is not correctly populated, the value of the *pnOne
|
||||
** output variable during stage 1 is undefined.
|
||||
*/
|
||||
void sqlite3rbu_bp_progress(sqlite3rbu *pRbu, int *pnOne, int *pnTwo);
|
||||
|
||||
/*
|
||||
** Obtain an indication as to the current stage of an RBU update or vacuum.
|
||||
** This function always returns one of the SQLITE_RBU_STATE_XXX constants
|
||||
** defined in this file. Return values should be interpreted as follows:
|
||||
**
|
||||
** SQLITE_RBU_STATE_OAL:
|
||||
** RBU is currently building a *-oal file. The next call to sqlite3rbu_step()
|
||||
** may either add further data to the *-oal file, or compute data that will
|
||||
** be added by a subsequent call.
|
||||
**
|
||||
** SQLITE_RBU_STATE_MOVE:
|
||||
** RBU has finished building the *-oal file. The next call to sqlite3rbu_step()
|
||||
** will move the *-oal file to the equivalent *-wal path. If the current
|
||||
** operation is an RBU update, then the updated version of the database
|
||||
** file will become visible to ordinary SQLite clients following the next
|
||||
** call to sqlite3rbu_step().
|
||||
**
|
||||
** SQLITE_RBU_STATE_CHECKPOINT:
|
||||
** RBU is currently performing an incremental checkpoint. The next call to
|
||||
** sqlite3rbu_step() will copy a page of data from the *-wal file into
|
||||
** the target database file.
|
||||
**
|
||||
** SQLITE_RBU_STATE_DONE:
|
||||
** The RBU operation has finished. Any subsequent calls to sqlite3rbu_step()
|
||||
** will immediately return SQLITE_DONE.
|
||||
**
|
||||
** SQLITE_RBU_STATE_ERROR:
|
||||
** An error has occurred. Any subsequent calls to sqlite3rbu_step() will
|
||||
** immediately return the SQLite error code associated with the error.
|
||||
*/
|
||||
#define SQLITE_RBU_STATE_OAL 1
|
||||
#define SQLITE_RBU_STATE_MOVE 2
|
||||
#define SQLITE_RBU_STATE_CHECKPOINT 3
|
||||
#define SQLITE_RBU_STATE_DONE 4
|
||||
#define SQLITE_RBU_STATE_ERROR 5
|
||||
|
||||
int sqlite3rbu_state(sqlite3rbu *pRbu);
|
||||
|
||||
/*
|
||||
** Create an RBU VFS named zName that accesses the underlying file-system
|
||||
** via existing VFS zParent. Or, if the zParent parameter is passed NULL,
|
||||
|
||||
@ -17,11 +17,19 @@
|
||||
#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_RBU)
|
||||
|
||||
#include "sqlite3rbu.h"
|
||||
#include <tcl.h>
|
||||
#if defined(INCLUDE_SQLITE_TCL_H)
|
||||
# include "sqlite_tcl.h"
|
||||
#else
|
||||
# include "tcl.h"
|
||||
# ifndef SQLITE_TCLAPI
|
||||
# define SQLITE_TCLAPI
|
||||
# endif
|
||||
#endif
|
||||
#include <assert.h>
|
||||
|
||||
/* From main.c (apparently...) */
|
||||
/* From main.c */
|
||||
extern const char *sqlite3ErrName(int);
|
||||
extern int sqlite3TestMakePointerStr(Tcl_Interp*, char*, void*);
|
||||
|
||||
void test_rbu_delta(sqlite3_context *pCtx, int nArg, sqlite3_value **apVal){
|
||||
Tcl_Interp *interp = (Tcl_Interp*)sqlite3_user_data(pCtx);
|
||||
@ -48,7 +56,7 @@ void test_rbu_delta(sqlite3_context *pCtx, int nArg, sqlite3_value **apVal){
|
||||
}
|
||||
|
||||
|
||||
static int test_sqlite3rbu_cmd(
|
||||
static int SQLITE_TCLAPI test_sqlite3rbu_cmd(
|
||||
ClientData clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
@ -66,6 +74,10 @@ static int test_sqlite3rbu_cmd(
|
||||
{"create_rbu_delta", 2, ""}, /* 2 */
|
||||
{"savestate", 2, ""}, /* 3 */
|
||||
{"dbMain_eval", 3, "SQL"}, /* 4 */
|
||||
{"bp_progress", 2, ""}, /* 5 */
|
||||
{"db", 3, "RBU"}, /* 6 */
|
||||
{"state", 2, ""}, /* 7 */
|
||||
{"progress", 2, ""}, /* 8 */
|
||||
{0,0,0}
|
||||
};
|
||||
int iCmd;
|
||||
@ -136,6 +148,46 @@ static int test_sqlite3rbu_cmd(
|
||||
break;
|
||||
}
|
||||
|
||||
case 5: /* bp_progress */ {
|
||||
int one, two;
|
||||
Tcl_Obj *pObj;
|
||||
sqlite3rbu_bp_progress(pRbu, &one, &two);
|
||||
|
||||
pObj = Tcl_NewObj();
|
||||
Tcl_ListObjAppendElement(interp, pObj, Tcl_NewIntObj(one));
|
||||
Tcl_ListObjAppendElement(interp, pObj, Tcl_NewIntObj(two));
|
||||
Tcl_SetObjResult(interp, pObj);
|
||||
break;
|
||||
}
|
||||
|
||||
case 6: /* db */ {
|
||||
int bArg;
|
||||
if( Tcl_GetBooleanFromObj(interp, objv[2], &bArg) ){
|
||||
ret = TCL_ERROR;
|
||||
}else{
|
||||
char zBuf[50];
|
||||
sqlite3 *db = sqlite3rbu_db(pRbu, bArg);
|
||||
if( sqlite3TestMakePointerStr(interp, zBuf, (void*)db) ){
|
||||
ret = TCL_ERROR;
|
||||
}else{
|
||||
Tcl_SetResult(interp, zBuf, TCL_VOLATILE);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 7: /* state */ {
|
||||
const char *aRes[] = { 0, "oal", "move", "checkpoint", "done", "error" };
|
||||
int eState = sqlite3rbu_state(pRbu);
|
||||
assert( eState>0 && eState<=5 );
|
||||
Tcl_SetResult(interp, (char*)aRes[eState], TCL_STATIC);
|
||||
break;
|
||||
}
|
||||
case 8: /* progress */ {
|
||||
sqlite3_int64 nStep = sqlite3rbu_progress(pRbu);
|
||||
Tcl_SetObjResult(interp, Tcl_NewWideIntObj(nStep));
|
||||
break;
|
||||
}
|
||||
|
||||
default: /* seems unlikely */
|
||||
assert( !"cannot happen" );
|
||||
break;
|
||||
@ -147,7 +199,7 @@ static int test_sqlite3rbu_cmd(
|
||||
/*
|
||||
** Tclcmd: sqlite3rbu CMD <target-db> <rbu-db> ?<state-db>?
|
||||
*/
|
||||
static int test_sqlite3rbu(
|
||||
static int SQLITE_TCLAPI test_sqlite3rbu(
|
||||
ClientData clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
@ -174,10 +226,38 @@ static int test_sqlite3rbu(
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Tclcmd: sqlite3rbu_vacuum CMD <target-db> <state-db>
|
||||
*/
|
||||
static int SQLITE_TCLAPI test_sqlite3rbu_vacuum(
|
||||
ClientData clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
sqlite3rbu *pRbu = 0;
|
||||
const char *zCmd;
|
||||
const char *zTarget;
|
||||
const char *zStateDb = 0;
|
||||
|
||||
if( objc!=3 && objc!=4 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "NAME TARGET-DB ?STATE-DB?");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
zCmd = Tcl_GetString(objv[1]);
|
||||
zTarget = Tcl_GetString(objv[2]);
|
||||
if( objc==4 ) zStateDb = Tcl_GetString(objv[3]);
|
||||
|
||||
pRbu = sqlite3rbu_vacuum(zTarget, zStateDb);
|
||||
Tcl_CreateObjCommand(interp, zCmd, test_sqlite3rbu_cmd, (ClientData)pRbu, 0);
|
||||
Tcl_SetObjResult(interp, objv[1]);
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Tclcmd: sqlite3rbu_create_vfs ?-default? NAME PARENT
|
||||
*/
|
||||
static int test_sqlite3rbu_create_vfs(
|
||||
static int SQLITE_TCLAPI test_sqlite3rbu_create_vfs(
|
||||
ClientData clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
@ -212,7 +292,7 @@ static int test_sqlite3rbu_create_vfs(
|
||||
/*
|
||||
** Tclcmd: sqlite3rbu_destroy_vfs NAME
|
||||
*/
|
||||
static int test_sqlite3rbu_destroy_vfs(
|
||||
static int SQLITE_TCLAPI test_sqlite3rbu_destroy_vfs(
|
||||
ClientData clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
@ -233,7 +313,7 @@ static int test_sqlite3rbu_destroy_vfs(
|
||||
/*
|
||||
** Tclcmd: sqlite3rbu_internal_test
|
||||
*/
|
||||
static int test_sqlite3rbu_internal_test(
|
||||
static int SQLITE_TCLAPI test_sqlite3rbu_internal_test(
|
||||
ClientData clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
@ -261,6 +341,7 @@ int SqliteRbu_Init(Tcl_Interp *interp){
|
||||
Tcl_ObjCmdProc *xProc;
|
||||
} aObjCmd[] = {
|
||||
{ "sqlite3rbu", test_sqlite3rbu },
|
||||
{ "sqlite3rbu_vacuum", test_sqlite3rbu_vacuum },
|
||||
{ "sqlite3rbu_create_vfs", test_sqlite3rbu_create_vfs },
|
||||
{ "sqlite3rbu_destroy_vfs", test_sqlite3rbu_destroy_vfs },
|
||||
{ "sqlite3rbu_internal_test", test_sqlite3rbu_internal_test },
|
||||
@ -273,7 +354,11 @@ int SqliteRbu_Init(Tcl_Interp *interp){
|
||||
}
|
||||
|
||||
#else
|
||||
#include <tcl.h>
|
||||
#if defined(INCLUDE_SQLITE_TCL_H)
|
||||
# include "sqlite_tcl.h"
|
||||
#else
|
||||
# include "tcl.h"
|
||||
#endif
|
||||
int SqliteRbu_Init(Tcl_Interp *interp){ return TCL_OK; }
|
||||
#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_RBU) */
|
||||
#endif /* defined(SQLITE_TEST) */
|
||||
|
||||
@ -1542,7 +1542,7 @@ static int rtreeFilter(
|
||||
if( idxNum==1 ){
|
||||
/* Special case - lookup by rowid. */
|
||||
RtreeNode *pLeaf; /* Leaf on which the required cell resides */
|
||||
RtreeSearchPoint *p; /* Search point for the the leaf */
|
||||
RtreeSearchPoint *p; /* Search point for the leaf */
|
||||
i64 iRowid = sqlite3_value_int64(argv[0]);
|
||||
i64 iNode = 0;
|
||||
rc = findLeafNode(pRtree, iRowid, &pLeaf, &iNode);
|
||||
@ -1741,7 +1741,7 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
|
||||
nRow = pRtree->nRowEst / (iIdx + 1);
|
||||
nRow = pRtree->nRowEst >> (iIdx/2);
|
||||
pIdxInfo->estimatedCost = (double)6.0 * (double)nRow;
|
||||
setEstimatedRows(pIdxInfo, nRow);
|
||||
|
||||
@ -2800,6 +2800,53 @@ static RtreeValue rtreeValueUp(sqlite3_value *v){
|
||||
}
|
||||
#endif /* !defined(SQLITE_RTREE_INT_ONLY) */
|
||||
|
||||
/*
|
||||
** A constraint has failed while inserting a row into an rtree table.
|
||||
** Assuming no OOM error occurs, this function sets the error message
|
||||
** (at pRtree->base.zErrMsg) to an appropriate value and returns
|
||||
** SQLITE_CONSTRAINT.
|
||||
**
|
||||
** Parameter iCol is the index of the leftmost column involved in the
|
||||
** constraint failure. If it is 0, then the constraint that failed is
|
||||
** the unique constraint on the id column. Otherwise, it is the rtree
|
||||
** (c1<=c2) constraint on columns iCol and iCol+1 that has failed.
|
||||
**
|
||||
** If an OOM occurs, SQLITE_NOMEM is returned instead of SQLITE_CONSTRAINT.
|
||||
*/
|
||||
static int rtreeConstraintError(Rtree *pRtree, int iCol){
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
char *zSql;
|
||||
int rc;
|
||||
|
||||
assert( iCol==0 || iCol%2 );
|
||||
zSql = sqlite3_mprintf("SELECT * FROM %Q.%Q", pRtree->zDb, pRtree->zName);
|
||||
if( zSql ){
|
||||
rc = sqlite3_prepare_v2(pRtree->db, zSql, -1, &pStmt, 0);
|
||||
}else{
|
||||
rc = SQLITE_NOMEM;
|
||||
}
|
||||
sqlite3_free(zSql);
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
if( iCol==0 ){
|
||||
const char *zCol = sqlite3_column_name(pStmt, 0);
|
||||
pRtree->base.zErrMsg = sqlite3_mprintf(
|
||||
"UNIQUE constraint failed: %s.%s", pRtree->zName, zCol
|
||||
);
|
||||
}else{
|
||||
const char *zCol1 = sqlite3_column_name(pStmt, iCol);
|
||||
const char *zCol2 = sqlite3_column_name(pStmt, iCol+1);
|
||||
pRtree->base.zErrMsg = sqlite3_mprintf(
|
||||
"rtree constraint failed: %s.(%s<=%s)", pRtree->zName, zCol1, zCol2
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
sqlite3_finalize(pStmt);
|
||||
return (rc==SQLITE_OK ? SQLITE_CONSTRAINT : rc);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** The xUpdate method for rtree module virtual tables.
|
||||
@ -2850,7 +2897,7 @@ static int rtreeUpdate(
|
||||
cell.aCoord[ii].f = rtreeValueDown(azData[ii+3]);
|
||||
cell.aCoord[ii+1].f = rtreeValueUp(azData[ii+4]);
|
||||
if( cell.aCoord[ii].f>cell.aCoord[ii+1].f ){
|
||||
rc = SQLITE_CONSTRAINT;
|
||||
rc = rtreeConstraintError(pRtree, ii+1);
|
||||
goto constraint;
|
||||
}
|
||||
}
|
||||
@ -2861,7 +2908,7 @@ static int rtreeUpdate(
|
||||
cell.aCoord[ii].i = sqlite3_value_int(azData[ii+3]);
|
||||
cell.aCoord[ii+1].i = sqlite3_value_int(azData[ii+4]);
|
||||
if( cell.aCoord[ii].i>cell.aCoord[ii+1].i ){
|
||||
rc = SQLITE_CONSTRAINT;
|
||||
rc = rtreeConstraintError(pRtree, ii+1);
|
||||
goto constraint;
|
||||
}
|
||||
}
|
||||
@ -2882,7 +2929,7 @@ static int rtreeUpdate(
|
||||
if( sqlite3_vtab_on_conflict(pRtree->db)==SQLITE_REPLACE ){
|
||||
rc = rtreeDeleteRowid(pRtree, cell.iRowid);
|
||||
}else{
|
||||
rc = SQLITE_CONSTRAINT;
|
||||
rc = rtreeConstraintError(pRtree, 0);
|
||||
goto constraint;
|
||||
}
|
||||
}
|
||||
@ -2965,6 +3012,13 @@ static int rtreeQueryStat1(sqlite3 *db, Rtree *pRtree){
|
||||
int rc;
|
||||
i64 nRow = 0;
|
||||
|
||||
rc = sqlite3_table_column_metadata(
|
||||
db, pRtree->zDb, "sqlite_stat1",0,0,0,0,0,0
|
||||
);
|
||||
if( rc!=SQLITE_OK ){
|
||||
pRtree->nRowEst = RTREE_DEFAULT_ROWEST;
|
||||
return rc==SQLITE_ERROR ? SQLITE_OK : rc;
|
||||
}
|
||||
zSql = sqlite3_mprintf(zFmt, pRtree->zDb, pRtree->zName);
|
||||
if( zSql==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
|
||||
@ -194,13 +194,13 @@ do_test rtree-2.1.3 {
|
||||
|
||||
do_test rtree-2.2.1 {
|
||||
catchsql { INSERT INTO t1 VALUES(2, 1, 3, 2, 4) }
|
||||
} {1 {constraint failed}}
|
||||
} {1 {UNIQUE constraint failed: t1.ii}}
|
||||
do_test rtree-2.2.2 {
|
||||
catchsql { INSERT INTO t1 VALUES(4, 1, 3, 4, 2) }
|
||||
} {1 {constraint failed}}
|
||||
} {1 {rtree constraint failed: t1.(y1<=y2)}}
|
||||
do_test rtree-2.2.3 {
|
||||
catchsql { INSERT INTO t1 VALUES(4, 3, 1, 2, 4) }
|
||||
} {1 {constraint failed}}
|
||||
} {1 {rtree constraint failed: t1.(x1<=x2)}}
|
||||
do_test rtree-2.2.4 {
|
||||
execsql { SELECT ii FROM t1 ORDER BY ii }
|
||||
} {1 2 3}
|
||||
@ -236,7 +236,7 @@ do_test rtree-3.1.3 {
|
||||
# Test the constraint on the coordinates (c[i]<=c[i+1] where (i%2==0)):
|
||||
do_test rtree-3.2.1 {
|
||||
catchsql { INSERT INTO t1 VALUES(7, 2, 6, 4, 3) }
|
||||
} {1 {constraint failed}}
|
||||
} {1 {rtree constraint failed: t1.(y1<=y2)}}
|
||||
do_test rtree-3.2.2 {
|
||||
catchsql { INSERT INTO t1 VALUES(8, 2, 6, 3, 3) }
|
||||
} {0 {}}
|
||||
@ -490,11 +490,11 @@ foreach {tn sql_template testdata} {
|
||||
}
|
||||
|
||||
4 "INSERT %CONF% INTO t1 VALUES(2, 7, 6, 7, 7)" {
|
||||
ROLLBACK 0 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6}
|
||||
ABORT 0 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
ROLLBACK 0 2 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6}
|
||||
ABORT 0 2 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
IGNORE 0 0 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
FAIL 0 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
REPLACE 0 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
FAIL 0 2 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
REPLACE 0 2 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
}
|
||||
|
||||
} {
|
||||
@ -510,7 +510,9 @@ foreach {tn sql_template testdata} {
|
||||
}
|
||||
|
||||
set res(0) {0 {}}
|
||||
set res(1) {1 {constraint failed}}
|
||||
set res(1) {1 {UNIQUE constraint failed: t1.idx}}
|
||||
set res(2) {1 {rtree constraint failed: t1.(x1<=x2)}}
|
||||
|
||||
do_catchsql_test $testname.1 $sql $res($error)
|
||||
do_test $testname.2 [list sql_uses_stmt db $sql] $uses
|
||||
do_execsql_test $testname.3 { SELECT * FROM t1 ORDER BY idx } $data
|
||||
|
||||
@ -47,7 +47,8 @@ ifcapable !rtree {
|
||||
#
|
||||
# rtree3-8: Test OOM while registering the r-tree module with sqlite.
|
||||
#
|
||||
|
||||
# rtree3-11: OOM following a constraint failure
|
||||
#
|
||||
do_faultsim_test rtree3-1 -faults oom* -prep {
|
||||
faultsim_delete_and_reopen
|
||||
} -body {
|
||||
@ -234,4 +235,32 @@ do_faultsim_test rtree3-10 -faults oom-* -prep {
|
||||
faultsim_test_result {0 2}
|
||||
}
|
||||
|
||||
|
||||
do_test rtree3-11.prep {
|
||||
faultsim_delete_and_reopen
|
||||
execsql {
|
||||
CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2);
|
||||
INSERT INTO rt VALUES(1, 2, 3, 4, 5);
|
||||
}
|
||||
faultsim_save_and_close
|
||||
} {}
|
||||
do_faultsim_test rtree3-10.1 -faults oom-* -prep {
|
||||
faultsim_restore_and_reopen
|
||||
execsql { SELECT * FROM rt }
|
||||
} -body {
|
||||
execsql { INSERT INTO rt VALUES(1, 2, 3, 4, 5) }
|
||||
} -test {
|
||||
faultsim_test_result {1 {UNIQUE constraint failed: rt.ii}} \
|
||||
{1 {constraint failed}}
|
||||
}
|
||||
do_faultsim_test rtree3-10.2 -faults oom-* -prep {
|
||||
faultsim_restore_and_reopen
|
||||
execsql { SELECT * FROM rt }
|
||||
} -body {
|
||||
execsql { INSERT INTO rt VALUES(2, 2, 3, 5, 4) }
|
||||
} -test {
|
||||
faultsim_test_result {1 {rtree constraint failed: rt.(y1<=y2)}} \
|
||||
{1 {constraint failed}}
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
@ -350,7 +350,3 @@ do_eqp_execsql_test 7.4 {
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
66
ext/rtree/rtreeG.test
Normal file
66
ext/rtree/rtreeG.test
Normal file
@ -0,0 +1,66 @@
|
||||
# 2016-05-32
|
||||
#
|
||||
# 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 contains tests for the r-tree module.
|
||||
#
|
||||
# Verify that no invalid SQL is run during initialization
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !rtree { finish_test ; return }
|
||||
|
||||
db close
|
||||
sqlite3_shutdown
|
||||
test_sqlite3_log [list lappend ::log]
|
||||
set ::log [list]
|
||||
sqlite3 db test.db
|
||||
|
||||
|
||||
set ::log {}
|
||||
do_execsql_test rtreeG-1.1 {
|
||||
CREATE VIRTUAL TABLE t1 USING rtree(id,x0,x1,y0,y1);
|
||||
} {}
|
||||
do_test rtreeG-1.1log {
|
||||
set ::log
|
||||
} {}
|
||||
|
||||
do_execsql_test rtreeG-1.2 {
|
||||
INSERT INTO t1 VALUES(1,10,15,5,23),(2,20,21,5,23),(3,10,15,20,30);
|
||||
SELECT id from t1 WHERE x0>8 AND x1<16 AND y0>2 AND y1<25;
|
||||
} {1}
|
||||
do_test rtreeG-1.2log {
|
||||
set ::log
|
||||
} {}
|
||||
|
||||
db close
|
||||
sqlite3 db test.db
|
||||
do_execsql_test rtreeG-1.3 {
|
||||
SELECT id from t1 WHERE x0>8 AND x1<16 AND y0>2 AND y1<25;
|
||||
} {1}
|
||||
do_test rtreeG-1.3log {
|
||||
set ::log
|
||||
} {}
|
||||
|
||||
do_execsql_test rtreeG-1.4 {
|
||||
DROP TABLE t1;
|
||||
} {}
|
||||
do_test rtreeG-1.4log {
|
||||
set ::log
|
||||
} {}
|
||||
|
||||
db close
|
||||
sqlite3_shutdown
|
||||
test_sqlite3_log
|
||||
sqlite3_initialize
|
||||
sqlite3 db test.db
|
||||
|
||||
finish_test
|
||||
416
ext/session/changeset.c
Normal file
416
ext/session/changeset.c
Normal file
@ -0,0 +1,416 @@
|
||||
/*
|
||||
** 2014-08-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 file contains code to implement the "changeset" command line
|
||||
** utility for displaying and transforming changesets generated by
|
||||
** the Sessions extension.
|
||||
*/
|
||||
#include "sqlite3.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
|
||||
|
||||
/*
|
||||
** Show a usage message on stderr then quit.
|
||||
*/
|
||||
static void usage(const char *argv0){
|
||||
fprintf(stderr, "Usage: %s FILENAME COMMAND ...\n", argv0);
|
||||
fprintf(stderr,
|
||||
"COMMANDs:\n"
|
||||
" apply DB Apply the changeset to database file DB\n"
|
||||
" concat FILE2 OUT Concatenate FILENAME and FILE2 into OUT\n"
|
||||
" dump Show the complete content of the changeset\n"
|
||||
" invert OUT Write an inverted changeset into file OUT\n"
|
||||
" sql Give a pseudo-SQL rendering of the changeset\n"
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/*
|
||||
** Read the content of a disk file into an in-memory buffer
|
||||
*/
|
||||
static void readFile(const char *zFilename, int *pSz, void **ppBuf){
|
||||
FILE *f;
|
||||
int sz;
|
||||
void *pBuf;
|
||||
f = fopen(zFilename, "rb");
|
||||
if( f==0 ){
|
||||
fprintf(stderr, "cannot open \"%s\" for reading\n", zFilename);
|
||||
exit(1);
|
||||
}
|
||||
fseek(f, 0, SEEK_END);
|
||||
sz = (int)ftell(f);
|
||||
rewind(f);
|
||||
pBuf = sqlite3_malloc( sz ? sz : 1 );
|
||||
if( pBuf==0 ){
|
||||
fprintf(stderr, "cannot allocate %d to hold content of \"%s\"\n",
|
||||
sz, zFilename);
|
||||
exit(1);
|
||||
}
|
||||
if( sz>0 ){
|
||||
if( fread(pBuf, sz, 1, f)!=1 ){
|
||||
fprintf(stderr, "cannot read all %d bytes of \"%s\"\n", sz, zFilename);
|
||||
exit(1);
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
*pSz = sz;
|
||||
*ppBuf = pBuf;
|
||||
}
|
||||
|
||||
/* Array for converting from half-bytes (nybbles) into ASCII hex
|
||||
** digits. */
|
||||
static const char hexdigits[] = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7',
|
||||
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
|
||||
};
|
||||
|
||||
/*
|
||||
** Render an sqlite3_value as an SQL string.
|
||||
*/
|
||||
static void renderValue(sqlite3_value *pVal){
|
||||
switch( sqlite3_value_type(pVal) ){
|
||||
case SQLITE_FLOAT: {
|
||||
double r1;
|
||||
char zBuf[50];
|
||||
r1 = sqlite3_value_double(pVal);
|
||||
sqlite3_snprintf(sizeof(zBuf), zBuf, "%!.15g", r1);
|
||||
printf("%s", zBuf);
|
||||
break;
|
||||
}
|
||||
case SQLITE_INTEGER: {
|
||||
printf("%lld", sqlite3_value_int64(pVal));
|
||||
break;
|
||||
}
|
||||
case SQLITE_BLOB: {
|
||||
char const *zBlob = sqlite3_value_blob(pVal);
|
||||
int nBlob = sqlite3_value_bytes(pVal);
|
||||
int i;
|
||||
printf("x'");
|
||||
for(i=0; i<nBlob; i++){
|
||||
putchar(hexdigits[(zBlob[i]>>4)&0x0F]);
|
||||
putchar(hexdigits[(zBlob[i])&0x0F]);
|
||||
}
|
||||
putchar('\'');
|
||||
break;
|
||||
}
|
||||
case SQLITE_TEXT: {
|
||||
const unsigned char *zArg = sqlite3_value_text(pVal);
|
||||
putchar('\'');
|
||||
while( zArg[0] ){
|
||||
putchar(zArg[0]);
|
||||
if( zArg[0]=='\'' ) putchar(zArg[0]);
|
||||
zArg++;
|
||||
}
|
||||
putchar('\'');
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assert( sqlite3_value_type(pVal)==SQLITE_NULL );
|
||||
printf("NULL");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Number of conflicts seen
|
||||
*/
|
||||
static int nConflict = 0;
|
||||
|
||||
/*
|
||||
** The conflict callback
|
||||
*/
|
||||
static int conflictCallback(
|
||||
void *pCtx,
|
||||
int eConflict,
|
||||
sqlite3_changeset_iter *pIter
|
||||
){
|
||||
int op, bIndirect, nCol, i;
|
||||
const char *zTab;
|
||||
unsigned char *abPK;
|
||||
const char *zType = "";
|
||||
const char *zOp = "";
|
||||
const char *zSep = " ";
|
||||
|
||||
nConflict++;
|
||||
sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
|
||||
sqlite3changeset_pk(pIter, &abPK, 0);
|
||||
switch( eConflict ){
|
||||
case SQLITE_CHANGESET_DATA: zType = "DATA"; break;
|
||||
case SQLITE_CHANGESET_NOTFOUND: zType = "NOTFOUND"; break;
|
||||
case SQLITE_CHANGESET_CONFLICT: zType = "PRIMARY KEY"; break;
|
||||
case SQLITE_CHANGESET_FOREIGN_KEY: zType = "FOREIGN KEY"; break;
|
||||
case SQLITE_CHANGESET_CONSTRAINT: zType = "CONSTRAINT"; break;
|
||||
}
|
||||
switch( op ){
|
||||
case SQLITE_UPDATE: zOp = "UPDATE of"; break;
|
||||
case SQLITE_INSERT: zOp = "INSERT into"; break;
|
||||
case SQLITE_DELETE: zOp = "DELETE from"; break;
|
||||
}
|
||||
printf("%s conflict on %s table %s with primary key", zType, zOp, zTab);
|
||||
for(i=0; i<nCol; i++){
|
||||
sqlite3_value *pVal;
|
||||
if( abPK[i]==0 ) continue;
|
||||
printf("%s", zSep);
|
||||
if( op==SQLITE_INSERT ){
|
||||
sqlite3changeset_new(pIter, i, &pVal);
|
||||
}else{
|
||||
sqlite3changeset_old(pIter, i, &pVal);
|
||||
}
|
||||
renderValue(pVal);
|
||||
zSep = ",";
|
||||
}
|
||||
printf("\n");
|
||||
return SQLITE_CHANGESET_OMIT;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv){
|
||||
int sz, rc;
|
||||
void *pBuf = 0;
|
||||
if( argc<3 ) usage(argv[0]);
|
||||
readFile(argv[1], &sz, &pBuf);
|
||||
|
||||
/* changeset FILENAME apply DB
|
||||
** Apply the changeset in FILENAME to the database file DB
|
||||
*/
|
||||
if( strcmp(argv[2],"apply")==0 ){
|
||||
sqlite3 *db;
|
||||
if( argc!=4 ) usage(argv[0]);
|
||||
rc = sqlite3_open(argv[3], &db);
|
||||
if( rc!=SQLITE_OK ){
|
||||
fprintf(stderr, "unable to open database file \"%s\": %s\n",
|
||||
argv[3], sqlite3_errmsg(db));
|
||||
sqlite3_close(db);
|
||||
exit(1);
|
||||
}
|
||||
sqlite3_exec(db, "BEGIN", 0, 0, 0);
|
||||
nConflict = 0;
|
||||
rc = sqlite3changeset_apply(db, sz, pBuf, 0, conflictCallback, 0);
|
||||
if( rc ){
|
||||
fprintf(stderr, "sqlite3changeset_apply() returned %d\n", rc);
|
||||
}
|
||||
if( nConflict ){
|
||||
fprintf(stderr, "%d conflicts - no changes applied\n", nConflict);
|
||||
sqlite3_exec(db, "ROLLBACK", 0, 0, 0);
|
||||
}else if( rc ){
|
||||
fprintf(stderr, "sqlite3changeset_apply() returns %d "
|
||||
"- no changes applied\n", rc);
|
||||
sqlite3_exec(db, "ROLLBACK", 0, 0, 0);
|
||||
}else{
|
||||
sqlite3_exec(db, "COMMIT", 0, 0, 0);
|
||||
}
|
||||
sqlite3_close(db);
|
||||
}else
|
||||
|
||||
/* changeset FILENAME concat FILE2 OUT
|
||||
** Add changeset FILE2 onto the end of the changeset in FILENAME
|
||||
** and write the result into OUT.
|
||||
*/
|
||||
if( strcmp(argv[2],"concat")==0 ){
|
||||
int szB;
|
||||
void *pB;
|
||||
int szOut;
|
||||
void *pOutBuf;
|
||||
FILE *out;
|
||||
const char *zOut = argv[4];
|
||||
if( argc!=5 ) usage(argv[0]);
|
||||
out = fopen(zOut, "wb");
|
||||
if( out==0 ){
|
||||
fprintf(stderr, "cannot open \"%s\" for writing\n", zOut);
|
||||
exit(1);
|
||||
}
|
||||
readFile(argv[3], &szB, &pB);
|
||||
rc = sqlite3changeset_concat(sz, pBuf, szB, pB, &szOut, &pOutBuf);
|
||||
if( rc!=SQLITE_OK ){
|
||||
fprintf(stderr, "sqlite3changeset_concat() returns %d\n", rc);
|
||||
}else if( szOut>0 && fwrite(pOutBuf, szOut, 1, out)!=1 ){
|
||||
fprintf(stderr, "unable to write all %d bytes of output to \"%s\"\n",
|
||||
szOut, zOut);
|
||||
}
|
||||
fclose(out);
|
||||
sqlite3_free(pOutBuf);
|
||||
sqlite3_free(pB);
|
||||
}else
|
||||
|
||||
/* changeset FILENAME dump
|
||||
** Show the complete content of the changeset in FILENAME
|
||||
*/
|
||||
if( strcmp(argv[2],"dump")==0 ){
|
||||
int cnt = 0;
|
||||
int i;
|
||||
sqlite3_changeset_iter *pIter;
|
||||
rc = sqlite3changeset_start(&pIter, sz, pBuf);
|
||||
if( rc!=SQLITE_OK ){
|
||||
fprintf(stderr, "sqlite3changeset_start() returns %d\n", rc);
|
||||
exit(1);
|
||||
}
|
||||
while( sqlite3changeset_next(pIter)==SQLITE_ROW ){
|
||||
int op, bIndirect, nCol;
|
||||
const char *zTab;
|
||||
unsigned char *abPK;
|
||||
sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
|
||||
cnt++;
|
||||
printf("%d: %s table=[%s] indirect=%d nColumn=%d\n",
|
||||
cnt, op==SQLITE_INSERT ? "INSERT" :
|
||||
op==SQLITE_UPDATE ? "UPDATE" : "DELETE",
|
||||
zTab, bIndirect, nCol);
|
||||
sqlite3changeset_pk(pIter, &abPK, 0);
|
||||
for(i=0; i<nCol; i++){
|
||||
sqlite3_value *pVal;
|
||||
pVal = 0;
|
||||
sqlite3changeset_old(pIter, i, &pVal);
|
||||
if( pVal ){
|
||||
printf(" old[%d]%s = ", i, abPK[i] ? "pk" : " ");
|
||||
renderValue(pVal);
|
||||
printf("\n");
|
||||
}
|
||||
pVal = 0;
|
||||
sqlite3changeset_new(pIter, i, &pVal);
|
||||
if( pVal ){
|
||||
printf(" new[%d]%s = ", i, abPK[i] ? "pk" : " ");
|
||||
renderValue(pVal);
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
sqlite3changeset_finalize(pIter);
|
||||
}else
|
||||
|
||||
/* changeset FILENAME invert OUT
|
||||
** Invert the changes in FILENAME and writes the result on OUT
|
||||
*/
|
||||
if( strcmp(argv[2],"invert")==0 ){
|
||||
FILE *out;
|
||||
int szOut = 0;
|
||||
void *pOutBuf = 0;
|
||||
const char *zOut = argv[3];
|
||||
if( argc!=4 ) usage(argv[0]);
|
||||
out = fopen(zOut, "wb");
|
||||
if( out==0 ){
|
||||
fprintf(stderr, "cannot open \"%s\" for writing\n", zOut);
|
||||
exit(1);
|
||||
}
|
||||
rc = sqlite3changeset_invert(sz, pBuf, &szOut, &pOutBuf);
|
||||
if( rc!=SQLITE_OK ){
|
||||
fprintf(stderr, "sqlite3changeset_invert() returns %d\n", rc);
|
||||
}else if( szOut>0 && fwrite(pOutBuf, szOut, 1, out)!=1 ){
|
||||
fprintf(stderr, "unable to write all %d bytes of output to \"%s\"\n",
|
||||
szOut, zOut);
|
||||
}
|
||||
fclose(out);
|
||||
sqlite3_free(pOutBuf);
|
||||
}else
|
||||
|
||||
/* changeset FILE sql
|
||||
** Show the content of the changeset as pseudo-SQL
|
||||
*/
|
||||
if( strcmp(argv[2],"sql")==0 ){
|
||||
int cnt = 0;
|
||||
char *zPrevTab = 0;
|
||||
char *zSQLTabName = 0;
|
||||
sqlite3_changeset_iter *pIter = 0;
|
||||
rc = sqlite3changeset_start(&pIter, sz, pBuf);
|
||||
if( rc!=SQLITE_OK ){
|
||||
fprintf(stderr, "sqlite3changeset_start() returns %d\n", rc);
|
||||
exit(1);
|
||||
}
|
||||
printf("BEGIN;\n");
|
||||
while( sqlite3changeset_next(pIter)==SQLITE_ROW ){
|
||||
int op, bIndirect, nCol;
|
||||
const char *zTab;
|
||||
sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
|
||||
cnt++;
|
||||
if( zPrevTab==0 || strcmp(zPrevTab,zTab)!=0 ){
|
||||
sqlite3_free(zPrevTab);
|
||||
sqlite3_free(zSQLTabName);
|
||||
zPrevTab = sqlite3_mprintf("%s", zTab);
|
||||
if( !isalnum(zTab[0]) || sqlite3_strglob("*[^a-zA-Z0-9]*",zTab)==0 ){
|
||||
zSQLTabName = sqlite3_mprintf("\"%w\"", zTab);
|
||||
}else{
|
||||
zSQLTabName = sqlite3_mprintf("%s", zTab);
|
||||
}
|
||||
printf("/****** Changes for table %s ***************/\n", zSQLTabName);
|
||||
}
|
||||
switch( op ){
|
||||
case SQLITE_DELETE: {
|
||||
unsigned char *abPK;
|
||||
int i;
|
||||
const char *zSep = " ";
|
||||
sqlite3changeset_pk(pIter, &abPK, 0);
|
||||
printf("/* %d */ DELETE FROM %s WHERE", cnt, zSQLTabName);
|
||||
for(i=0; i<nCol; i++){
|
||||
sqlite3_value *pVal;
|
||||
if( abPK[i]==0 ) continue;
|
||||
printf("%sc%d=", zSep, i+1);
|
||||
zSep = " AND ";
|
||||
sqlite3changeset_old(pIter, i, &pVal);
|
||||
renderValue(pVal);
|
||||
}
|
||||
printf(";\n");
|
||||
break;
|
||||
}
|
||||
case SQLITE_UPDATE: {
|
||||
unsigned char *abPK;
|
||||
int i;
|
||||
const char *zSep = " ";
|
||||
sqlite3changeset_pk(pIter, &abPK, 0);
|
||||
printf("/* %d */ UPDATE %s SET", cnt, zSQLTabName);
|
||||
for(i=0; i<nCol; i++){
|
||||
sqlite3_value *pVal = 0;
|
||||
sqlite3changeset_new(pIter, i, &pVal);
|
||||
if( pVal ){
|
||||
printf("%sc%d=", zSep, i+1);
|
||||
zSep = ", ";
|
||||
renderValue(pVal);
|
||||
}
|
||||
}
|
||||
printf(" WHERE");
|
||||
zSep = " ";
|
||||
for(i=0; i<nCol; i++){
|
||||
sqlite3_value *pVal;
|
||||
if( abPK[i]==0 ) continue;
|
||||
printf("%sc%d=", zSep, i+1);
|
||||
zSep = " AND ";
|
||||
sqlite3changeset_old(pIter, i, &pVal);
|
||||
renderValue(pVal);
|
||||
}
|
||||
printf(";\n");
|
||||
break;
|
||||
}
|
||||
case SQLITE_INSERT: {
|
||||
int i;
|
||||
printf("/* %d */ INSERT INTO %s VALUES", cnt, zSQLTabName);
|
||||
for(i=0; i<nCol; i++){
|
||||
sqlite3_value *pVal;
|
||||
printf("%c", i==0 ? '(' : ',');
|
||||
sqlite3changeset_new(pIter, i, &pVal);
|
||||
renderValue(pVal);
|
||||
}
|
||||
printf(");\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("COMMIT;\n");
|
||||
sqlite3changeset_finalize(pIter);
|
||||
sqlite3_free(zPrevTab);
|
||||
sqlite3_free(zSQLTabName);
|
||||
}else
|
||||
|
||||
/* If nothing else matches, show the usage comment */
|
||||
usage(argv[0]);
|
||||
sqlite3_free(pBuf);
|
||||
return 0;
|
||||
}
|
||||
585
ext/session/session1.test
Normal file
585
ext/session/session1.test
Normal file
@ -0,0 +1,585 @@
|
||||
# 2011 March 07
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] session_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !session {finish_test; return}
|
||||
|
||||
set testprefix session1
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(x PRIMARY KEY, y);
|
||||
INSERT INTO t1 VALUES('abc', 'def');
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test creating, attaching tables to and deleting session objects.
|
||||
#
|
||||
do_test 1.1 { sqlite3session S db main } {S}
|
||||
do_test 1.2 { S delete } {}
|
||||
do_test 1.3 { sqlite3session S db main } {S}
|
||||
do_test 1.4 { S attach t1 } {}
|
||||
do_test 1.5 { S delete } {}
|
||||
do_test 1.6 { sqlite3session S db main } {S}
|
||||
do_test 1.7 { S attach t1 ; S attach t2 ; S attach t3 } {}
|
||||
do_test 1.8 { S attach t1 ; S attach t2 ; S attach t3 } {}
|
||||
do_test 1.9 { S delete } {}
|
||||
do_test 1.10 {
|
||||
sqlite3session S db main
|
||||
S attach t1
|
||||
execsql { INSERT INTO t1 VALUES('ghi', 'jkl') }
|
||||
} {}
|
||||
do_test 1.11 { S delete } {}
|
||||
do_test 1.12 {
|
||||
sqlite3session S db main
|
||||
S attach t1
|
||||
execsql { INSERT INTO t1 VALUES('mno', 'pqr') }
|
||||
execsql { UPDATE t1 SET x = 111 WHERE rowid = 1 }
|
||||
execsql { DELETE FROM t1 WHERE rowid = 2 }
|
||||
} {}
|
||||
do_test 1.13 {
|
||||
S changeset
|
||||
S delete
|
||||
} {}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Simple changeset tests. Also test the sqlite3changeset_invert()
|
||||
# function.
|
||||
#
|
||||
do_test 2.1.1 {
|
||||
execsql { DELETE FROM t1 }
|
||||
sqlite3session S db main
|
||||
S attach t1
|
||||
execsql { INSERT INTO t1 VALUES(1, 'Sukhothai') }
|
||||
execsql { INSERT INTO t1 VALUES(2, 'Ayutthaya') }
|
||||
execsql { INSERT INTO t1 VALUES(3, 'Thonburi') }
|
||||
} {}
|
||||
do_changeset_test 2.1.2 S {
|
||||
{INSERT t1 0 X. {} {i 1 t Sukhothai}}
|
||||
{INSERT t1 0 X. {} {i 2 t Ayutthaya}}
|
||||
{INSERT t1 0 X. {} {i 3 t Thonburi}}
|
||||
}
|
||||
do_changeset_invert_test 2.1.3 S {
|
||||
{DELETE t1 0 X. {i 1 t Sukhothai} {}}
|
||||
{DELETE t1 0 X. {i 2 t Ayutthaya} {}}
|
||||
{DELETE t1 0 X. {i 3 t Thonburi} {}}
|
||||
}
|
||||
do_test 2.1.4 { S delete } {}
|
||||
|
||||
do_test 2.2.1 {
|
||||
sqlite3session S db main
|
||||
S attach t1
|
||||
execsql { DELETE FROM t1 WHERE 1 }
|
||||
} {}
|
||||
do_changeset_test 2.2.2 S {
|
||||
{DELETE t1 0 X. {i 1 t Sukhothai} {}}
|
||||
{DELETE t1 0 X. {i 2 t Ayutthaya} {}}
|
||||
{DELETE t1 0 X. {i 3 t Thonburi} {}}
|
||||
}
|
||||
do_changeset_invert_test 2.2.3 S {
|
||||
{INSERT t1 0 X. {} {i 1 t Sukhothai}}
|
||||
{INSERT t1 0 X. {} {i 2 t Ayutthaya}}
|
||||
{INSERT t1 0 X. {} {i 3 t Thonburi}}
|
||||
}
|
||||
do_test 2.2.4 { S delete } {}
|
||||
|
||||
do_test 2.3.1 {
|
||||
execsql { DELETE FROM t1 }
|
||||
sqlite3session S db main
|
||||
execsql { INSERT INTO t1 VALUES(1, 'Sukhothai') }
|
||||
execsql { INSERT INTO t1 VALUES(2, 'Ayutthaya') }
|
||||
execsql { INSERT INTO t1 VALUES(3, 'Thonburi') }
|
||||
S attach t1
|
||||
execsql {
|
||||
UPDATE t1 SET x = 10 WHERE x = 1;
|
||||
UPDATE t1 SET y = 'Surin' WHERE x = 2;
|
||||
UPDATE t1 SET x = 20, y = 'Thapae' WHERE x = 3;
|
||||
}
|
||||
} {}
|
||||
|
||||
do_changeset_test 2.3.2 S {
|
||||
{INSERT t1 0 X. {} {i 10 t Sukhothai}}
|
||||
{DELETE t1 0 X. {i 1 t Sukhothai} {}}
|
||||
{UPDATE t1 0 X. {i 2 t Ayutthaya} {{} {} t Surin}}
|
||||
{DELETE t1 0 X. {i 3 t Thonburi} {}}
|
||||
{INSERT t1 0 X. {} {i 20 t Thapae}}
|
||||
}
|
||||
|
||||
do_changeset_invert_test 2.3.3 S {
|
||||
{DELETE t1 0 X. {i 10 t Sukhothai} {}}
|
||||
{INSERT t1 0 X. {} {i 1 t Sukhothai}}
|
||||
{UPDATE t1 0 X. {i 2 t Surin} {{} {} t Ayutthaya}}
|
||||
{INSERT t1 0 X. {} {i 3 t Thonburi}}
|
||||
{DELETE t1 0 X. {i 20 t Thapae} {}}
|
||||
}
|
||||
do_test 2.3.4 { S delete } {}
|
||||
|
||||
do_test 2.4.1 {
|
||||
sqlite3session S db main
|
||||
S attach t1
|
||||
execsql { INSERT INTO t1 VALUES(100, 'Bangkok') }
|
||||
execsql { DELETE FROM t1 WHERE x = 100 }
|
||||
} {}
|
||||
do_changeset_test 2.4.2 S {}
|
||||
do_changeset_invert_test 2.4.3 S {}
|
||||
do_test 2.4.4 { S delete } {}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test the application of simple changesets. These tests also test that
|
||||
# the conflict callback is invoked correctly. For these tests, the
|
||||
# conflict callback always returns OMIT.
|
||||
#
|
||||
db close
|
||||
forcedelete test.db test.db2
|
||||
sqlite3 db test.db
|
||||
sqlite3 db2 test.db2
|
||||
|
||||
proc xConflict {args} {
|
||||
lappend ::xConflict $args
|
||||
return ""
|
||||
}
|
||||
|
||||
proc bgerror {args} { set ::background_error $args }
|
||||
|
||||
proc do_conflict_test {tn args} {
|
||||
set O(-tables) [list]
|
||||
set O(-sql) [list]
|
||||
set O(-conflicts) [list]
|
||||
|
||||
array set V $args
|
||||
foreach key [array names V] {
|
||||
if {![info exists O($key)]} {error "no such option: $key"}
|
||||
}
|
||||
array set O $args
|
||||
|
||||
sqlite3session S db main
|
||||
foreach t $O(-tables) { S attach $t }
|
||||
execsql $O(-sql)
|
||||
set ::xConflict [list]
|
||||
sqlite3changeset_apply db2 [S changeset] xConflict
|
||||
|
||||
set conflicts [list]
|
||||
foreach c $O(-conflicts) {
|
||||
lappend conflicts $c
|
||||
}
|
||||
|
||||
after 1 {set go 1}
|
||||
vwait go
|
||||
|
||||
uplevel do_test $tn [list { set ::xConflict }] [list $conflicts]
|
||||
S delete
|
||||
}
|
||||
|
||||
proc do_db2_test {testname sql {result {}}} {
|
||||
uplevel do_test $testname [list "execsql {$sql} db2"] [list [list {*}$result]]
|
||||
}
|
||||
|
||||
# Test INSERT changesets.
|
||||
#
|
||||
do_test 3.1.0 {
|
||||
execsql { CREATE TABLE t1(a PRIMARY KEY, b NOT NULL) } db2
|
||||
execsql {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
INSERT INTO t1 VALUES(1, 'one');
|
||||
INSERT INTO t1 VALUES(2, 'two');
|
||||
} db
|
||||
} {}
|
||||
do_db2_test 3.1.1 "INSERT INTO t1 VALUES(6, 'VI')"
|
||||
do_conflict_test 3.1.2 -tables t1 -sql {
|
||||
INSERT INTO t1 VALUES(3, 'three');
|
||||
INSERT INTO t1 VALUES(4, 'four');
|
||||
INSERT INTO t1 VALUES(5, 'five');
|
||||
INSERT INTO t1 VALUES(6, 'six');
|
||||
INSERT INTO t1 VALUES(7, 'seven');
|
||||
INSERT INTO t1 VALUES(8, NULL);
|
||||
} -conflicts {
|
||||
{INSERT t1 CONFLICT {i 6 t six} {i 6 t VI}}
|
||||
{INSERT t1 CONSTRAINT {i 8 n {}}}
|
||||
}
|
||||
|
||||
do_db2_test 3.1.3 "SELECT * FROM t1" {
|
||||
6 VI 3 three 4 four 5 five 7 seven
|
||||
}
|
||||
do_execsql_test 3.1.4 "SELECT * FROM t1" {
|
||||
1 one 2 two 3 three 4 four 5 five 6 six 7 seven 8 {}
|
||||
}
|
||||
|
||||
# Test DELETE changesets.
|
||||
#
|
||||
do_execsql_test 3.2.1 {
|
||||
PRAGMA foreign_keys = on;
|
||||
CREATE TABLE t2(a PRIMARY KEY, b);
|
||||
CREATE TABLE t3(c, d REFERENCES t2);
|
||||
INSERT INTO t2 VALUES(1, 'one');
|
||||
INSERT INTO t2 VALUES(2, 'two');
|
||||
INSERT INTO t2 VALUES(3, 'three');
|
||||
INSERT INTO t2 VALUES(4, 'four');
|
||||
}
|
||||
do_db2_test 3.2.2 {
|
||||
PRAGMA foreign_keys = on;
|
||||
CREATE TABLE t2(a PRIMARY KEY, b);
|
||||
CREATE TABLE t3(c, d REFERENCES t2);
|
||||
INSERT INTO t2 VALUES(1, 'one');
|
||||
INSERT INTO t2 VALUES(2, 'two');
|
||||
INSERT INTO t2 VALUES(4, 'five');
|
||||
INSERT INTO t3 VALUES('i', 1);
|
||||
}
|
||||
do_conflict_test 3.2.3 -tables t2 -sql {
|
||||
DELETE FROM t2 WHERE a = 1;
|
||||
DELETE FROM t2 WHERE a = 2;
|
||||
DELETE FROM t2 WHERE a = 3;
|
||||
DELETE FROM t2 WHERE a = 4;
|
||||
} -conflicts {
|
||||
{DELETE t2 NOTFOUND {i 3 t three}}
|
||||
{DELETE t2 DATA {i 4 t four} {i 4 t five}}
|
||||
{FOREIGN_KEY 1}
|
||||
}
|
||||
do_execsql_test 3.2.4 "SELECT * FROM t2" {}
|
||||
do_db2_test 3.2.5 "SELECT * FROM t2" {4 five}
|
||||
|
||||
# Test UPDATE changesets.
|
||||
#
|
||||
do_execsql_test 3.3.1 {
|
||||
CREATE TABLE t4(a, b, c, PRIMARY KEY(b, c));
|
||||
INSERT INTO t4 VALUES(1, 2, 3);
|
||||
INSERT INTO t4 VALUES(4, 5, 6);
|
||||
INSERT INTO t4 VALUES(7, 8, 9);
|
||||
INSERT INTO t4 VALUES(10, 11, 12);
|
||||
}
|
||||
do_db2_test 3.3.2 {
|
||||
CREATE TABLE t4(a NOT NULL, b, c, PRIMARY KEY(b, c));
|
||||
INSERT INTO t4 VALUES(0, 2, 3);
|
||||
INSERT INTO t4 VALUES(4, 5, 7);
|
||||
INSERT INTO t4 VALUES(7, 8, 9);
|
||||
INSERT INTO t4 VALUES(10, 11, 12);
|
||||
}
|
||||
do_conflict_test 3.3.3 -tables t4 -sql {
|
||||
UPDATE t4 SET a = -1 WHERE b = 2;
|
||||
UPDATE t4 SET a = -1 WHERE b = 5;
|
||||
UPDATE t4 SET a = NULL WHERE c = 9;
|
||||
UPDATE t4 SET a = 'x' WHERE b = 11;
|
||||
} -conflicts {
|
||||
{UPDATE t4 DATA {i 1 i 2 i 3} {i -1 {} {} {} {}} {i 0 i 2 i 3}}
|
||||
{UPDATE t4 NOTFOUND {i 4 i 5 i 6} {i -1 {} {} {} {}}}
|
||||
{UPDATE t4 CONSTRAINT {i 7 i 8 i 9} {n {} {} {} {} {}}}
|
||||
}
|
||||
do_db2_test 3.3.4 { SELECT * FROM t4 } {0 2 3 4 5 7 7 8 9 x 11 12}
|
||||
do_execsql_test 3.3.5 { SELECT * FROM t4 } {-1 2 3 -1 5 6 {} 8 9 x 11 12}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# This next block of tests verifies that values returned by the conflict
|
||||
# handler are intepreted correctly.
|
||||
#
|
||||
|
||||
proc test_reset {} {
|
||||
db close
|
||||
db2 close
|
||||
forcedelete test.db test.db2
|
||||
sqlite3 db test.db
|
||||
sqlite3 db2 test.db2
|
||||
}
|
||||
|
||||
proc xConflict {args} {
|
||||
lappend ::xConflict $args
|
||||
return $::conflict_return
|
||||
}
|
||||
|
||||
foreach {tn conflict_return after} {
|
||||
1 OMIT {1 2 value1 4 5 7 10 x x}
|
||||
2 REPLACE {1 2 value1 4 5 value2 10 8 9}
|
||||
} {
|
||||
test_reset
|
||||
|
||||
do_test 4.$tn.1 {
|
||||
foreach db {db db2} {
|
||||
execsql {
|
||||
CREATE TABLE t1(a, b, c, PRIMARY KEY(a));
|
||||
INSERT INTO t1 VALUES(1, 2, 3);
|
||||
INSERT INTO t1 VALUES(4, 5, 6);
|
||||
INSERT INTO t1 VALUES(7, 8, 9);
|
||||
} $db
|
||||
}
|
||||
execsql {
|
||||
REPLACE INTO t1 VALUES(4, 5, 7);
|
||||
REPLACE INTO t1 VALUES(10, 'x', 'x');
|
||||
} db2
|
||||
} {}
|
||||
|
||||
do_conflict_test 4.$tn.2 -tables t1 -sql {
|
||||
UPDATE t1 SET c = 'value1' WHERE a = 1; -- no conflict
|
||||
UPDATE t1 SET c = 'value2' WHERE a = 4; -- DATA conflict
|
||||
UPDATE t1 SET a = 10 WHERE a = 7; -- CONFLICT conflict
|
||||
} -conflicts {
|
||||
{INSERT t1 CONFLICT {i 10 i 8 i 9} {i 10 t x t x}}
|
||||
{UPDATE t1 DATA {i 4 {} {} i 6} {{} {} {} {} t value2} {i 4 i 5 i 7}}
|
||||
}
|
||||
|
||||
do_db2_test 4.$tn.3 "SELECT * FROM t1 ORDER BY a" $after
|
||||
}
|
||||
|
||||
foreach {tn conflict_return} {
|
||||
1 OMIT
|
||||
2 REPLACE
|
||||
} {
|
||||
test_reset
|
||||
|
||||
do_test 5.$tn.1 {
|
||||
# Create an identical schema in both databases.
|
||||
set schema {
|
||||
CREATE TABLE "'foolish name'"(x, y, z, PRIMARY KEY(x, y));
|
||||
}
|
||||
execsql $schema db
|
||||
execsql $schema db2
|
||||
|
||||
# Add some rows to [db2]. These rows will cause conflicts later
|
||||
# on when the changeset from [db] is applied to it.
|
||||
execsql {
|
||||
INSERT INTO "'foolish name'" VALUES('one', 'one', 'ii');
|
||||
INSERT INTO "'foolish name'" VALUES('one', 'two', 'i');
|
||||
INSERT INTO "'foolish name'" VALUES('two', 'two', 'ii');
|
||||
} db2
|
||||
|
||||
} {}
|
||||
|
||||
do_conflict_test 5.$tn.2 -tables {{'foolish name'}} -sql {
|
||||
INSERT INTO "'foolish name'" VALUES('one', 'two', 2);
|
||||
} -conflicts {
|
||||
{INSERT {'foolish name'} CONFLICT {t one t two i 2} {t one t two t i}}
|
||||
}
|
||||
|
||||
set res(REPLACE) {one one ii one two 2 two two ii}
|
||||
set res(OMIT) {one one ii one two i two two ii}
|
||||
do_db2_test 5.$tn.3 {
|
||||
SELECT * FROM "'foolish name'" ORDER BY x, y
|
||||
} $res($conflict_return)
|
||||
|
||||
|
||||
do_test 5.$tn.1 {
|
||||
set schema {
|
||||
CREATE TABLE d1("z""z" PRIMARY KEY, y);
|
||||
INSERT INTO d1 VALUES(1, 'one');
|
||||
INSERT INTO d1 VALUES(2, 'two');
|
||||
}
|
||||
execsql $schema db
|
||||
execsql $schema db2
|
||||
|
||||
execsql {
|
||||
UPDATE d1 SET y = 'TWO' WHERE "z""z" = 2;
|
||||
} db2
|
||||
|
||||
} {}
|
||||
|
||||
do_conflict_test 5.$tn.2 -tables d1 -sql {
|
||||
DELETE FROM d1 WHERE "z""z" = 2;
|
||||
} -conflicts {
|
||||
{DELETE d1 DATA {i 2 t two} {i 2 t TWO}}
|
||||
}
|
||||
|
||||
set res(REPLACE) {1 one}
|
||||
set res(OMIT) {1 one 2 TWO}
|
||||
do_db2_test 5.$tn.3 "SELECT * FROM d1" $res($conflict_return)
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that two tables can be monitored by a single session object.
|
||||
#
|
||||
test_reset
|
||||
set schema {
|
||||
CREATE TABLE t1(a COLLATE nocase PRIMARY KEY, b);
|
||||
CREATE TABLE t2(a, b PRIMARY KEY);
|
||||
}
|
||||
do_test 6.0 {
|
||||
execsql $schema db
|
||||
execsql $schema db2
|
||||
execsql {
|
||||
INSERT INTO t1 VALUES('a', 'b');
|
||||
INSERT INTO t2 VALUES('a', 'b');
|
||||
} db2
|
||||
} {}
|
||||
|
||||
set conflict_return ""
|
||||
do_conflict_test 6.1 -tables {t1 t2} -sql {
|
||||
INSERT INTO t1 VALUES('1', '2');
|
||||
INSERT INTO t1 VALUES('A', 'B');
|
||||
INSERT INTO t2 VALUES('A', 'B');
|
||||
} -conflicts {
|
||||
{INSERT t1 CONFLICT {t A t B} {t a t b}}
|
||||
}
|
||||
|
||||
do_db2_test 6.2 "SELECT * FROM t1" {a b 1 2}
|
||||
do_db2_test 6.3 "SELECT * FROM t2" {a b A B}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that session objects are not confused by changes to table in
|
||||
# other databases.
|
||||
#
|
||||
catch { db2 close }
|
||||
drop_all_tables
|
||||
forcedelete test.db2
|
||||
do_iterator_test 7.1 * {
|
||||
ATTACH 'test.db2' AS aux;
|
||||
CREATE TABLE main.t1(x PRIMARY KEY, y);
|
||||
CREATE TABLE aux.t1(x PRIMARY KEY, y);
|
||||
|
||||
INSERT INTO main.t1 VALUES('one', 1);
|
||||
INSERT INTO main.t1 VALUES('two', 2);
|
||||
INSERT INTO aux.t1 VALUES('three', 3);
|
||||
INSERT INTO aux.t1 VALUES('four', 4);
|
||||
} {
|
||||
{INSERT t1 0 X. {} {t two i 2}}
|
||||
{INSERT t1 0 X. {} {t one i 1}}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test the sqlite3session_isempty() function.
|
||||
#
|
||||
do_test 8.1 {
|
||||
execsql {
|
||||
CREATE TABLE t5(x PRIMARY KEY, y);
|
||||
CREATE TABLE t6(x PRIMARY KEY, y);
|
||||
INSERT INTO t5 VALUES('a', 'b');
|
||||
INSERT INTO t6 VALUES('a', 'b');
|
||||
}
|
||||
sqlite3session S db main
|
||||
S attach *
|
||||
|
||||
S isempty
|
||||
} {1}
|
||||
do_test 8.2 {
|
||||
execsql { DELETE FROM t5 }
|
||||
S isempty
|
||||
} {0}
|
||||
do_test 8.3 {
|
||||
S delete
|
||||
sqlite3session S db main
|
||||
S attach t5
|
||||
execsql { DELETE FROM t5 }
|
||||
S isempty
|
||||
} {1}
|
||||
do_test 8.4 { S delete } {}
|
||||
|
||||
do_test 8.5 {
|
||||
sqlite3session S db main
|
||||
S attach t5
|
||||
S attach t6
|
||||
execsql { INSERT INTO t5 VALUES(1, 2) }
|
||||
S isempty
|
||||
} {0}
|
||||
|
||||
do_test 8.6 {
|
||||
S delete
|
||||
sqlite3session S db main
|
||||
S attach t5
|
||||
S attach t6
|
||||
execsql { INSERT INTO t6 VALUES(1, 2) }
|
||||
S isempty
|
||||
} {0}
|
||||
do_test 8.7 { S delete } {}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
do_execsql_test 9.1 {
|
||||
CREATE TABLE t7(a, b, c, d, e PRIMARY KEY, f, g);
|
||||
INSERT INTO t7 VALUES(1, 1, 1, 1, 1, 1, 1);
|
||||
}
|
||||
do_test 9.2 {
|
||||
sqlite3session S db main
|
||||
S attach *
|
||||
execsql { UPDATE t7 SET b=2, d=2 }
|
||||
} {}
|
||||
do_changeset_test 9.2 S {{UPDATE t7 0 ....X.. {{} {} i 1 {} {} i 1 i 1 {} {} {} {}} {{} {} i 2 {} {} i 2 {} {} {} {} {} {}}}}
|
||||
S delete
|
||||
catch { db2 close }
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test a really long table name.
|
||||
#
|
||||
reset_db
|
||||
set tblname [string repeat tblname123 100]
|
||||
do_test 10.1.1 {
|
||||
execsql "
|
||||
CREATE TABLE $tblname (a PRIMARY KEY, b);
|
||||
INSERT INTO $tblname VALUES('xyz', 'def');
|
||||
"
|
||||
sqlite3session S db main
|
||||
S attach $tblname
|
||||
execsql "
|
||||
INSERT INTO $tblname VALUES('uvw', 'abc');
|
||||
DELETE FROM $tblname WHERE a = 'xyz';
|
||||
"
|
||||
} {}
|
||||
breakpoint
|
||||
do_changeset_test 10.1.2 S "
|
||||
{INSERT $tblname 0 X. {} {t uvw t abc}}
|
||||
{DELETE $tblname 0 X. {t xyz t def} {}}
|
||||
"
|
||||
do_test 10.1.4 { S delete } {}
|
||||
|
||||
#---------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 11.1 {
|
||||
CREATE TABLE t1(a, b);
|
||||
}
|
||||
do_test 11.2 {
|
||||
sqlite3session S db main
|
||||
S attach t1
|
||||
execsql {
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
}
|
||||
S changeset
|
||||
} {}
|
||||
|
||||
S delete
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test a really long table name.
|
||||
#
|
||||
reset_db
|
||||
set tblname [string repeat tblname123 100]
|
||||
do_test 10.1.1 {
|
||||
execsql "
|
||||
CREATE TABLE $tblname (a PRIMARY KEY, b);
|
||||
INSERT INTO $tblname VALUES('xyz', 'def');
|
||||
"
|
||||
sqlite3session S db main
|
||||
S attach $tblname
|
||||
execsql "
|
||||
INSERT INTO $tblname VALUES('uvw', 'abc');
|
||||
DELETE FROM $tblname WHERE a = 'xyz';
|
||||
"
|
||||
} {}
|
||||
breakpoint
|
||||
do_changeset_test 10.1.2 S "
|
||||
{INSERT $tblname 0 X. {} {t uvw t abc}}
|
||||
{DELETE $tblname 0 X. {t xyz t def} {}}
|
||||
"
|
||||
do_test 10.1.4 { S delete } {}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test the effect of updating a column from 0.0 to 0.0.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 11.1 {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b REAL);
|
||||
INSERT INTO t1 VALUES(1, 0.0);
|
||||
}
|
||||
do_iterator_test 11.2 * {
|
||||
UPDATE t1 SET b = 0.0;
|
||||
} {
|
||||
}
|
||||
|
||||
finish_test
|
||||
591
ext/session/session2.test
Normal file
591
ext/session/session2.test
Normal file
@ -0,0 +1,591 @@
|
||||
# 2011 Mar 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.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# The focus of this file is testing the session module.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] session_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !session {finish_test; return}
|
||||
|
||||
set testprefix session2
|
||||
|
||||
proc test_reset {} {
|
||||
catch { db close }
|
||||
catch { db2 close }
|
||||
forcedelete test.db test.db2
|
||||
sqlite3 db test.db
|
||||
sqlite3 db2 test.db2
|
||||
}
|
||||
|
||||
##########################################################################
|
||||
# End of proc definitions. Start of tests.
|
||||
##########################################################################
|
||||
|
||||
test_reset
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
INSERT INTO t1 VALUES('i', 'one');
|
||||
}
|
||||
do_iterator_test 1.1 t1 {
|
||||
DELETE FROM t1 WHERE a = 'i';
|
||||
INSERT INTO t1 VALUES('ii', 'two');
|
||||
} {
|
||||
{DELETE t1 0 X. {t i t one} {}}
|
||||
{INSERT t1 0 X. {} {t ii t two}}
|
||||
}
|
||||
|
||||
do_iterator_test 1.2 t1 {
|
||||
INSERT INTO t1 VALUES(1.5, 99.9)
|
||||
} {
|
||||
{INSERT t1 0 X. {} {f 1.5 f 99.9}}
|
||||
}
|
||||
|
||||
do_iterator_test 1.3 t1 {
|
||||
UPDATE t1 SET b = 100.1 WHERE a = 1.5;
|
||||
UPDATE t1 SET b = 99.9 WHERE a = 1.5;
|
||||
} { }
|
||||
|
||||
do_iterator_test 1.4 t1 {
|
||||
UPDATE t1 SET b = 100.1 WHERE a = 1.5;
|
||||
} {
|
||||
{UPDATE t1 0 X. {f 1.5 f 99.9} {{} {} f 100.1}}
|
||||
}
|
||||
|
||||
|
||||
# Execute each of the following blocks of SQL on database [db1]. Collect
|
||||
# changes using a session object. Apply the resulting changeset to
|
||||
# database [db2]. Then check that the contents of the two databases are
|
||||
# identical.
|
||||
#
|
||||
|
||||
set set_of_tests {
|
||||
1 { INSERT INTO %T1% VALUES(1, 2) }
|
||||
|
||||
2 {
|
||||
INSERT INTO %T2% VALUES(1, NULL);
|
||||
INSERT INTO %T2% VALUES(2, NULL);
|
||||
INSERT INTO %T2% VALUES(3, NULL);
|
||||
DELETE FROM %T2% WHERE a = 2;
|
||||
INSERT INTO %T2% VALUES(4, NULL);
|
||||
UPDATE %T2% SET b=0 WHERE b=1;
|
||||
}
|
||||
|
||||
3 { INSERT INTO %T3% SELECT *, NULL FROM %T2% }
|
||||
|
||||
4 {
|
||||
INSERT INTO %T3% SELECT a||a, b||b, NULL FROM %T3%;
|
||||
DELETE FROM %T3% WHERE rowid%2;
|
||||
}
|
||||
|
||||
5 { UPDATE %T3% SET c = a||b }
|
||||
|
||||
6 { UPDATE %T1% SET a = 32 }
|
||||
|
||||
7 {
|
||||
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
|
||||
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
|
||||
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
|
||||
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
|
||||
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
|
||||
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
|
||||
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
|
||||
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
|
||||
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
|
||||
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
|
||||
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
|
||||
DELETE FROM %T1% WHERE (rowid%3)==0;
|
||||
}
|
||||
|
||||
8 {
|
||||
BEGIN;
|
||||
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
|
||||
ROLLBACK;
|
||||
}
|
||||
9 {
|
||||
BEGIN;
|
||||
UPDATE %T1% SET b = 'xxx';
|
||||
ROLLBACK;
|
||||
}
|
||||
10 {
|
||||
BEGIN;
|
||||
DELETE FROM %T1% WHERE 1;
|
||||
ROLLBACK;
|
||||
}
|
||||
11 {
|
||||
INSERT INTO %T1% VALUES(randomblob(21000), randomblob(0));
|
||||
INSERT INTO %T1% VALUES(1.5, 1.5);
|
||||
INSERT INTO %T1% VALUES(4.56, -99.999999999999999999999);
|
||||
}
|
||||
12 {
|
||||
INSERT INTO %T2% VALUES(NULL, NULL);
|
||||
}
|
||||
|
||||
13 {
|
||||
DELETE FROM %T1% WHERE 1;
|
||||
|
||||
-- Insert many rows with real primary keys. Enough to force the session
|
||||
-- objects hash table to resize.
|
||||
INSERT INTO %T1% VALUES(0.1, 0.1);
|
||||
INSERT INTO %T1% SELECT a+0.1, b+0.1 FROM %T1%;
|
||||
INSERT INTO %T1% SELECT a+0.2, b+0.2 FROM %T1%;
|
||||
INSERT INTO %T1% SELECT a+0.4, b+0.4 FROM %T1%;
|
||||
INSERT INTO %T1% SELECT a+0.8, b+0.8 FROM %T1%;
|
||||
INSERT INTO %T1% SELECT a+1.6, b+1.6 FROM %T1%;
|
||||
INSERT INTO %T1% SELECT a+3.2, b+3.2 FROM %T1%;
|
||||
INSERT INTO %T1% SELECT a+6.4, b+6.4 FROM %T1%;
|
||||
INSERT INTO %T1% SELECT a+12.8, b+12.8 FROM %T1%;
|
||||
INSERT INTO %T1% SELECT a+25.6, b+25.6 FROM %T1%;
|
||||
INSERT INTO %T1% SELECT a+51.2, b+51.2 FROM %T1%;
|
||||
INSERT INTO %T1% SELECT a+102.4, b+102.4 FROM %T1%;
|
||||
INSERT INTO %T1% SELECT a+204.8, b+204.8 FROM %T1%;
|
||||
}
|
||||
|
||||
14 {
|
||||
DELETE FROM %T1% WHERE 1;
|
||||
}
|
||||
|
||||
15 {
|
||||
INSERT INTO %T1% VALUES(1, 1);
|
||||
INSERT INTO %T1% SELECT a+2, b+2 FROM %T1%;
|
||||
INSERT INTO %T1% SELECT a+4, b+4 FROM %T1%;
|
||||
INSERT INTO %T1% SELECT a+8, b+8 FROM %T1%;
|
||||
INSERT INTO %T1% SELECT a+256, b+256 FROM %T1%;
|
||||
}
|
||||
|
||||
16 {
|
||||
INSERT INTO %T4% VALUES('abc', 'def');
|
||||
INSERT INTO %T4% VALUES('def', 'abc');
|
||||
}
|
||||
17 { UPDATE %T4% SET b = 1 }
|
||||
|
||||
18 { DELETE FROM %T4% WHERE 1 }
|
||||
|
||||
19 {
|
||||
INSERT INTO t1 VALUES('', '');
|
||||
INSERT INTO t1 VALUES(X'', X'');
|
||||
}
|
||||
20 {
|
||||
DELETE FROM t1;
|
||||
INSERT INTO t1 VALUES('', NULL);
|
||||
}
|
||||
}
|
||||
|
||||
test_reset
|
||||
do_common_sql {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
CREATE TABLE t2(a, b INTEGER PRIMARY KEY);
|
||||
CREATE TABLE t3(a, b, c, PRIMARY KEY(a, b));
|
||||
CREATE TABLE t4(a, b, PRIMARY KEY(b, a));
|
||||
}
|
||||
|
||||
foreach {tn sql} [string map {%T1% t1 %T2% t2 %T3% t3 %T4% t4} $set_of_tests] {
|
||||
do_then_apply_sql $sql
|
||||
do_test 2.$tn { compare_db db db2 } {}
|
||||
}
|
||||
|
||||
# The following block of tests is similar to the last, except that the
|
||||
# session object is recording changes made to an attached database. The
|
||||
# main database contains a table of the same name as the table being
|
||||
# modified within the attached db.
|
||||
#
|
||||
test_reset
|
||||
forcedelete test.db3
|
||||
sqlite3 db3 test.db3
|
||||
do_test 3.0 {
|
||||
execsql {
|
||||
ATTACH 'test.db3' AS 'aux';
|
||||
CREATE TABLE t1(a, b PRIMARY KEY);
|
||||
CREATE TABLE t2(x, y, z);
|
||||
CREATE TABLE t3(a);
|
||||
|
||||
CREATE TABLE aux.t1(a PRIMARY KEY, b);
|
||||
CREATE TABLE aux.t2(a, b INTEGER PRIMARY KEY);
|
||||
CREATE TABLE aux.t3(a, b, c, PRIMARY KEY(a, b));
|
||||
CREATE TABLE aux.t4(a, b, PRIMARY KEY(b, a));
|
||||
}
|
||||
execsql {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
CREATE TABLE t2(a, b INTEGER PRIMARY KEY);
|
||||
CREATE TABLE t3(a, b, c, PRIMARY KEY(a, b));
|
||||
CREATE TABLE t4(a, b, PRIMARY KEY(b, a));
|
||||
} db2
|
||||
} {}
|
||||
|
||||
proc xTrace {args} { puts $args }
|
||||
|
||||
foreach {tn sql} [
|
||||
string map {%T1% aux.t1 %T2% aux.t2 %T3% aux.t3 %T4% aux.t4} $set_of_tests
|
||||
] {
|
||||
do_then_apply_sql $sql aux
|
||||
do_test 3.$tn { compare_db db2 db3 } {}
|
||||
}
|
||||
catch {db3 close}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# The following tests verify that NULL values in primary key columns are
|
||||
# handled correctly by the session module.
|
||||
#
|
||||
test_reset
|
||||
do_execsql_test 4.0 {
|
||||
CREATE TABLE t1(a PRIMARY KEY);
|
||||
CREATE TABLE t2(a, b, c, PRIMARY KEY(c, b));
|
||||
CREATE TABLE t3(a, b INTEGER PRIMARY KEY);
|
||||
}
|
||||
|
||||
foreach {tn sql changeset} {
|
||||
1 {
|
||||
INSERT INTO t1 VALUES(123);
|
||||
INSERT INTO t1 VALUES(NULL);
|
||||
INSERT INTO t1 VALUES(456);
|
||||
} {
|
||||
{INSERT t1 0 X {} {i 456}}
|
||||
{INSERT t1 0 X {} {i 123}}
|
||||
}
|
||||
|
||||
2 {
|
||||
UPDATE t1 SET a = NULL;
|
||||
} {
|
||||
{DELETE t1 0 X {i 456} {}}
|
||||
{DELETE t1 0 X {i 123} {}}
|
||||
}
|
||||
|
||||
3 { DELETE FROM t1 } { }
|
||||
|
||||
4 {
|
||||
INSERT INTO t3 VALUES(NULL, NULL)
|
||||
} {
|
||||
{INSERT t3 0 .X {} {n {} i 1}}
|
||||
}
|
||||
|
||||
5 { INSERT INTO t2 VALUES(1, 2, NULL) } { }
|
||||
6 { INSERT INTO t2 VALUES(1, NULL, 3) } { }
|
||||
7 { INSERT INTO t2 VALUES(1, NULL, NULL) } { }
|
||||
8 { INSERT INTO t2 VALUES(1, 2, 3) } { {INSERT t2 0 .XX {} {i 1 i 2 i 3}} }
|
||||
9 { DELETE FROM t2 WHERE 1 } { {DELETE t2 0 .XX {i 1 i 2 i 3} {}} }
|
||||
|
||||
} {
|
||||
do_iterator_test 4.$tn {t1 t2 t3} $sql $changeset
|
||||
}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that if NULL is passed to sqlite3session_attach(), all database
|
||||
# tables are attached to the session object.
|
||||
#
|
||||
test_reset
|
||||
do_execsql_test 5.0 {
|
||||
CREATE TABLE t1(a PRIMARY KEY);
|
||||
CREATE TABLE t2(x, y PRIMARY KEY);
|
||||
}
|
||||
|
||||
foreach {tn sql changeset} {
|
||||
1 { INSERT INTO t1 VALUES(35) } { {INSERT t1 0 X {} {i 35}} }
|
||||
2 { INSERT INTO t2 VALUES(36, 37) } { {INSERT t2 0 .X {} {i 36 i 37}} }
|
||||
3 {
|
||||
DELETE FROM t1 WHERE 1;
|
||||
UPDATE t2 SET x = 34;
|
||||
} {
|
||||
{DELETE t1 0 X {i 35} {}}
|
||||
{UPDATE t2 0 .X {i 36 i 37} {i 34 {} {}}}
|
||||
}
|
||||
} {
|
||||
do_iterator_test 5.$tn * $sql $changeset
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# The next block of tests verify that the "indirect" flag is set
|
||||
# correctly within changesets. The indirect flag is set for a change
|
||||
# if either of the following are true:
|
||||
#
|
||||
# * The sqlite3session_indirect() API has been used to set the session
|
||||
# indirect flag to true, or
|
||||
# * The change was made by a trigger.
|
||||
#
|
||||
# If the same row is updated more than once during a session, then the
|
||||
# change is considered indirect only if all changes meet the criteria
|
||||
# above.
|
||||
#
|
||||
test_reset
|
||||
db function indirect [list S indirect]
|
||||
|
||||
do_execsql_test 6.0 {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b, c);
|
||||
|
||||
CREATE TABLE t2(x PRIMARY KEY, y);
|
||||
CREATE TRIGGER AFTER INSERT ON t2 WHEN new.x%2 BEGIN
|
||||
INSERT INTO t2 VALUES(new.x+1, NULL);
|
||||
END;
|
||||
}
|
||||
|
||||
do_iterator_test 6.1.1 * {
|
||||
INSERT INTO t1 VALUES(1, 'one', 'i');
|
||||
SELECT indirect(1);
|
||||
INSERT INTO t1 VALUES(2, 'two', 'ii');
|
||||
SELECT indirect(0);
|
||||
INSERT INTO t1 VALUES(3, 'three', 'iii');
|
||||
} {
|
||||
{INSERT t1 0 X.. {} {i 1 t one t i}}
|
||||
{INSERT t1 1 X.. {} {i 2 t two t ii}}
|
||||
{INSERT t1 0 X.. {} {i 3 t three t iii}}
|
||||
}
|
||||
|
||||
do_iterator_test 6.1.2 * {
|
||||
SELECT indirect(1);
|
||||
UPDATE t1 SET c = 'I' WHERE a = 1;
|
||||
SELECT indirect(0);
|
||||
} {
|
||||
{UPDATE t1 1 X.. {i 1 {} {} t i} {{} {} {} {} t I}}
|
||||
}
|
||||
do_iterator_test 6.1.3 * {
|
||||
SELECT indirect(1);
|
||||
UPDATE t1 SET c = '.' WHERE a = 1;
|
||||
SELECT indirect(0);
|
||||
UPDATE t1 SET c = 'o' WHERE a = 1;
|
||||
} {
|
||||
{UPDATE t1 0 X.. {i 1 {} {} t I} {{} {} {} {} t o}}
|
||||
}
|
||||
do_iterator_test 6.1.4 * {
|
||||
SELECT indirect(0);
|
||||
UPDATE t1 SET c = 'x' WHERE a = 1;
|
||||
SELECT indirect(1);
|
||||
UPDATE t1 SET c = 'i' WHERE a = 1;
|
||||
} {
|
||||
{UPDATE t1 0 X.. {i 1 {} {} t o} {{} {} {} {} t i}}
|
||||
}
|
||||
do_iterator_test 6.1.4 * {
|
||||
SELECT indirect(1);
|
||||
UPDATE t1 SET c = 'y' WHERE a = 1;
|
||||
SELECT indirect(1);
|
||||
UPDATE t1 SET c = 'I' WHERE a = 1;
|
||||
} {
|
||||
{UPDATE t1 1 X.. {i 1 {} {} t i} {{} {} {} {} t I}}
|
||||
}
|
||||
|
||||
do_iterator_test 6.1.5 * {
|
||||
INSERT INTO t2 VALUES(1, 'x');
|
||||
} {
|
||||
{INSERT t2 0 X. {} {i 1 t x}}
|
||||
{INSERT t2 1 X. {} {i 2 n {}}}
|
||||
}
|
||||
|
||||
do_iterator_test 6.1.6 * {
|
||||
SELECT indirect(1);
|
||||
INSERT INTO t2 VALUES(3, 'x');
|
||||
SELECT indirect(0);
|
||||
UPDATE t2 SET y = 'y' WHERE x>2;
|
||||
} {
|
||||
{INSERT t2 0 X. {} {i 3 t y}}
|
||||
{INSERT t2 0 X. {} {i 4 t y}}
|
||||
}
|
||||
|
||||
do_iterator_test 6.1.7 * {
|
||||
SELECT indirect(1);
|
||||
DELETE FROM t2 WHERE x = 4;
|
||||
SELECT indirect(0);
|
||||
INSERT INTO t2 VALUES(4, 'new');
|
||||
} {
|
||||
{UPDATE t2 0 X. {i 4 t y} {{} {} t new}}
|
||||
}
|
||||
|
||||
do_iterator_test 6.1.8 * {
|
||||
CREATE TABLE t3(a, b PRIMARY KEY);
|
||||
CREATE TABLE t4(a, b PRIMARY KEY);
|
||||
CREATE TRIGGER t4t AFTER UPDATE ON t4 BEGIN
|
||||
UPDATE t3 SET a = new.a WHERE b = new.b;
|
||||
END;
|
||||
|
||||
SELECT indirect(1);
|
||||
INSERT INTO t3 VALUES('one', 1);
|
||||
INSERT INTO t4 VALUES('one', 1);
|
||||
SELECT indirect(0);
|
||||
UPDATE t4 SET a = 'two' WHERE b = 1;
|
||||
} {
|
||||
{INSERT t3 1 .X {} {t two i 1}}
|
||||
{INSERT t4 0 .X {} {t two i 1}}
|
||||
}
|
||||
|
||||
sqlite3session S db main
|
||||
do_execsql_test 6.2.1 {
|
||||
SELECT indirect(0);
|
||||
SELECT indirect(-1);
|
||||
SELECT indirect(45);
|
||||
SELECT indirect(-100);
|
||||
} {0 0 1 1}
|
||||
S delete
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that if a conflict-handler that has been passed either NOTFOUND or
|
||||
# CONSTRAINT returns REPLACE - the sqlite3changeset_apply() call returns
|
||||
# MISUSE and rolls back any changes made so far.
|
||||
#
|
||||
# 7.1.*: NOTFOUND conflict-callback.
|
||||
# 7.2.*: CONSTRAINT conflict-callback.
|
||||
#
|
||||
proc xConflict {args} {return REPLACE}
|
||||
test_reset
|
||||
|
||||
do_execsql_test 7.1.1 {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
INSERT INTO t1 VALUES(1, 'one');
|
||||
INSERT INTO t1 VALUES(2, 'two');
|
||||
}
|
||||
do_test 7.1.2 {
|
||||
execsql {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b NOT NULL);
|
||||
INSERT INTO t1 VALUES(1, 'one');
|
||||
} db2
|
||||
} {}
|
||||
do_test 7.1.3 {
|
||||
set changeset [changeset_from_sql {
|
||||
UPDATE t1 SET b = 'five' WHERE a = 1;
|
||||
UPDATE t1 SET b = 'six' WHERE a = 2;
|
||||
}]
|
||||
set x [list]
|
||||
sqlite3session_foreach c $changeset { lappend x $c }
|
||||
set x
|
||||
} [list \
|
||||
{UPDATE t1 0 X. {i 1 t one} {{} {} t five}} \
|
||||
{UPDATE t1 0 X. {i 2 t two} {{} {} t six}} \
|
||||
]
|
||||
do_test 7.1.4 {
|
||||
list [catch {sqlite3changeset_apply db2 $changeset xConflict} msg] $msg
|
||||
} {1 SQLITE_MISUSE}
|
||||
do_test 7.1.5 { execsql { SELECT * FROM t1 } db2 } {1 one}
|
||||
|
||||
do_test 7.2.1 {
|
||||
set changeset [changeset_from_sql { UPDATE t1 SET b = NULL WHERE a = 1 }]
|
||||
|
||||
set x [list]
|
||||
sqlite3session_foreach c $changeset { lappend x $c }
|
||||
set x
|
||||
} [list \
|
||||
{UPDATE t1 0 X. {i 1 t five} {{} {} n {}}} \
|
||||
]
|
||||
do_test 7.2.2 {
|
||||
list [catch {sqlite3changeset_apply db2 $changeset xConflict} msg] $msg
|
||||
} {1 SQLITE_MISUSE}
|
||||
do_test 7.2.3 { execsql { SELECT * FROM t1 } db2 } {1 one}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that if a conflict-handler returns ABORT, application of the
|
||||
# changeset is rolled back and the sqlite3changeset_apply() method returns
|
||||
# SQLITE_ABORT.
|
||||
#
|
||||
# Also test that the same thing happens if a conflict handler returns an
|
||||
# unrecognized integer value. Except, in this case SQLITE_MISUSE is returned
|
||||
# instead of SQLITE_ABORT.
|
||||
#
|
||||
foreach {tn conflict_return apply_return} {
|
||||
1 ABORT SQLITE_ABORT
|
||||
2 567 SQLITE_MISUSE
|
||||
} {
|
||||
test_reset
|
||||
proc xConflict {args} [list return $conflict_return]
|
||||
|
||||
do_test 8.$tn.0 {
|
||||
do_common_sql {
|
||||
CREATE TABLE t1(x, y, PRIMARY KEY(x, y));
|
||||
INSERT INTO t1 VALUES('x', 'y');
|
||||
}
|
||||
execsql { INSERT INTO t1 VALUES('w', 'w') }
|
||||
|
||||
set changeset [changeset_from_sql { DELETE FROM t1 WHERE 1 }]
|
||||
|
||||
set x [list]
|
||||
sqlite3session_foreach c $changeset { lappend x $c }
|
||||
set x
|
||||
} [list \
|
||||
{DELETE t1 0 XX {t w t w} {}} \
|
||||
{DELETE t1 0 XX {t x t y} {}} \
|
||||
]
|
||||
|
||||
do_test 8.$tn.1 {
|
||||
list [catch {sqlite3changeset_apply db2 $changeset xConflict} msg] $msg
|
||||
} [list 1 $apply_return]
|
||||
|
||||
do_test 8.$tn.2 {
|
||||
execsql {SELECT * FROM t1} db2
|
||||
} {x y}
|
||||
}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Try to cause an infinite loop as follows:
|
||||
#
|
||||
# 1. Have a changeset insert a row that causes a CONFLICT callback,
|
||||
# 2. Have the conflict handler return REPLACE,
|
||||
# 3. After the session module deletes the conflicting row, have a trigger
|
||||
# re-insert it.
|
||||
# 4. Goto step 1...
|
||||
#
|
||||
# This doesn't work, as the second invocation of the conflict handler is a
|
||||
# CONSTRAINT, not a CONFLICT. There is at most one CONFLICT callback for
|
||||
# each change in the changeset.
|
||||
#
|
||||
test_reset
|
||||
proc xConflict {type args} {
|
||||
if {$type == "CONFLICT"} { return REPLACE }
|
||||
return OMIT
|
||||
}
|
||||
do_test 9.1 {
|
||||
execsql {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
}
|
||||
execsql {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
INSERT INTO t1 VALUES('x', 2);
|
||||
CREATE TRIGGER tr1 AFTER DELETE ON t1 BEGIN
|
||||
INSERT INTO t1 VALUES(old.a, old.b);
|
||||
END;
|
||||
} db2
|
||||
} {}
|
||||
do_test 9.2 {
|
||||
set changeset [changeset_from_sql { INSERT INTO t1 VALUES('x', 1) }]
|
||||
sqlite3changeset_apply db2 $changeset xConflict
|
||||
} {}
|
||||
do_test 9.3 {
|
||||
execsql { SELECT * FROM t1 } db2
|
||||
} {x 2}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
test_reset
|
||||
db function enable [list S enable]
|
||||
|
||||
do_common_sql {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
INSERT INTO t1 VALUES('x', 'X');
|
||||
}
|
||||
|
||||
do_iterator_test 10.1 t1 {
|
||||
INSERT INTO t1 VALUES('y', 'Y');
|
||||
SELECT enable(0);
|
||||
INSERT INTO t1 VALUES('z', 'Z');
|
||||
SELECT enable(1);
|
||||
} {
|
||||
{INSERT t1 0 X. {} {t y t Y}}
|
||||
}
|
||||
|
||||
sqlite3session S db main
|
||||
do_execsql_test 10.2 {
|
||||
SELECT enable(0);
|
||||
SELECT enable(-1);
|
||||
SELECT enable(1);
|
||||
SELECT enable(-1);
|
||||
} {0 0 1 1}
|
||||
S delete
|
||||
|
||||
finish_test
|
||||
211
ext/session/session3.test
Normal file
211
ext/session/session3.test
Normal file
@ -0,0 +1,211 @@
|
||||
# 2011 March 24
|
||||
#
|
||||
# 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 the session module. More
|
||||
# specifically, it focuses on testing the session modules response to
|
||||
# database schema modifications and mismatches.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] session_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !session {finish_test; return}
|
||||
|
||||
set testprefix session3
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# These tests - session3-1.* - verify that the session module behaves
|
||||
# correctly when confronted with a schema mismatch when applying a
|
||||
# changeset (in function sqlite3changeset_apply()).
|
||||
#
|
||||
# session3-1.1.*: Table does not exist in target db.
|
||||
# session3-1.2.*: Table has wrong number of columns in target db.
|
||||
# session3-1.3.*: Table has wrong PK columns in target db.
|
||||
#
|
||||
db close
|
||||
sqlite3_shutdown
|
||||
test_sqlite3_log log
|
||||
sqlite3 db test.db
|
||||
|
||||
proc log {code msg} { lappend ::log $code $msg }
|
||||
|
||||
forcedelete test.db2
|
||||
sqlite3 db2 test.db2
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
}
|
||||
do_test 1.1 {
|
||||
set ::log {}
|
||||
do_then_apply_sql {
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
INSERT INTO t1 VALUES(3, 4);
|
||||
}
|
||||
set ::log
|
||||
} {SQLITE_SCHEMA {sqlite3changeset_apply(): no such table: t1}}
|
||||
|
||||
do_test 1.2.0 {
|
||||
execsql { CREATE TABLE t1(a PRIMARY KEY, b, c) } db2
|
||||
} {}
|
||||
do_test 1.2.1 {
|
||||
set ::log {}
|
||||
do_then_apply_sql {
|
||||
INSERT INTO t1 VALUES(5, 6);
|
||||
INSERT INTO t1 VALUES(7, 8);
|
||||
}
|
||||
set ::log
|
||||
} {SQLITE_SCHEMA {sqlite3changeset_apply(): table t1 has 3 columns, expected 2}}
|
||||
|
||||
do_test 1.3.0 {
|
||||
execsql {
|
||||
DROP TABLE t1;
|
||||
CREATE TABLE t1(a, b PRIMARY KEY);
|
||||
} db2
|
||||
} {}
|
||||
do_test 1.3.1 {
|
||||
set ::log {}
|
||||
do_then_apply_sql {
|
||||
INSERT INTO t1 VALUES(9, 10);
|
||||
INSERT INTO t1 VALUES(11, 12);
|
||||
}
|
||||
set ::log
|
||||
} {SQLITE_SCHEMA {sqlite3changeset_apply(): primary key mismatch for table t1}}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# These tests - session3-2.* - verify that the session module behaves
|
||||
# correctly when the schema of an attached table is modified during the
|
||||
# session.
|
||||
#
|
||||
# session3-2.1.*: Table is dropped midway through the session.
|
||||
# session3-2.2.*: Table is dropped and recreated with a different # cols.
|
||||
# session3-2.3.*: Table is dropped and recreated with a different PK.
|
||||
#
|
||||
# In all of these scenarios, the call to sqlite3session_changeset() will
|
||||
# return SQLITE_SCHEMA. Also:
|
||||
#
|
||||
# session3-2.4.*: Table is dropped and recreated with an identical schema.
|
||||
# In this case sqlite3session_changeset() returns SQLITE_OK.
|
||||
#
|
||||
|
||||
do_test 2.1 {
|
||||
execsql { CREATE TABLE t2(a, b PRIMARY KEY) }
|
||||
sqlite3session S db main
|
||||
S attach t2
|
||||
execsql {
|
||||
INSERT INTO t2 VALUES(1, 2);
|
||||
DROP TABLE t2;
|
||||
}
|
||||
list [catch { S changeset } msg] $msg
|
||||
} {1 SQLITE_SCHEMA}
|
||||
|
||||
do_test 2.2.1 {
|
||||
S delete
|
||||
sqlite3session S db main
|
||||
execsql { CREATE TABLE t2(a, b PRIMARY KEY, c) }
|
||||
S attach t2
|
||||
execsql {
|
||||
INSERT INTO t2 VALUES(1, 2, 3);
|
||||
DROP TABLE t2;
|
||||
CREATE TABLE t2(a, b PRIMARY KEY);
|
||||
}
|
||||
list [catch { S changeset } msg] $msg
|
||||
} {1 SQLITE_SCHEMA}
|
||||
do_test 2.2.2 {
|
||||
S delete
|
||||
sqlite3session S db main
|
||||
execsql {
|
||||
DROP TABLE t2;
|
||||
CREATE TABLE t2(a, b PRIMARY KEY, c);
|
||||
}
|
||||
S attach t2
|
||||
execsql {
|
||||
INSERT INTO t2 VALUES(1, 2, 3);
|
||||
DROP TABLE t2;
|
||||
CREATE TABLE t2(a, b PRIMARY KEY, c, d);
|
||||
}
|
||||
list [catch { S changeset } msg] $msg
|
||||
} {1 SQLITE_SCHEMA}
|
||||
do_test 2.2.3 {
|
||||
S delete
|
||||
sqlite3session S db main
|
||||
execsql {
|
||||
DROP TABLE t2;
|
||||
CREATE TABLE t2(a, b PRIMARY KEY, c);
|
||||
}
|
||||
S attach t2
|
||||
execsql {
|
||||
INSERT INTO t2 VALUES(1, 2, 3);
|
||||
DROP TABLE t2;
|
||||
CREATE TABLE t2(a, b PRIMARY KEY);
|
||||
INSERT INTO t2 VALUES(4, 5);
|
||||
}
|
||||
list [catch { S changeset } msg] $msg
|
||||
} {1 SQLITE_SCHEMA}
|
||||
do_test 2.2.4 {
|
||||
S delete
|
||||
sqlite3session S db main
|
||||
execsql {
|
||||
DROP TABLE t2;
|
||||
CREATE TABLE t2(a, b PRIMARY KEY, c);
|
||||
}
|
||||
S attach t2
|
||||
execsql {
|
||||
INSERT INTO t2 VALUES(1, 2, 3);
|
||||
DROP TABLE t2;
|
||||
CREATE TABLE t2(a, b PRIMARY KEY, c, d);
|
||||
INSERT INTO t2 VALUES(4, 5, 6, 7);
|
||||
}
|
||||
list [catch { S changeset } msg] $msg
|
||||
} {1 SQLITE_SCHEMA}
|
||||
|
||||
do_test 2.3 {
|
||||
S delete
|
||||
sqlite3session S db main
|
||||
execsql {
|
||||
DROP TABLE t2;
|
||||
CREATE TABLE t2(a, b PRIMARY KEY);
|
||||
}
|
||||
S attach t2
|
||||
execsql {
|
||||
INSERT INTO t2 VALUES(1, 2);
|
||||
DROP TABLE t2;
|
||||
CREATE TABLE t2(a PRIMARY KEY, b);
|
||||
}
|
||||
list [catch { S changeset } msg] $msg
|
||||
} {1 SQLITE_SCHEMA}
|
||||
|
||||
do_test 2.4 {
|
||||
S delete
|
||||
sqlite3session S db main
|
||||
execsql {
|
||||
DROP TABLE t2;
|
||||
CREATE TABLE t2(a, b PRIMARY KEY);
|
||||
}
|
||||
S attach t2
|
||||
execsql {
|
||||
INSERT INTO t2 VALUES(1, 2);
|
||||
DROP TABLE t2;
|
||||
CREATE TABLE t2(a, b PRIMARY KEY);
|
||||
}
|
||||
list [catch { S changeset } msg] $msg
|
||||
} {0 {}}
|
||||
|
||||
S delete
|
||||
|
||||
|
||||
catch { db close }
|
||||
catch { db2 close }
|
||||
sqlite3_shutdown
|
||||
test_sqlite3_log
|
||||
sqlite3_initialize
|
||||
|
||||
finish_test
|
||||
67
ext/session/session4.test
Normal file
67
ext/session/session4.test
Normal file
@ -0,0 +1,67 @@
|
||||
# 2011 March 25
|
||||
#
|
||||
# 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 the session module.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] session_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !session {finish_test; return}
|
||||
|
||||
set testprefix session4
|
||||
|
||||
do_test 1.0 {
|
||||
execsql {
|
||||
CREATE TABLE x(a, b, c, d, e, PRIMARY KEY(c, e));
|
||||
INSERT INTO x VALUES(65.21, X'28B0', 16.35, NULL, 'doers');
|
||||
INSERT INTO x VALUES(NULL, 78.49, 2, X'60', -66);
|
||||
INSERT INTO x VALUES('cathedral', NULL, 35, NULL, X'B220937E80A2D8');
|
||||
INSERT INTO x VALUES(NULL, 'masking', -91.37, NULL, X'596D');
|
||||
INSERT INTO x VALUES(19, 'domains', 'espouse', -94, 'throw');
|
||||
}
|
||||
|
||||
set changeset [changeset_from_sql {
|
||||
DELETE FROM x WHERE e = -66;
|
||||
UPDATE x SET a = 'parameterizable', b = 31.8 WHERE c = 35;
|
||||
INSERT INTO x VALUES(-75.61, -17, 16.85, NULL, X'D73DB02678');
|
||||
}]
|
||||
set {} {}
|
||||
} {}
|
||||
|
||||
|
||||
# This currently causes crashes. sqlite3changeset_invert() does not handle
|
||||
# corrupt changesets well.
|
||||
if 0 {
|
||||
do_test 1.1 {
|
||||
for {set i 0} {$i < [string length $changeset]} {incr i} {
|
||||
set before [string range $changeset 0 [expr $i-1]]
|
||||
set after [string range $changeset [expr $i+1] end]
|
||||
for {set j 10} {$j < 260} {incr j} {
|
||||
set x [binary format "a*ca*" $before $j $after]
|
||||
catch { sqlite3changeset_invert $x }
|
||||
}
|
||||
}
|
||||
} {}
|
||||
}
|
||||
|
||||
do_test 1.2 {
|
||||
set x [binary format "ca*" 0 [string range $changeset 1 end]]
|
||||
list [catch { sqlite3changeset_invert $x } msg] $msg
|
||||
} {1 SQLITE_CORRUPT}
|
||||
|
||||
do_test 1.3 {
|
||||
set x [binary format "ca*" 0 [string range $changeset 1 end]]
|
||||
list [catch { sqlite3changeset_apply db $x xConflict } msg] $msg
|
||||
} {1 SQLITE_CORRUPT}
|
||||
|
||||
finish_test
|
||||
408
ext/session/session5.test
Normal file
408
ext/session/session5.test
Normal file
@ -0,0 +1,408 @@
|
||||
# 2011 April 13
|
||||
#
|
||||
# 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 the session module.
|
||||
# Specifically, for the sqlite3changeset_concat() command.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] session_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !session {finish_test; return}
|
||||
|
||||
set testprefix session5
|
||||
|
||||
# Organization of tests:
|
||||
#
|
||||
# session5-1.*: Simple tests to check the concat() function produces
|
||||
# correct results.
|
||||
#
|
||||
# session5-2.*: More complicated tests.
|
||||
#
|
||||
# session5-3.*: Schema mismatch errors.
|
||||
#
|
||||
# session5-4.*: Test the concat cases that indicate that the database
|
||||
# was modified in between recording of the two changesets
|
||||
# being concatenated (i.e. two changesets that INSERT rows
|
||||
# with the same PK values).
|
||||
#
|
||||
|
||||
proc do_concat_test {tn args} {
|
||||
|
||||
set subtest 0
|
||||
foreach sql $args {
|
||||
incr subtest
|
||||
sqlite3session S db main ; S attach *
|
||||
execsql $sql
|
||||
|
||||
set c [S changeset]
|
||||
if {[info commands s_prev] != ""} {
|
||||
set c_concat [sqlite3changeset_concat $c_prev $c]
|
||||
set c_two [s_prev changeset]
|
||||
s_prev delete
|
||||
|
||||
set h_concat [changeset_to_list $c_concat]
|
||||
set h_two [changeset_to_list $c_two]
|
||||
|
||||
do_test $tn.$subtest [list set {} $h_concat] $h_two
|
||||
}
|
||||
set c_prev $c
|
||||
rename S s_prev
|
||||
}
|
||||
|
||||
catch { s_prev delete }
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test cases session5-1.* - simple tests.
|
||||
#
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
}
|
||||
|
||||
do_concat_test 1.1.1 {
|
||||
INSERT INTO t1 VALUES(1, 'one');
|
||||
} {
|
||||
INSERT INTO t1 VALUES(2, 'two');
|
||||
}
|
||||
|
||||
do_concat_test 1.1.2 {
|
||||
UPDATE t1 SET b = 'five' WHERE a = 1;
|
||||
} {
|
||||
UPDATE t1 SET b = 'six' WHERE a = 2;
|
||||
}
|
||||
|
||||
do_concat_test 1.1.3 {
|
||||
DELETE FROM t1 WHERE a = 1;
|
||||
} {
|
||||
DELETE FROM t1 WHERE a = 2;
|
||||
}
|
||||
|
||||
|
||||
# 1.2.1: INSERT + DELETE -> (none)
|
||||
# 1.2.2: INSERT + UPDATE -> INSERT
|
||||
#
|
||||
# 1.2.3: DELETE + INSERT (matching data) -> (none)
|
||||
# 1.2.4: DELETE + INSERT (non-matching data) -> UPDATE
|
||||
#
|
||||
# 1.2.5: UPDATE + UPDATE (matching data) -> (none)
|
||||
# 1.2.6: UPDATE + UPDATE (non-matching data) -> UPDATE
|
||||
# 1.2.7: UPDATE + DELETE -> DELETE
|
||||
#
|
||||
do_concat_test 1.2.1 {
|
||||
INSERT INTO t1 VALUES('x', 'y');
|
||||
} {
|
||||
DELETE FROM t1 WHERE a = 'x';
|
||||
}
|
||||
do_concat_test 1.2.2 {
|
||||
INSERT INTO t1 VALUES(5.0, 'five');
|
||||
} {
|
||||
UPDATE t1 SET b = 'six' WHERE a = 5.0;
|
||||
}
|
||||
|
||||
do_execsql_test 1.2.3.1 "INSERT INTO t1 VALUES('I', 'one')"
|
||||
do_concat_test 1.2.3.2 {
|
||||
DELETE FROM t1 WHERE a = 'I';
|
||||
} {
|
||||
INSERT INTO t1 VALUES('I', 'one');
|
||||
}
|
||||
do_concat_test 1.2.4 {
|
||||
DELETE FROM t1 WHERE a = 'I';
|
||||
} {
|
||||
INSERT INTO t1 VALUES('I', 'two');
|
||||
}
|
||||
do_concat_test 1.2.5 {
|
||||
UPDATE t1 SET b = 'five' WHERE a = 'I';
|
||||
} {
|
||||
UPDATE t1 SET b = 'two' WHERE a = 'I';
|
||||
}
|
||||
do_concat_test 1.2.6 {
|
||||
UPDATE t1 SET b = 'six' WHERE a = 'I';
|
||||
} {
|
||||
UPDATE t1 SET b = 'seven' WHERE a = 'I';
|
||||
}
|
||||
do_concat_test 1.2.7 {
|
||||
UPDATE t1 SET b = 'eight' WHERE a = 'I';
|
||||
} {
|
||||
DELETE FROM t1 WHERE a = 'I';
|
||||
}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test cases session5-2.* - more complex tests.
|
||||
#
|
||||
db function indirect indirect
|
||||
proc indirect {{x -1}} {
|
||||
S indirect $x
|
||||
s_prev indirect $x
|
||||
}
|
||||
do_concat_test 2.1 {
|
||||
CREATE TABLE abc(a, b, c PRIMARY KEY);
|
||||
INSERT INTO abc VALUES(NULL, NULL, 1);
|
||||
INSERT INTO abc VALUES('abcdefghijkl', NULL, 2);
|
||||
} {
|
||||
DELETE FROM abc WHERE c = 1;
|
||||
UPDATE abc SET c = 1 WHERE c = 2;
|
||||
} {
|
||||
INSERT INTO abc VALUES('abcdefghijkl', NULL, 2);
|
||||
INSERT INTO abc VALUES(1.0, 2.0, 3);
|
||||
} {
|
||||
UPDATE abc SET a = a-1;
|
||||
} {
|
||||
CREATE TABLE def(d, e, f, PRIMARY KEY(e, f));
|
||||
INSERT INTO def VALUES('x', randomblob(11000), 67);
|
||||
INSERT INTO def SELECT d, e, f+1 FROM def;
|
||||
INSERT INTO def SELECT d, e, f+2 FROM def;
|
||||
INSERT INTO def SELECT d, e, f+4 FROM def;
|
||||
} {
|
||||
DELETE FROM def WHERE rowid>4;
|
||||
} {
|
||||
INSERT INTO def SELECT d, e, f+4 FROM def;
|
||||
} {
|
||||
INSERT INTO abc VALUES(22, 44, -1);
|
||||
} {
|
||||
UPDATE abc SET c=-2 WHERE c=-1;
|
||||
UPDATE abc SET c=-3 WHERE c=-2;
|
||||
} {
|
||||
UPDATE abc SET c=-4 WHERE c=-3;
|
||||
} {
|
||||
UPDATE abc SET a=a+1 WHERE c=-3;
|
||||
UPDATE abc SET a=a+1 WHERE c=-3;
|
||||
} {
|
||||
UPDATE abc SET a=a+1 WHERE c=-3;
|
||||
UPDATE abc SET a=a+1 WHERE c=-3;
|
||||
} {
|
||||
INSERT INTO abc VALUES('one', 'two', 'three');
|
||||
} {
|
||||
SELECT indirect(1);
|
||||
UPDATE abc SET a='one point five' WHERE c = 'three';
|
||||
} {
|
||||
SELECT indirect(0);
|
||||
UPDATE abc SET a='one point six' WHERE c = 'three';
|
||||
} {
|
||||
CREATE TABLE x1(a, b, PRIMARY KEY(a));
|
||||
SELECT indirect(1);
|
||||
INSERT INTO x1 VALUES(1, 2);
|
||||
} {
|
||||
SELECT indirect(1);
|
||||
UPDATE x1 SET b = 3 WHERE a = 1;
|
||||
}
|
||||
|
||||
catch {db close}
|
||||
forcedelete test.db
|
||||
sqlite3 db test.db
|
||||
do_concat_test 2.2 {
|
||||
CREATE TABLE t1(a, b, PRIMARY KEY(b));
|
||||
CREATE TABLE t2(a PRIMARY KEY, b);
|
||||
INSERT INTO t1 VALUES('string', 1);
|
||||
INSERT INTO t1 VALUES(4, 2);
|
||||
INSERT INTO t1 VALUES(X'FFAAFFAAFFAA', 3);
|
||||
} {
|
||||
INSERT INTO t2 VALUES('one', 'two');
|
||||
INSERT INTO t2 VALUES(1, NULL);
|
||||
UPDATE t1 SET a = 5 WHERE a = 2;
|
||||
} {
|
||||
DELETE FROM t2 WHERE a = 1;
|
||||
UPDATE t1 SET a = 4 WHERE a = 2;
|
||||
INSERT INTO t2 VALUES('x', 'y');
|
||||
}
|
||||
|
||||
do_test 2.3.0 {
|
||||
catch {db close}
|
||||
forcedelete test.db
|
||||
sqlite3 db test.db
|
||||
|
||||
set sql1 ""
|
||||
set sql2 ""
|
||||
for {set i 1} {$i < 120} {incr i} {
|
||||
append sql1 "INSERT INTO x1 VALUES($i*4, $i);"
|
||||
}
|
||||
for {set i 1} {$i < 120} {incr i} {
|
||||
append sql2 "DELETE FROM x1 WHERE a = $i*4;"
|
||||
}
|
||||
set {} {}
|
||||
} {}
|
||||
do_concat_test 2.3 {
|
||||
CREATE TABLE x1(a PRIMARY KEY, b)
|
||||
} $sql1 $sql2 $sql1 $sql2
|
||||
|
||||
do_concat_test 2.4 {
|
||||
CREATE TABLE x2(a PRIMARY KEY, b);
|
||||
CREATE TABLE x3(a PRIMARY KEY, b);
|
||||
|
||||
INSERT INTO x2 VALUES('a', 'b');
|
||||
INSERT INTO x2 VALUES('x', 'y');
|
||||
INSERT INTO x3 VALUES('a', 'b');
|
||||
} {
|
||||
INSERT INTO x2 VALUES('c', 'd');
|
||||
INSERT INTO x3 VALUES('e', 'f');
|
||||
INSERT INTO x3 VALUES('x', 'y');
|
||||
}
|
||||
|
||||
do_concat_test 2.5 {
|
||||
UPDATE x3 SET b = 'Y' WHERE a = 'x'
|
||||
} {
|
||||
DELETE FROM x3 WHERE a = 'x'
|
||||
} {
|
||||
DELETE FROM x2 WHERE a = 'a'
|
||||
} {
|
||||
INSERT INTO x2 VALUES('a', 'B');
|
||||
}
|
||||
|
||||
for {set k 1} {$k <=10} {incr k} {
|
||||
do_test 2.6.$k.1 {
|
||||
drop_all_tables
|
||||
set sql1 ""
|
||||
set sql2 ""
|
||||
for {set i 1} {$i < 120} {incr i} {
|
||||
append sql1 "INSERT INTO x1 VALUES(randomblob(20+(random()%10)), $i);"
|
||||
}
|
||||
for {set i 1} {$i < 120} {incr i} {
|
||||
append sql2 "DELETE FROM x1 WHERE rowid = $i;"
|
||||
}
|
||||
set {} {}
|
||||
} {}
|
||||
do_concat_test 2.6.$k {
|
||||
CREATE TABLE x1(a PRIMARY KEY, b)
|
||||
} $sql1 $sql2 $sql1 $sql2
|
||||
}
|
||||
|
||||
for {set k 1} {$k <=10} {incr k} {
|
||||
do_test 2.7.$k.1 {
|
||||
drop_all_tables
|
||||
set sql1 ""
|
||||
set sql2 ""
|
||||
for {set i 1} {$i < 120} {incr i} {
|
||||
append sql1 {
|
||||
INSERT INTO x1 VALUES(
|
||||
CASE WHEN random()%2 THEN random() ELSE randomblob(20+random()%10) END,
|
||||
CASE WHEN random()%2 THEN random() ELSE randomblob(20+random()%10) END
|
||||
);
|
||||
}
|
||||
}
|
||||
for {set i 1} {$i < 120} {incr i} {
|
||||
append sql2 "DELETE FROM x1 WHERE rowid = $i;"
|
||||
}
|
||||
set {} {}
|
||||
} {}
|
||||
do_concat_test 2.7.$k {
|
||||
CREATE TABLE x1(a PRIMARY KEY, b)
|
||||
} $sql1 $sql2 $sql1 $sql2
|
||||
}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that schema incompatibilities are detected correctly.
|
||||
#
|
||||
# session5-3.1: Incompatible number of columns.
|
||||
# session5-3.2: Incompatible PK definition.
|
||||
#
|
||||
|
||||
do_test 3.1 {
|
||||
db close
|
||||
forcedelete test.db
|
||||
sqlite3 db test.db
|
||||
|
||||
execsql { CREATE TABLE t1(a PRIMARY KEY, b) }
|
||||
set c1 [changeset_from_sql { INSERT INTO t1 VALUES(1, 2) }]
|
||||
execsql {
|
||||
DROP TABLE t1;
|
||||
CREATE TABLE t1(a PRIMARY KEY, b, c);
|
||||
}
|
||||
set c2 [changeset_from_sql { INSERT INTO t1 VALUES(2, 3, 4) }]
|
||||
|
||||
list [catch { sqlite3changeset_concat $c1 $c2 } msg] $msg
|
||||
} {1 SQLITE_SCHEMA}
|
||||
|
||||
do_test 3.2 {
|
||||
db close
|
||||
forcedelete test.db
|
||||
sqlite3 db test.db
|
||||
|
||||
execsql { CREATE TABLE t1(a PRIMARY KEY, b) }
|
||||
set c1 [changeset_from_sql { INSERT INTO t1 VALUES(1, 2) }]
|
||||
execsql {
|
||||
DROP TABLE t1;
|
||||
CREATE TABLE t1(a, b PRIMARY KEY);
|
||||
}
|
||||
set c2 [changeset_from_sql { INSERT INTO t1 VALUES(2, 3) }]
|
||||
|
||||
list [catch { sqlite3changeset_concat $c1 $c2 } msg] $msg
|
||||
} {1 SQLITE_SCHEMA}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that concat() handles these properly:
|
||||
#
|
||||
# session5-4.1: INSERT + INSERT
|
||||
# session5-4.2: UPDATE + INSERT
|
||||
# session5-4.3: DELETE + UPDATE
|
||||
# session5-4.4: DELETE + DELETE
|
||||
#
|
||||
|
||||
proc do_concat_test2 {tn sql1 sqlX sql2 expected} {
|
||||
sqlite3session S db main ; S attach *
|
||||
execsql $sql1
|
||||
set ::c1 [S changeset]
|
||||
S delete
|
||||
|
||||
execsql $sqlX
|
||||
|
||||
sqlite3session S db main ; S attach *
|
||||
execsql $sql2
|
||||
set ::c2 [S changeset]
|
||||
S delete
|
||||
|
||||
uplevel do_test $tn [list {
|
||||
changeset_to_list [sqlite3changeset_concat $::c1 $::c2]
|
||||
}] [list [normalize_list $expected]]
|
||||
}
|
||||
|
||||
drop_all_tables db
|
||||
do_concat_test2 4.1 {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
INSERT INTO t1 VALUES('key', 'value');
|
||||
} {
|
||||
DELETE FROM t1 WHERE a = 'key';
|
||||
} {
|
||||
INSERT INTO t1 VALUES('key', 'xxx');
|
||||
} {
|
||||
{INSERT t1 0 X. {} {t key t value}}
|
||||
}
|
||||
do_concat_test2 4.2 {
|
||||
UPDATE t1 SET b = 'yyy';
|
||||
} {
|
||||
DELETE FROM t1 WHERE a = 'key';
|
||||
} {
|
||||
INSERT INTO t1 VALUES('key', 'value');
|
||||
} {
|
||||
{UPDATE t1 0 X. {t key t xxx} {{} {} t yyy}}
|
||||
}
|
||||
do_concat_test2 4.3 {
|
||||
DELETE FROM t1 WHERE a = 'key';
|
||||
} {
|
||||
INSERT INTO t1 VALUES('key', 'www');
|
||||
} {
|
||||
UPDATE t1 SET b = 'valueX' WHERE a = 'key';
|
||||
} {
|
||||
{DELETE t1 0 X. {t key t value} {}}
|
||||
}
|
||||
do_concat_test2 4.4 {
|
||||
DELETE FROM t1 WHERE a = 'key';
|
||||
} {
|
||||
INSERT INTO t1 VALUES('key', 'ttt');
|
||||
} {
|
||||
DELETE FROM t1 WHERE a = 'key';
|
||||
} {
|
||||
{DELETE t1 0 X. {t key t valueX} {}}
|
||||
}
|
||||
|
||||
finish_test
|
||||
90
ext/session/session6.test
Normal file
90
ext/session/session6.test
Normal file
@ -0,0 +1,90 @@
|
||||
# 2011 July 11
|
||||
#
|
||||
# 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 sessions extension.
|
||||
# Specifically, it tests that sessions work when the database is modified
|
||||
# using incremental blob handles.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] session_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !session {finish_test; return}
|
||||
|
||||
set testprefix session6
|
||||
|
||||
proc do_then_apply_tcl {tcl {dbname main}} {
|
||||
proc xConflict args { return "OMIT" }
|
||||
set rc [catch {
|
||||
sqlite3session S db $dbname
|
||||
db eval "SELECT name FROM $dbname.sqlite_master WHERE type = 'table'" {
|
||||
S attach $name
|
||||
}
|
||||
eval $tcl
|
||||
sqlite3changeset_apply db2 [S changeset] xConflict
|
||||
} msg]
|
||||
|
||||
catch { S delete }
|
||||
if {$rc} {error $msg}
|
||||
}
|
||||
|
||||
test_sqlite3_log x
|
||||
proc x {args} {puts $args}
|
||||
|
||||
forcedelete test.db2
|
||||
sqlite3 db2 test.db2
|
||||
|
||||
do_common_sql {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
CREATE TABLE t2(c PRIMARY KEY, d);
|
||||
}
|
||||
|
||||
# Test a blob update.
|
||||
#
|
||||
do_test 1.1 {
|
||||
do_then_apply_tcl {
|
||||
db eval { INSERT INTO t1 VALUES(1, 'helloworld') }
|
||||
db eval { INSERT INTO t2 VALUES(2, 'onetwothree') }
|
||||
}
|
||||
compare_db db db2
|
||||
} {}
|
||||
do_test 1.2 {
|
||||
do_then_apply_tcl {
|
||||
set fd [db incrblob t1 b 1]
|
||||
puts -nonewline $fd 1234567890
|
||||
close $fd
|
||||
}
|
||||
compare_db db db2
|
||||
} {}
|
||||
|
||||
# Test an attached database.
|
||||
#
|
||||
do_test 2.1 {
|
||||
forcedelete test.db3
|
||||
file copy test.db2 test.db3
|
||||
execsql { ATTACH 'test.db3' AS aux; }
|
||||
|
||||
do_then_apply_tcl {
|
||||
set fd [db incrblob aux t2 d 1]
|
||||
puts -nonewline $fd fourfivesix
|
||||
close $fd
|
||||
} aux
|
||||
|
||||
sqlite3 db3 test.db3
|
||||
compare_db db2 db3
|
||||
} {}
|
||||
|
||||
|
||||
db3 close
|
||||
db2 close
|
||||
|
||||
finish_test
|
||||
91
ext/session/session8.test
Normal file
91
ext/session/session8.test
Normal file
@ -0,0 +1,91 @@
|
||||
# 2011 July 13
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] session_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !session {finish_test; return}
|
||||
|
||||
set testprefix session8
|
||||
|
||||
proc noop {args} {}
|
||||
|
||||
# Like [dbcksum] in tester.tcl. Except this version is not sensitive
|
||||
# to changes in the value of implicit IPK columns.
|
||||
#
|
||||
proc udbcksum {db dbname} {
|
||||
if {$dbname=="temp"} {
|
||||
set master sqlite_temp_master
|
||||
} else {
|
||||
set master $dbname.sqlite_master
|
||||
}
|
||||
set alltab [$db eval "SELECT name FROM $master WHERE type='table'"]
|
||||
set txt [$db eval "SELECT * FROM $master"]\n
|
||||
foreach tab $alltab {
|
||||
append txt [lsort [$db eval "SELECT * FROM $dbname.$tab"]]\n
|
||||
}
|
||||
return [md5 $txt]
|
||||
}
|
||||
|
||||
proc do_then_undo {tn sql} {
|
||||
set ck1 [udbcksum db main]
|
||||
|
||||
sqlite3session S db main
|
||||
S attach *
|
||||
db eval $sql
|
||||
|
||||
set ck2 [udbcksum db main]
|
||||
|
||||
set invert [sqlite3changeset_invert [S changeset]]
|
||||
S delete
|
||||
sqlite3changeset_apply db $invert noop
|
||||
|
||||
set ck3 [udbcksum db main]
|
||||
|
||||
set a [expr {$ck1==$ck2}]
|
||||
set b [expr {$ck1==$ck3}]
|
||||
uplevel [list do_test $tn.1 "set {} $a" 0]
|
||||
uplevel [list do_test $tn.2 "set {} $b" 1]
|
||||
}
|
||||
|
||||
do_execsql_test 1.1 {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
INSERT INTO t1 VALUES("abc", "xyz");
|
||||
}
|
||||
do_then_undo 1.2 { INSERT INTO t1 VALUES(3, 4); }
|
||||
do_then_undo 1.3 { DELETE FROM t1 WHERE b=2; }
|
||||
do_then_undo 1.4 { UPDATE t1 SET b = 3 WHERE a = 1; }
|
||||
|
||||
do_execsql_test 2.1 {
|
||||
CREATE TABLE t2(a, b PRIMARY KEY);
|
||||
INSERT INTO t2 VALUES(1, 2);
|
||||
INSERT INTO t2 VALUES('abc', 'xyz');
|
||||
}
|
||||
do_then_undo 1.2 { INSERT INTO t2 VALUES(3, 4); }
|
||||
do_then_undo 1.3 { DELETE FROM t2 WHERE b=2; }
|
||||
do_then_undo 1.4 { UPDATE t1 SET a = '123' WHERE b = 'xyz'; }
|
||||
|
||||
do_execsql_test 3.1 {
|
||||
CREATE TABLE t3(a, b, c, d, e, PRIMARY KEY(c, e));
|
||||
INSERT INTO t3 VALUES('x', 45, 0.0, 'abcdef', 12);
|
||||
INSERT INTO t3 VALUES(45, 0.0, 'abcdef', 12, 'x');
|
||||
INSERT INTO t3 VALUES(0.0, 'abcdef', 12, 'x', 45);
|
||||
}
|
||||
|
||||
do_then_undo 3.2 { UPDATE t3 SET b=b||b WHERE e!='x' }
|
||||
do_then_undo 3.3 { UPDATE t3 SET a = 46 }
|
||||
|
||||
finish_test
|
||||
287
ext/session/session9.test
Normal file
287
ext/session/session9.test
Normal file
@ -0,0 +1,287 @@
|
||||
# 2013 July 04
|
||||
#
|
||||
# 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 tests that the sessions module handles foreign key constraint
|
||||
# violations when applying changesets as required.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] session_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !session {finish_test; return}
|
||||
set testprefix session9
|
||||
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# Basic tests.
|
||||
#
|
||||
proc populate_db {} {
|
||||
drop_all_tables
|
||||
execsql {
|
||||
PRAGMA foreign_keys = 1;
|
||||
CREATE TABLE p1(a PRIMARY KEY, b);
|
||||
CREATE TABLE c1(a PRIMARY KEY, b REFERENCES p1);
|
||||
CREATE TABLE c2(a PRIMARY KEY,
|
||||
b REFERENCES p1 DEFERRABLE INITIALLY DEFERRED
|
||||
);
|
||||
|
||||
INSERT INTO p1 VALUES(1, 'one');
|
||||
INSERT INTO p1 VALUES(2, 'two');
|
||||
INSERT INTO p1 VALUES(3, 'three');
|
||||
INSERT INTO p1 VALUES(4, 'four');
|
||||
}
|
||||
}
|
||||
|
||||
proc capture_changeset {sql} {
|
||||
sqlite3session S db main
|
||||
|
||||
foreach t [db eval {SELECT name FROM sqlite_master WHERE type='table'}] {
|
||||
S attach $t
|
||||
}
|
||||
execsql $sql
|
||||
set ret [S changeset]
|
||||
S delete
|
||||
|
||||
return $ret
|
||||
}
|
||||
|
||||
do_test 1.1 {
|
||||
populate_db
|
||||
set cc [capture_changeset {
|
||||
INSERT INTO c1 VALUES('ii', 2);
|
||||
INSERT INTO c2 VALUES('iii', 3);
|
||||
}]
|
||||
set {} {}
|
||||
} {}
|
||||
|
||||
proc xConflict {args} {
|
||||
lappend ::xConflict {*}$args
|
||||
return $::conflictret
|
||||
}
|
||||
|
||||
foreach {tn delrow trans conflictargs conflictret} {
|
||||
1 2 0 {FOREIGN_KEY 1} OMIT
|
||||
2 3 0 {FOREIGN_KEY 1} OMIT
|
||||
3 2 1 {FOREIGN_KEY 1} OMIT
|
||||
4 3 1 {FOREIGN_KEY 1} OMIT
|
||||
5 2 0 {FOREIGN_KEY 1} ABORT
|
||||
6 3 0 {FOREIGN_KEY 1} ABORT
|
||||
7 2 1 {FOREIGN_KEY 1} ABORT
|
||||
8 3 1 {FOREIGN_KEY 1} ABORT
|
||||
} {
|
||||
|
||||
set A(OMIT) {0 {}}
|
||||
set A(ABORT) {1 SQLITE_CONSTRAINT}
|
||||
do_test 1.2.$tn.1 {
|
||||
populate_db
|
||||
execsql { DELETE FROM p1 WHERE a=($delrow+0) }
|
||||
if {$trans} { execsql BEGIN }
|
||||
|
||||
set ::xConflict [list]
|
||||
list [catch {sqlite3changeset_apply db $::cc xConflict} msg] $msg
|
||||
} $A($conflictret)
|
||||
|
||||
do_test 1.2.$tn.2 { set ::xConflict } $conflictargs
|
||||
|
||||
set A(OMIT) {1 1}
|
||||
set A(ABORT) {0 0}
|
||||
do_test 1.2.$tn.3 {
|
||||
execsql { SELECT count(*) FROM c1 UNION ALL SELECT count(*) FROM c2 }
|
||||
} $A($conflictret)
|
||||
|
||||
do_test 1.2.$tn.4 { expr ![sqlite3_get_autocommit db] } $trans
|
||||
do_test 1.2.$tn.5 {
|
||||
if { $trans } { execsql COMMIT }
|
||||
} {}
|
||||
}
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# Test that closing a transaction clears the defer_foreign_keys flag.
|
||||
#
|
||||
foreach {tn open noclose close} {
|
||||
1 BEGIN {} COMMIT
|
||||
2 BEGIN {} ROLLBACK
|
||||
|
||||
3 {SAVEPOINT one} {} {RELEASE one}
|
||||
4 {SAVEPOINT one} {ROLLBACK TO one} {RELEASE one}
|
||||
} {
|
||||
execsql $open
|
||||
do_execsql_test 2.$tn.1 { PRAGMA defer_foreign_keys } {0}
|
||||
|
||||
do_execsql_test 2.$tn.2 {
|
||||
PRAGMA defer_foreign_keys = 1;
|
||||
PRAGMA defer_foreign_keys;
|
||||
} {1}
|
||||
|
||||
execsql $noclose
|
||||
do_execsql_test 2.$tn.3 { PRAGMA defer_foreign_keys } {1}
|
||||
|
||||
execsql $close
|
||||
do_execsql_test 2.$tn.4 { PRAGMA defer_foreign_keys } {0}
|
||||
}
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# Test that a cyclic relationship can be inserted and deleted.
|
||||
#
|
||||
# This situation does not come up in practice, but testing it serves to
|
||||
# show that it does not matter which order parent and child keys
|
||||
# are processed in internally when applying a changeset.
|
||||
#
|
||||
drop_all_tables
|
||||
|
||||
do_execsql_test 3.1 {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
CREATE TABLE t2(x PRIMARY KEY, y);
|
||||
}
|
||||
|
||||
# Create changesets as follows:
|
||||
#
|
||||
# $cc1 - Insert a row into t1.
|
||||
# $cc2 - Insert a row into t2.
|
||||
# $cc - Combination of $cc1 and $cc2.
|
||||
#
|
||||
# $ccdel1 - Delete the row from t1.
|
||||
# $ccdel2 - Delete the row from t2.
|
||||
# $ccdel - Combination of $cc1 and $cc2.
|
||||
#
|
||||
do_test 3.2 {
|
||||
set cc1 [capture_changeset {
|
||||
INSERT INTO t1 VALUES('one', 'value one');
|
||||
}]
|
||||
set ccdel1 [capture_changeset { DELETE FROM t1; }]
|
||||
set cc2 [capture_changeset {
|
||||
INSERT INTO t2 VALUES('value one', 'one');
|
||||
}]
|
||||
set ccdel2 [capture_changeset { DELETE FROM t2; }]
|
||||
set cc [capture_changeset {
|
||||
INSERT INTO t1 VALUES('one', 'value one');
|
||||
INSERT INTO t2 VALUES('value one', 'one');
|
||||
}]
|
||||
set ccdel [capture_changeset {
|
||||
DELETE FROM t1;
|
||||
DELETE FROM t2;
|
||||
}]
|
||||
set {} {}
|
||||
} {}
|
||||
|
||||
# Now modify the database schema to create a cyclic foreign key dependency
|
||||
# between tables t1 and t2. This means that although changesets $cc and
|
||||
# $ccdel can be applied, none of the others may without violating the
|
||||
# foreign key constraints.
|
||||
#
|
||||
do_test 3.3 {
|
||||
|
||||
drop_all_tables
|
||||
execsql {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b REFERENCES t2);
|
||||
CREATE TABLE t2(x PRIMARY KEY, y REFERENCES t1);
|
||||
}
|
||||
|
||||
|
||||
proc conflict_handler {args} { return "ABORT" }
|
||||
sqlite3changeset_apply db $cc conflict_handler
|
||||
|
||||
execsql {
|
||||
SELECT * FROM t1;
|
||||
SELECT * FROM t2;
|
||||
}
|
||||
} {one {value one} {value one} one}
|
||||
|
||||
do_test 3.3.1 {
|
||||
list [catch {sqlite3changeset_apply db $::ccdel1 conflict_handler} msg] $msg
|
||||
} {1 SQLITE_CONSTRAINT}
|
||||
|
||||
do_test 3.3.2 {
|
||||
list [catch {sqlite3changeset_apply db $::ccdel2 conflict_handler} msg] $msg
|
||||
} {1 SQLITE_CONSTRAINT}
|
||||
|
||||
do_test 3.3.4.1 {
|
||||
list [catch {sqlite3changeset_apply db $::ccdel conflict_handler} msg] $msg
|
||||
} {0 {}}
|
||||
do_execsql_test 3.3.4.2 {
|
||||
SELECT * FROM t1;
|
||||
SELECT * FROM t2;
|
||||
} {}
|
||||
|
||||
do_test 3.5.1 {
|
||||
list [catch {sqlite3changeset_apply db $::cc1 conflict_handler} msg] $msg
|
||||
} {1 SQLITE_CONSTRAINT}
|
||||
do_test 3.5.2 {
|
||||
list [catch {sqlite3changeset_apply db $::cc2 conflict_handler} msg] $msg
|
||||
} {1 SQLITE_CONSTRAINT}
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# Test that if a change that affects FK processing is not applied
|
||||
# due to a separate constraint, SQLite does not get confused and
|
||||
# increment FK counters anyway.
|
||||
#
|
||||
drop_all_tables
|
||||
do_execsql_test 4.1 {
|
||||
CREATE TABLE p1(x PRIMARY KEY, y);
|
||||
CREATE TABLE c1(a PRIMARY KEY, b REFERENCES p1);
|
||||
INSERT INTO p1 VALUES(1,1);
|
||||
}
|
||||
|
||||
do_execsql_test 4.2.1 {
|
||||
BEGIN;
|
||||
PRAGMA defer_foreign_keys = 1;
|
||||
INSERT INTO c1 VALUES('x', 'x');
|
||||
}
|
||||
do_catchsql_test 4.2.2 { COMMIT } {1 {FOREIGN KEY constraint failed}}
|
||||
do_catchsql_test 4.2.3 { ROLLBACK } {0 {}}
|
||||
|
||||
do_execsql_test 4.3.1 {
|
||||
BEGIN;
|
||||
PRAGMA defer_foreign_keys = 1;
|
||||
INSERT INTO c1 VALUES(1, 1);
|
||||
}
|
||||
do_catchsql_test 4.3.2 {
|
||||
INSERT INTO c1 VALUES(1, 'x')
|
||||
} {1 {UNIQUE constraint failed: c1.a}}
|
||||
|
||||
do_catchsql_test 4.3.3 { COMMIT } {0 {}}
|
||||
do_catchsql_test 4.3.4 { BEGIN ; COMMIT } {0 {}}
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# Test that if a DELETE change cannot be applied due to an
|
||||
# SQLITE_CONSTRAINT error thrown by a trigger program, things do not
|
||||
# go awry.
|
||||
|
||||
drop_all_tables
|
||||
reset_db
|
||||
do_execsql_test 5.1 {
|
||||
CREATE TABLE x1(x PRIMARY KEY, y);
|
||||
CREATE TABLE x2(x PRIMARY KEY, y);
|
||||
INSERT INTO x2 VALUES(1, 1);
|
||||
INSERT INTO x1 VALUES(1, 1);
|
||||
}
|
||||
|
||||
set ::cc [changeset_from_sql { DELETE FROM x1; }]
|
||||
|
||||
do_execsql_test 5.2 {
|
||||
INSERT INTO x1 VALUES(1, 1);
|
||||
CREATE TRIGGER tr1 AFTER DELETE ON x1 BEGIN
|
||||
INSERT INTO x2 VALUES(old.x, old.y);
|
||||
END;
|
||||
} {}
|
||||
|
||||
proc conflict_handler {args} { return "ABORT" }
|
||||
do_test 5.3 {
|
||||
list [catch {sqlite3changeset_apply db $::cc conflict_handler} msg] $msg
|
||||
} {1 SQLITE_ABORT}
|
||||
|
||||
do_execsql_test 5.4 {
|
||||
SELECT * FROM X1;
|
||||
} {1 1}
|
||||
|
||||
finish_test
|
||||
106
ext/session/sessionA.test
Normal file
106
ext/session/sessionA.test
Normal file
@ -0,0 +1,106 @@
|
||||
# 2013 July 04
|
||||
#
|
||||
# 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 tests that filter callbacks work as required.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] session_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !session {finish_test; return}
|
||||
set testprefix sessionA
|
||||
|
||||
|
||||
forcedelete test.db2
|
||||
sqlite3 db2 test.db2
|
||||
foreach {tn db} {1 db 2 db2} {
|
||||
do_test 1.$tn.1 {
|
||||
execsql {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
CREATE TABLE t2(a PRIMARY KEY, b);
|
||||
CREATE TABLE t3(a PRIMARY KEY, b);
|
||||
} $db
|
||||
} {}
|
||||
}
|
||||
|
||||
proc tbl_filter {zTbl} {
|
||||
return $::table_filter($zTbl)
|
||||
}
|
||||
|
||||
do_test 2.1 {
|
||||
set ::table_filter(t1) 1
|
||||
set ::table_filter(t2) 0
|
||||
set ::table_filter(t3) 1
|
||||
|
||||
sqlite3session S db main
|
||||
S table_filter tbl_filter
|
||||
|
||||
execsql {
|
||||
INSERT INTO t1 VALUES('a', 'b');
|
||||
INSERT INTO t2 VALUES('c', 'd');
|
||||
INSERT INTO t3 VALUES('e', 'f');
|
||||
}
|
||||
|
||||
set changeset [S changeset]
|
||||
S delete
|
||||
sqlite3changeset_apply db2 $changeset xConflict
|
||||
|
||||
execsql {
|
||||
SELECT * FROM t1;
|
||||
SELECT * FROM t2;
|
||||
SELECT * FROM t3;
|
||||
} db2
|
||||
} {a b e f}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that filter callbacks passed to sqlite3changeset_apply() are
|
||||
# invoked correctly.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 3.1 {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
CREATE TABLE t2(x PRIMARY KEY, y);
|
||||
}
|
||||
|
||||
do_test 3.2 {
|
||||
execsql BEGIN
|
||||
set ::cs [changeset_from_sql {
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
INSERT INTO t2 VALUES('x', 'y');
|
||||
}]
|
||||
execsql ROLLBACK
|
||||
set {} {}
|
||||
} {}
|
||||
|
||||
proc filter {x y} {
|
||||
return [string equal $x $y]
|
||||
}
|
||||
|
||||
do_test 3.3 {
|
||||
sqlite3changeset_apply db $::cs {} [list filter t1]
|
||||
execsql {
|
||||
SELECT * FROM t1;
|
||||
SELECT * FROM t2;
|
||||
}
|
||||
} {1 2}
|
||||
|
||||
do_test 3.4 {
|
||||
execsql { DELETE FROM t1 }
|
||||
sqlite3changeset_apply db $::cs {} [list filter t2]
|
||||
execsql {
|
||||
SELECT * FROM t1;
|
||||
SELECT * FROM t2;
|
||||
}
|
||||
} {x y}
|
||||
|
||||
finish_test
|
||||
508
ext/session/sessionB.test
Normal file
508
ext/session/sessionB.test
Normal file
@ -0,0 +1,508 @@
|
||||
# 2014 August 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 file implements regression tests for sessions SQLite extension.
|
||||
# Specifically, this file contains tests for "patchset" changes.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] session_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !session {finish_test; return}
|
||||
|
||||
set testprefix sessionB
|
||||
|
||||
#
|
||||
# 1.*: Test that the blobs returned by the session_patchset() API are
|
||||
# as expected. Also the sqlite3_changeset_iter functions.
|
||||
#
|
||||
# 2.*: Test that patchset blobs are handled by sqlite3changeset_apply().
|
||||
#
|
||||
# 3.*: Test that sqlite3changeset_invert() works with patchset blobs.
|
||||
# Correct behaviour is to return SQLITE_CORRUPT.
|
||||
|
||||
proc do_sql2patchset_test {tn sql res} {
|
||||
sqlite3session S db main
|
||||
S attach *
|
||||
execsql $sql
|
||||
uplevel [list do_patchset_test $tn S $res]
|
||||
S delete
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Run simple tests of the _patchset() API.
|
||||
#
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a, b, c, d, PRIMARY KEY(d, a));
|
||||
INSERT INTO t1 VALUES(1, 2, 3, 4);
|
||||
INSERT INTO t1 VALUES(5, 6, 7, 8);
|
||||
INSERT INTO t1 VALUES(9, 10, 11, 12);
|
||||
}
|
||||
|
||||
do_test 1.1 {
|
||||
sqlite3session S db main
|
||||
S attach t1
|
||||
execsql {
|
||||
INSERT INTO t1 VALUES('w', 'x', 'y', 'z');
|
||||
DELETE FROM t1 WHERE d=4;
|
||||
UPDATE t1 SET c = 14 WHERE a=5;
|
||||
}
|
||||
} {}
|
||||
|
||||
do_patchset_test 1.2 S {
|
||||
{UPDATE t1 0 X..X {i 5 {} {} {} {} i 8} {{} {} {} {} i 14 {} {}}}
|
||||
{INSERT t1 0 X..X {} {t w t x t y t z}}
|
||||
{DELETE t1 0 X..X {i 1 {} {} {} {} i 4} {}}
|
||||
}
|
||||
|
||||
do_test 1.3 {
|
||||
S delete
|
||||
} {}
|
||||
|
||||
do_sql2patchset_test 1.4 {
|
||||
DELETE FROM t1;
|
||||
} {
|
||||
{DELETE t1 0 X..X {i 5 {} {} {} {} i 8} {}}
|
||||
{DELETE t1 0 X..X {t w {} {} {} {} t z} {}}
|
||||
{DELETE t1 0 X..X {i 9 {} {} {} {} i 12} {}}
|
||||
}
|
||||
|
||||
do_sql2patchset_test 1.5 {
|
||||
INSERT INTO t1 VALUES(X'61626364', NULL, NULL, 4.2);
|
||||
INSERT INTO t1 VALUES(4.2, NULL, NULL, X'61626364');
|
||||
} {
|
||||
{INSERT t1 0 X..X {} {f 4.2 n {} n {} b abcd}}
|
||||
{INSERT t1 0 X..X {} {b abcd n {} n {} f 4.2}}
|
||||
}
|
||||
|
||||
do_sql2patchset_test 1.6 {
|
||||
UPDATE t1 SET b=45 WHERE typeof(a)=='blob';
|
||||
UPDATE t1 SET c='zzzz' WHERE typeof(a)!='blob';
|
||||
} {
|
||||
{UPDATE t1 0 X..X {f 4.2 {} {} {} {} b abcd} {{} {} {} {} t zzzz {} {}}}
|
||||
{UPDATE t1 0 X..X {b abcd {} {} {} {} f 4.2} {{} {} i 45 {} {} {} {}}}
|
||||
}
|
||||
|
||||
do_sql2patchset_test 1.7 {
|
||||
UPDATE t1 SET b='xyz' WHERE typeof(a)=='blob';
|
||||
UPDATE t1 SET c='xyz' WHERE typeof(a)!='blob';
|
||||
UPDATE t1 SET b=45 WHERE typeof(a)=='blob';
|
||||
UPDATE t1 SET c='zzzz' WHERE typeof(a)!='blob';
|
||||
} {
|
||||
}
|
||||
|
||||
do_sql2patchset_test 1.8 {
|
||||
DELETE FROM t1;
|
||||
} {
|
||||
{DELETE t1 0 X..X {f 4.2 {} {} {} {} b abcd} {}}
|
||||
{DELETE t1 0 X..X {b abcd {} {} {} {} f 4.2} {}}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Run simple tests of _apply() with patchset objects.
|
||||
#
|
||||
reset_db
|
||||
|
||||
proc noop {args} { error $args }
|
||||
proc exec_rollback_replay {sql} {
|
||||
sqlite3session S db main
|
||||
S attach *
|
||||
execsql BEGIN
|
||||
execsql $sql
|
||||
set patchset [S patchset]
|
||||
S delete
|
||||
execsql ROLLBACK
|
||||
sqlite3changeset_apply db $patchset noop
|
||||
}
|
||||
|
||||
do_execsql_test 2.0 {
|
||||
CREATE TABLE t2(a, b, c, d, PRIMARY KEY(b,c));
|
||||
CREATE TABLE t3(w, x, y, z, PRIMARY KEY(w));
|
||||
}
|
||||
|
||||
do_test 2.1 {
|
||||
exec_rollback_replay {
|
||||
INSERT INTO t2 VALUES(1, 2, 3, 4);
|
||||
INSERT INTO t2 VALUES('w', 'x', 'y', 'z');
|
||||
}
|
||||
execsql { SELECT * FROM t2 }
|
||||
} {1 2 3 4 w x y z}
|
||||
|
||||
do_test 2.2 {
|
||||
exec_rollback_replay {
|
||||
DELETE FROM t2 WHERE a=1;
|
||||
UPDATE t2 SET d = 'a';
|
||||
}
|
||||
execsql { SELECT * FROM t2 }
|
||||
} {w x y a}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# sqlite3changeset_invert()
|
||||
#
|
||||
reset_db
|
||||
|
||||
do_execsql_test 3.1 { CREATE TABLE t1(x PRIMARY KEY, y) }
|
||||
do_test 3.2 {
|
||||
sqlite3session S db main
|
||||
S attach *
|
||||
execsql { INSERT INTO t1 VALUES(1, 2) }
|
||||
set patchset [S patchset]
|
||||
S delete
|
||||
list [catch { sqlite3changeset_invert $patchset } msg] [set msg]
|
||||
} {1 SQLITE_CORRUPT}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# sqlite3changeset_concat()
|
||||
#
|
||||
reset_db
|
||||
|
||||
proc do_patchconcat_test {tn args} {
|
||||
set bRevert 0
|
||||
if {[lindex $args 0] == "-revert"} {
|
||||
set bRevert 1
|
||||
set args [lrange $args 1 end]
|
||||
}
|
||||
set nSql [expr [llength $args]-1]
|
||||
set res [lindex $args $nSql]
|
||||
set patchlist [list]
|
||||
|
||||
execsql BEGIN
|
||||
if {$bRevert} { execsql { SAVEPOINT x } }
|
||||
foreach sql [lrange $args 0 end-1] {
|
||||
sqlite3session S db main
|
||||
S attach *
|
||||
execsql $sql
|
||||
lappend patchlist [S patchset]
|
||||
S delete
|
||||
if {$bRevert} { execsql { ROLLBACK TO x } }
|
||||
}
|
||||
execsql ROLLBACK
|
||||
|
||||
set patch [lindex $patchlist 0]
|
||||
foreach p [lrange $patchlist 1 end] {
|
||||
set patch [sqlite3changeset_concat $patch $p]
|
||||
}
|
||||
|
||||
set x [list]
|
||||
sqlite3session_foreach c $patch { lappend x $c }
|
||||
|
||||
uplevel [list do_test $tn [list set {} $x] [list {*}$res]]
|
||||
}
|
||||
|
||||
do_execsql_test 4.1.1 {
|
||||
CREATE TABLE t1(x PRIMARY KEY, y, z);
|
||||
}
|
||||
do_patchconcat_test 4.1.2 {
|
||||
INSERT INTO t1 VALUES(1, 2, 3);
|
||||
} {
|
||||
INSERT INTO t1 VALUES(4, 5, 6);
|
||||
} {
|
||||
{INSERT t1 0 X.. {} {i 1 i 2 i 3}}
|
||||
{INSERT t1 0 X.. {} {i 4 i 5 i 6}}
|
||||
}
|
||||
|
||||
do_execsql_test 4.2.1 {
|
||||
INSERT INTO t1 VALUES(1, 2, 3);
|
||||
INSERT INTO t1 VALUES(4, 5, 6);
|
||||
}
|
||||
|
||||
do_patchconcat_test 4.2.2 {
|
||||
UPDATE t1 SET z = 'abc' WHERE x=1
|
||||
} {
|
||||
UPDATE t1 SET z = 'def' WHERE x=4
|
||||
} {
|
||||
{UPDATE t1 0 X.. {i 1 {} {} {} {}} {{} {} {} {} t abc}}
|
||||
{UPDATE t1 0 X.. {i 4 {} {} {} {}} {{} {} {} {} t def}}
|
||||
}
|
||||
|
||||
do_patchconcat_test 4.2.3 {
|
||||
DELETE FROM t1 WHERE x=1;
|
||||
} {
|
||||
DELETE FROM t1 WHERE x=4;
|
||||
} {
|
||||
{DELETE t1 0 X.. {i 1 {} {} {} {}} {}}
|
||||
{DELETE t1 0 X.. {i 4 {} {} {} {}} {}}
|
||||
}
|
||||
|
||||
|
||||
do_execsql_test 4.3.1 {
|
||||
CREATE TABLE t2(a, b, c, d, PRIMARY KEY(c, b));
|
||||
INSERT INTO t2 VALUES('.', 1, 1, '.');
|
||||
INSERT INTO t2 VALUES('.', 1, 2, '.');
|
||||
INSERT INTO t2 VALUES('.', 2, 1, '.');
|
||||
INSERT INTO t2 VALUES('.', 2, 2, '.');
|
||||
}
|
||||
|
||||
# INSERT + INSERT
|
||||
do_patchconcat_test 4.3.2 -revert {
|
||||
INSERT INTO t2 VALUES('a', 'a', 'a', 'a');
|
||||
} {
|
||||
INSERT INTO t2 VALUES('b', 'a', 'a', 'b');
|
||||
} {
|
||||
{INSERT t2 0 .XX. {} {t a t a t a t a}}
|
||||
}
|
||||
|
||||
# INSERT + DELETE
|
||||
do_patchconcat_test 4.3.3 {
|
||||
INSERT INTO t2 VALUES('a', 'a', 'a', 'a');
|
||||
} {
|
||||
DELETE FROM t2 WHERE c = 'a';
|
||||
} {
|
||||
}
|
||||
|
||||
# INSERT + UPDATE
|
||||
do_patchconcat_test 4.3.4 {
|
||||
INSERT INTO t2 VALUES('a', 'a', 'a', 'a');
|
||||
} {
|
||||
UPDATE t2 SET d = 'b' WHERE c='a';
|
||||
} {
|
||||
{INSERT t2 0 .XX. {} {t a t a t a t b}}
|
||||
}
|
||||
|
||||
# UPDATE + UPDATE
|
||||
do_patchconcat_test 4.3.5 {
|
||||
UPDATE t2 SET a = 'a' WHERE c=1 AND b=2;
|
||||
} {
|
||||
UPDATE t2 SET d = 'd' WHERE c=1 AND b=2;
|
||||
} {
|
||||
{UPDATE t2 0 .XX. {{} {} i 2 i 1 {} {}} {t a {} {} {} {} t d}}
|
||||
}
|
||||
|
||||
# UPDATE + DELETE
|
||||
do_patchconcat_test 4.3.6 {
|
||||
UPDATE t2 SET a = 'a' WHERE c=1 AND b=2;
|
||||
} {
|
||||
DELETE FROM t2 WHERE c=1 AND b=2;
|
||||
} {
|
||||
{DELETE t2 0 .XX. {{} {} i 2 i 1 {} {}} {}}
|
||||
}
|
||||
|
||||
# DELETE + INSERT
|
||||
do_patchconcat_test 4.3.7 {
|
||||
DELETE FROM t2 WHERE b=1;
|
||||
} {
|
||||
INSERT INTO t2 VALUES('x', 1, 2, '.');
|
||||
} {
|
||||
{DELETE t2 0 .XX. {{} {} i 1 i 1 {} {}} {}}
|
||||
{UPDATE t2 0 .XX. {{} {} i 1 i 2 {} {}} {t x {} {} {} {} t .}}
|
||||
}
|
||||
|
||||
# DELETE + UPDATE
|
||||
do_patchconcat_test 4.3.8 -revert {
|
||||
DELETE FROM t2 WHERE b=1 AND c=2;
|
||||
} {
|
||||
UPDATE t2 SET a=5 WHERE b=1 AND c=2;
|
||||
} {
|
||||
{DELETE t2 0 .XX. {{} {} i 1 i 2 {} {}} {}}
|
||||
}
|
||||
|
||||
# DELETE + UPDATE
|
||||
do_patchconcat_test 4.3.9 -revert {
|
||||
DELETE FROM t2 WHERE b=1 AND c=2;
|
||||
} {
|
||||
DELETE FROM t2 WHERE b=1;
|
||||
} {
|
||||
{DELETE t2 0 .XX. {{} {} i 1 i 1 {} {}} {}}
|
||||
{DELETE t2 0 .XX. {{} {} i 1 i 2 {} {}} {}}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# More rigorous testing of the _patchset(), _apply and _concat() APIs.
|
||||
#
|
||||
# The inputs to each test are a populate database and a list of DML
|
||||
# statements. This test determines that the final database is the same
|
||||
# if:
|
||||
#
|
||||
# 1) the statements are executed directly on the database.
|
||||
#
|
||||
# 2) a single patchset is collected while executing the statements and
|
||||
# then applied to a copy of the original database file.
|
||||
#
|
||||
# 3) individual patchsets are collected for statement while executing
|
||||
# them and concatenated together before being applied to a copy of
|
||||
# the original database. The concatenation is done in a couple of
|
||||
# different ways - linear, pairwise etc.
|
||||
#
|
||||
# All tests, as it happens, are run with both changesets and patchsets.
|
||||
# But the focus is on patchset capabilities.
|
||||
#
|
||||
|
||||
# Return a checksum of the contents of the database file. Implicit IPK
|
||||
# columns are not included in the checksum - just modifying rowids does
|
||||
# not change the database checksum.
|
||||
#
|
||||
proc databasecksum {db} {
|
||||
set alltab [$db eval {SELECT name FROM sqlite_master WHERE type='table'}]
|
||||
foreach tab $alltab {
|
||||
$db eval "SELECT * FROM $tab LIMIT 1" res { }
|
||||
set slist [list]
|
||||
foreach col [lsort $res(*)] {
|
||||
lappend slist "quote($col)"
|
||||
}
|
||||
set sql "SELECT [join $slist ,] FROM $tab"
|
||||
append txt "[lsort [$db eval $sql]]\n"
|
||||
}
|
||||
return [md5 $txt]
|
||||
}
|
||||
|
||||
proc do_patchset_test {tn tstcmd lSql} {
|
||||
if {$tstcmd != "patchset" && $tstcmd != "changeset"} {
|
||||
error "have $tstcmd: must be patchset or changeset"
|
||||
}
|
||||
|
||||
foreach fname {test.db2 test.db3 test.db4 test.db5} {
|
||||
forcedelete $fname
|
||||
forcecopy test.db $fname
|
||||
}
|
||||
|
||||
# Execute the SQL statements on [db]. Collect a patchset for each
|
||||
# individual statement, as well as a single patchset for the entire
|
||||
# operation.
|
||||
sqlite3session S db main
|
||||
S attach *
|
||||
foreach sql $lSql {
|
||||
sqlite3session T db main
|
||||
T attach *
|
||||
db eval $sql
|
||||
lappend lPatch [T $tstcmd]
|
||||
T delete
|
||||
}
|
||||
set patchset [S $tstcmd]
|
||||
S delete
|
||||
|
||||
# Calculate a checksum for the final database.
|
||||
set cksum [databasecksum db]
|
||||
|
||||
# 1. Apply the single large patchset to test.db2
|
||||
sqlite3 db2 test.db2
|
||||
sqlite3changeset_apply db2 $patchset noop
|
||||
uplevel [list do_test $tn.1 { databasecksum db2 } $cksum ]
|
||||
db2 close
|
||||
|
||||
# 2. Apply each of the single-statement patchsets to test.db3
|
||||
sqlite3 db2 test.db3
|
||||
foreach p $lPatch {
|
||||
sqlite3changeset_apply db2 $p noop
|
||||
}
|
||||
uplevel [list do_test $tn.2 { databasecksum db2 } $cksum ]
|
||||
db2 close
|
||||
|
||||
# 3. Concatenate all single-statement patchsets into a single large
|
||||
# patchset, then apply it to test.db4.
|
||||
#
|
||||
sqlite3 db2 test.db4
|
||||
set big ""
|
||||
foreach p $lPatch {
|
||||
set big [sqlite3changeset_concat $big $p]
|
||||
}
|
||||
sqlite3changeset_apply db2 $big noop
|
||||
uplevel [list do_test $tn.3 { databasecksum db2 } $cksum ]
|
||||
db2 close
|
||||
|
||||
# 4. Concatenate all single-statement patchsets pairwise into a single
|
||||
# large patchset, then apply it to test.db5. Pairwise concatenation:
|
||||
#
|
||||
# a b c d e f g h i j k
|
||||
# -> {a b} {c d} {e f} {g h} {i j} k
|
||||
# -> {a b c d} {e f g h} {i j k}
|
||||
# -> {a b c d e f g h} {i j k}
|
||||
# -> {a b c d e f g h i j k}
|
||||
# -> APPLY!
|
||||
#
|
||||
sqlite3 db2 test.db5
|
||||
set L $lPatch
|
||||
while {[llength $L] > 1} {
|
||||
set O [list]
|
||||
for {set i 0} {$i < [llength $L]} {incr i 2} {
|
||||
if {$i==[llength $L]-1} {
|
||||
lappend O [lindex $L $i]
|
||||
} else {
|
||||
set i1 [expr $i+1]
|
||||
lappend O [sqlite3changeset_concat [lindex $L $i] [lindex $L $i1]]
|
||||
}
|
||||
}
|
||||
set L $O
|
||||
}
|
||||
sqlite3changeset_apply db2 [lindex $L 0] noop
|
||||
uplevel [list do_test $tn.4 { databasecksum db2 } $cksum ]
|
||||
db2 close
|
||||
}
|
||||
|
||||
proc do_patchset_changeset_test {tn initsql args} {
|
||||
foreach tstcmd {patchset changeset} {
|
||||
reset_db
|
||||
execsql $initsql
|
||||
set x 0
|
||||
foreach sql $args {
|
||||
incr x
|
||||
set lSql [split $sql ";"]
|
||||
uplevel [list do_patchset_test $tn.$tstcmd.$x $tstcmd $lSql]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do_patchset_changeset_test 5.1 {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b, c);
|
||||
INSERT INTO t1 VALUES(1, 2, 3);
|
||||
} {
|
||||
INSERT INTO t1 VALUES(4, 5, 6);
|
||||
DELETE FROM t1 WHERE a=1;
|
||||
} {
|
||||
INSERT INTO t1 VALUES(7, 8, 9);
|
||||
UPDATE t1 SET c = 5;
|
||||
INSERT INTO t1 VALUES(10, 11, 12);
|
||||
UPDATE t1 SET c = 6;
|
||||
INSERT INTO t1 VALUES(13, 14, 15);
|
||||
} {
|
||||
UPDATE t1 SET c=c+1;
|
||||
DELETE FROM t1 WHERE (a%2);
|
||||
}
|
||||
|
||||
do_patchset_changeset_test 5.2 {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b, c);
|
||||
CREATE TABLE t2(a, b, c, d, PRIMARY KEY(c, b));
|
||||
} {
|
||||
INSERT INTO t1 VALUES(x'00', 0, 'zero');
|
||||
INSERT INTO t1 VALUES(x'01', 1, 'one');
|
||||
INSERT INTO t1 VALUES(x'02', 4, 'four');
|
||||
INSERT INTO t1 VALUES(x'03', 9, 'nine');
|
||||
INSERT INTO t1 VALUES(x'04', 16, 'sixteen');
|
||||
INSERT INTO t1 VALUES(x'05', 25, 'twenty-five');
|
||||
} {
|
||||
UPDATE t1 SET a = b WHERE b<=4;
|
||||
INSERT INTO t2 SELECT NULL, * FROM t1;
|
||||
DELETE FROM t1 WHERE b=25;
|
||||
} {
|
||||
DELETE FROM t2;
|
||||
INSERT INTO t2 SELECT NULL, * FROM t1;
|
||||
DELETE FROM t1;
|
||||
INSERT INTO t1 SELECT b, c, d FROM t2;
|
||||
UPDATE t1 SET b = b+1;
|
||||
UPDATE t1 SET b = b+1;
|
||||
UPDATE t1 SET b = b+1;
|
||||
}
|
||||
|
||||
set initsql { CREATE TABLE t1(a, b, c, PRIMARY KEY(c, b)); }
|
||||
for {set i 0} {$i < 1000} {incr i} {
|
||||
append insert "INSERT INTO t1 VALUES($i, $i, $i);"
|
||||
append delete "DELETE FROM t1 WHERE b=$i;"
|
||||
}
|
||||
do_patchset_changeset_test 5.3 \
|
||||
$initsql $insert $delete \
|
||||
$insert $delete \
|
||||
"$insert $delete" \
|
||||
$delete
|
||||
|
||||
|
||||
finish_test
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user